nkeynes@1099: /** nkeynes@1099: * $Id$ nkeynes@1099: * nkeynes@1099: * ISO9660 filesystem reading support nkeynes@1099: * nkeynes@1099: * Copyright (c) 2010 Nathan Keynes. nkeynes@1099: * nkeynes@1099: * This program is free software; you can redistribute it and/or modify nkeynes@1099: * it under the terms of the GNU General Public License as published by nkeynes@1099: * the Free Software Foundation; either version 2 of the License, or nkeynes@1099: * (at your option) any later version. nkeynes@1099: * nkeynes@1099: * This program is distributed in the hope that it will be useful, nkeynes@1099: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@1099: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@1099: * GNU General Public License for more details. nkeynes@1099: */ nkeynes@1099: nkeynes@1099: #include "drivers/cdrom/cdrom.h" nkeynes@1099: #include "drivers/cdrom/isoread.h" nkeynes@1099: #include "drivers/cdrom/iso_impl.h" nkeynes@1099: nkeynes@1099: #include nkeynes@1099: #include nkeynes@1099: nkeynes@1099: static char isofs_magic[5] = { 'C', 'D', '0', '0', '1' }; nkeynes@1099: nkeynes@1099: #define ISO_DIR_TAG 0x52494449 nkeynes@1099: nkeynes@1099: struct isofs_reader_dir { nkeynes@1099: uint32_t tag; nkeynes@1099: isofs_reader_dir_t parent; nkeynes@1099: size_t num_entries; nkeynes@1099: struct isofs_reader_dirent entries[]; nkeynes@1099: }; nkeynes@1099: nkeynes@1099: struct isofs_reader { nkeynes@1099: /** nkeynes@1099: * Base sector source to read the filesystem from (must support Mode 1 reads) nkeynes@1099: */ nkeynes@1099: sector_source_t source; nkeynes@1099: nkeynes@1099: /** nkeynes@1099: * Offset of the source relative to the start of the (notional) disc - nkeynes@1099: * this is subtracted from all source lba addresses. nkeynes@1099: */ nkeynes@1099: cdrom_lba_t source_offset; nkeynes@1099: nkeynes@1099: /** nkeynes@1099: * Start of the ISO9660 filesystem relative to the start of the disc. nkeynes@1099: * (The actual superblock is at fs_start+16) nkeynes@1099: */ nkeynes@1099: cdrom_lba_t fs_start; nkeynes@1099: nkeynes@1099: /** nkeynes@1099: * If TRUE, read the little-endian side of the FS, otherwise the big-endian nkeynes@1099: * side. (They should normally give the same result, but in case it matters...) nkeynes@1099: */ nkeynes@1099: gboolean little_endian; nkeynes@1099: nkeynes@1099: /** nkeynes@1099: * The volume sequence number, for multi-volume sets. nkeynes@1099: */ nkeynes@1099: uint16_t volume_seq_no; nkeynes@1099: nkeynes@1099: char volume_label[33]; nkeynes@1099: nkeynes@1099: /** nkeynes@1099: * Filesystem root directory nkeynes@1099: */ nkeynes@1099: isofs_reader_dir_t root_dir; nkeynes@1099: }; nkeynes@1099: nkeynes@1099: /** nkeynes@1099: * Read a 16-bit dual-endian field using the defined endianness of the reader nkeynes@1099: */ nkeynes@1099: #define ISO_GET_DE16( iso, field ) \ nkeynes@1099: ( ((iso)->little_endian) ? GINT16_FROM_LE(field) : GINT16_FROM_BE(*((&field)+1)) ) nkeynes@1099: nkeynes@1099: /** nkeynes@1099: * Read a 32-bit dual-endian field using the defined endianness of the reader nkeynes@1099: */ nkeynes@1099: #define ISO_GET_DE32( iso, field ) \ nkeynes@1099: ( ((iso)->little_endian) ? GINT32_FROM_LE(field) : GINT32_FROM_BE(*((&field)+1)) ) nkeynes@1099: nkeynes@1099: nkeynes@1099: static void isofs_reader_convert_dirent( isofs_reader_t iso, isofs_reader_dirent_t dest, iso_dirent_t src, nkeynes@1099: char **strp ) nkeynes@1099: { nkeynes@1099: dest->start_lba = ISO_GET_DE32(iso, src->file_lba_le); nkeynes@1099: dest->size = ISO_GET_DE32(iso, src->file_size_le); nkeynes@1099: dest->is_dir = (src->flags & ISO_FILE_DIR) ? TRUE : FALSE; nkeynes@1099: dest->interleave_gap = src->gap_size; nkeynes@1099: dest->interleave_size = src->unit_size; nkeynes@1099: dest->name = *strp; nkeynes@1099: memcpy( *strp, src->file_id, src->file_id_len ); nkeynes@1099: (*strp)[src->file_id_len] = '\0'; nkeynes@1099: *strp += src->file_id_len + 1; nkeynes@1099: } nkeynes@1099: nkeynes@1099: /** nkeynes@1099: * Read a directory from the disc into memory. nkeynes@1099: */ nkeynes@1099: isofs_reader_dir_t isofs_reader_read_dir( isofs_reader_t iso, cdrom_lba_t lba, size_t size ) nkeynes@1099: { nkeynes@1099: cdrom_count_t count = (size+2047)/2048; nkeynes@1099: nkeynes@1099: char buf[count*2048]; nkeynes@1099: nkeynes@1099: if( isofs_reader_read_sectors( iso, lba, count, buf ) != CDROM_ERROR_OK ) nkeynes@1099: return NULL; nkeynes@1099: nkeynes@1099: size_t len = 0; nkeynes@1099: unsigned num_entries = 0, i=0, offset=0; nkeynes@1099: /* Compute number of entries and total string length */ nkeynes@1099: while( offset < size ) { nkeynes@1099: struct iso_dirent *p = (struct iso_dirent *)&buf[offset]; nkeynes@1099: offset += p->record_len; nkeynes@1099: if( offset > size || p->record_len < sizeof(struct iso_dirent) ) nkeynes@1099: break; // Bad record length nkeynes@1099: if( p->file_id_len + sizeof(struct iso_dirent)-1 > p->record_len ) nkeynes@1099: break; // Bad fileid length nkeynes@1099: if( p->file_id_len == 1 && (p->file_id[0] == 0 || p->file_id[0] == 1 ) ) nkeynes@1099: continue; /* self and parent-dir references */ nkeynes@1099: num_entries++; nkeynes@1099: len += p->file_id_len + 1; nkeynes@1099: } nkeynes@1099: nkeynes@1099: size_t table_len = num_entries * sizeof(struct isofs_reader_dirent); nkeynes@1099: isofs_reader_dir_t dir = g_malloc0( sizeof(struct isofs_reader_dir) + table_len + len ); nkeynes@1099: dir->tag = ISO_DIR_TAG; nkeynes@1099: dir->num_entries = num_entries; nkeynes@1099: nkeynes@1099: char *strp = (char *)&dir->entries[num_entries]; nkeynes@1099: offset = 0; nkeynes@1099: for( i=0; i < num_entries; i++ ) { nkeynes@1099: struct iso_dirent *p; nkeynes@1099: do { nkeynes@1099: p = (struct iso_dirent *)&buf[offset]; nkeynes@1099: offset += p->record_len; nkeynes@1099: /* Skip over self and parent-dir references */ nkeynes@1099: } while( p->file_id_len == 1 && (p->file_id[0] == 0 || p->file_id[0] == 1 ) ); nkeynes@1099: nkeynes@1099: isofs_reader_convert_dirent( iso, &dir->entries[i], p, &strp ); nkeynes@1099: nkeynes@1099: } nkeynes@1099: return dir; nkeynes@1099: } nkeynes@1099: nkeynes@1099: static gboolean isofs_reader_dirent_match_exact( const char *file, const char *find ) nkeynes@1099: { nkeynes@1099: return strcasecmp( file, find ) == 0; nkeynes@1099: } nkeynes@1099: nkeynes@1099: static gboolean isofs_reader_dirent_match_unversioned( const char *file, const char *find ) nkeynes@1099: { nkeynes@1099: char *semi = strchr(file, ';'); nkeynes@1099: if( semi == NULL ) { nkeynes@1099: /* Unversioned ISO file */ nkeynes@1099: return strcasecmp( file, find ) == 0; nkeynes@1099: } else { nkeynes@1099: int len = semi - file; nkeynes@1099: return strncasecmp( file, find, len ) == 0 && strlen(find) == len; nkeynes@1099: } nkeynes@1099: } nkeynes@1099: nkeynes@1099: /** nkeynes@1099: * Search a directory for a given filename. If found, return the corresponding nkeynes@1099: * dirent structure, otherwise NULL. Comparison is case-insensitive, and returns nkeynes@1099: * the most recent (highest numbered) version of a file in case of multiple nkeynes@1099: * versions unless the requested component is also explicitly versioned. nkeynes@1099: * nkeynes@1099: * For now just do a linear search, although we could do a binary search given nkeynes@1099: * that the directory should be sorted. nkeynes@1099: */ nkeynes@1099: static isofs_reader_dirent_t isofs_reader_get_file_component( isofs_reader_dir_t dir, const char *component ) nkeynes@1099: { nkeynes@1099: nkeynes@1099: if( strchr( component, ';' ) != NULL ) { nkeynes@1099: for( unsigned i=0; inum_entries; i++ ) { nkeynes@1099: if( isofs_reader_dirent_match_exact(dir->entries[i].name,component) ) { nkeynes@1099: return &dir->entries[i]; nkeynes@1099: } nkeynes@1099: } nkeynes@1099: } else { nkeynes@1099: for( unsigned i=0; inum_entries; i++ ) { nkeynes@1099: if( isofs_reader_dirent_match_unversioned(dir->entries[i].name,component) ) { nkeynes@1099: return &dir->entries[i]; nkeynes@1099: } nkeynes@1099: } nkeynes@1099: } nkeynes@1099: return NULL; nkeynes@1099: } nkeynes@1099: nkeynes@1099: isofs_reader_dirent_t isofs_reader_get_file( isofs_reader_t iso, const char *pathname ) nkeynes@1099: { nkeynes@1099: int pathlen = strlen(pathname); nkeynes@1099: char tmp[pathlen+1]; nkeynes@1099: char *p = tmp; nkeynes@1099: isofs_reader_dir_t dir = iso->root_dir; nkeynes@1099: nkeynes@1099: memcpy( tmp, pathname, pathlen+1 ); nkeynes@1099: char *q = strchr(p, '/'); nkeynes@1099: while( q != NULL ) { nkeynes@1099: *q = '\0'; nkeynes@1099: isofs_reader_dirent_t ent = isofs_reader_get_file_component( dir, p ); nkeynes@1099: if( ent == NULL || !ent->is_dir ) { nkeynes@1099: return NULL; nkeynes@1099: } nkeynes@1099: if( ent->subdir == NULL ) { nkeynes@1099: ent->subdir = dir = isofs_reader_read_dir( iso, ent->start_lba, ent->size ); nkeynes@1099: if( dir == NULL ) { nkeynes@1099: return NULL; nkeynes@1099: } nkeynes@1099: } nkeynes@1099: nkeynes@1099: p = q+1; nkeynes@1099: q = strchr(p, '/'); nkeynes@1099: nkeynes@1099: } nkeynes@1099: return isofs_reader_get_file_component( dir, p ); nkeynes@1099: } nkeynes@1099: nkeynes@1099: cdrom_error_t isofs_reader_read_file( isofs_reader_t iso, isofs_reader_dirent_t file, nkeynes@1099: size_t offset, size_t byte_count, unsigned char *buf ) nkeynes@1099: { nkeynes@1099: char tmp[2048]; nkeynes@1099: nkeynes@1099: if( offset + byte_count > file->size ) nkeynes@1099: return CDROM_ERROR_BADREAD; nkeynes@1099: nkeynes@1099: if( file->interleave_gap == 0 ) { nkeynes@1099: cdrom_lba_t lba = file->start_lba + (offset>>11); nkeynes@1099: lba += ((file->xa_size+2047)>>11); /* Skip XA record if present */ nkeynes@1099: nkeynes@1099: if( (offset & 2047) != 0 ) { nkeynes@1099: /* Read an unaligned start block */ nkeynes@1099: cdrom_error_t status = isofs_reader_read_sectors( iso, lba, 1, tmp ); nkeynes@1099: if( status != CDROM_ERROR_OK ) nkeynes@1099: return status; nkeynes@1099: unsigned align = offset & 2047; nkeynes@1099: size_t length = 2048 - align; nkeynes@1099: if( length >= byte_count ) { nkeynes@1099: memcpy( buf, &tmp[align], byte_count ); nkeynes@1099: return CDROM_ERROR_OK; nkeynes@1099: } else { nkeynes@1099: memcpy( buf, &tmp[align], length ); nkeynes@1099: byte_count -= length; nkeynes@1099: buf += length; nkeynes@1099: lba++; nkeynes@1099: } nkeynes@1099: } nkeynes@1099: /* Read the bulk of the data */ nkeynes@1099: cdrom_count_t sector_count = byte_count >> 11; nkeynes@1099: if( sector_count > 0 ) { nkeynes@1099: cdrom_error_t status = isofs_reader_read_sectors( iso, lba, sector_count, buf ); nkeynes@1099: if( status != CDROM_ERROR_OK ) nkeynes@1099: return status; nkeynes@1099: buf += (sector_count << 11); nkeynes@1099: lba += sector_count; nkeynes@1099: } nkeynes@1099: /* Finally read a partial final block */ nkeynes@1099: if( (byte_count & 2047) != 0 ) { nkeynes@1099: cdrom_error_t status = isofs_reader_read_sectors( iso, lba, 1, tmp ); nkeynes@1099: if( status != CDROM_ERROR_OK ) nkeynes@1099: return status; nkeynes@1099: memcpy( buf, tmp, byte_count & 2047 ); nkeynes@1099: } nkeynes@1099: return CDROM_ERROR_OK; nkeynes@1099: } else { nkeynes@1099: // ERROR("Interleaved files not supported"); nkeynes@1099: return CDROM_ERROR_BADREAD; nkeynes@1099: } nkeynes@1099: } nkeynes@1099: nkeynes@1099: void isofs_reader_destroy_dir( isofs_reader_dir_t dir ) nkeynes@1099: { nkeynes@1099: dir->tag = 0; nkeynes@1099: for( unsigned i=0; inum_entries; i++ ) { nkeynes@1099: if( dir->entries[i].subdir != NULL ) { nkeynes@1099: isofs_reader_dir_t subdir = dir->entries[i].subdir; nkeynes@1099: dir->entries[i].subdir = NULL; nkeynes@1099: isofs_reader_destroy_dir(dir); nkeynes@1099: } nkeynes@1099: } nkeynes@1099: g_free(dir); nkeynes@1099: } nkeynes@1099: nkeynes@1099: cdrom_error_t isofs_reader_read_sectors( isofs_reader_t iso, cdrom_lba_t lba, cdrom_count_t count, nkeynes@1099: unsigned char *buf ) nkeynes@1099: { nkeynes@1099: if( lba < iso->source_offset ) nkeynes@1099: return CDROM_ERROR_BADREAD; nkeynes@1099: return sector_source_read_sectors( iso->source, lba - iso->source_offset, count, nkeynes@1099: CDROM_READ_MODE2_FORM1|CDROM_READ_DATA, buf, NULL ); nkeynes@1099: } nkeynes@1099: nkeynes@1099: nkeynes@1099: isofs_reader_t isofs_reader_new( sector_source_t source, cdrom_lba_t offset, cdrom_lba_t start, ERROR *err ) nkeynes@1099: { nkeynes@1099: char buf[2048]; nkeynes@1099: iso_pvd_t pvd = (iso_pvd_t)&buf; nkeynes@1099: unsigned i = 0; nkeynes@1099: nkeynes@1099: isofs_reader_t iso = g_malloc0( sizeof(struct isofs_reader) ); nkeynes@1099: if( iso == NULL ) { nkeynes@1099: SET_ERROR( err, ENOMEM, "Unable to allocate memory" ); nkeynes@1099: return NULL; nkeynes@1099: } nkeynes@1099: iso->source = source; nkeynes@1099: iso->source_offset = offset; nkeynes@1099: iso->fs_start = start; nkeynes@1099: iso->little_endian = TRUE; nkeynes@1099: nkeynes@1099: do { nkeynes@1099: /* Find the primary volume descriptor */ nkeynes@1099: cdrom_error_t status = isofs_reader_read_sectors( iso, iso->fs_start + ISO_SUPERBLOCK_OFFSET + i, 1, buf ); nkeynes@1099: if( status != CDROM_ERROR_OK ) { nkeynes@1099: SET_ERROR( err, EBADF, "Unable to read superblock from ISO9660 filesystem" ); nkeynes@1099: g_free(iso); nkeynes@1099: return NULL; nkeynes@1099: } nkeynes@1099: if( memcmp(pvd->tag, isofs_magic, 5) != 0 || /* Not an ISO volume descriptor */ nkeynes@1099: pvd->desc_type == ISO_TERMINAL_DESCRIPTOR ) { /* Reached the end of the descriptor list */ nkeynes@1099: SET_ERROR( err, EINVAL, "ISO9660 filesystem not found" ); nkeynes@1099: g_free(iso); nkeynes@1099: return NULL; nkeynes@1099: } nkeynes@1099: i++; nkeynes@1099: } while( pvd->desc_type != ISO_PRIMARY_DESCRIPTOR ); nkeynes@1099: nkeynes@1099: if( pvd->desc_version != 1 ) { nkeynes@1099: SET_ERROR( err, EINVAL, "Incompatible ISO9660 filesystem" ); nkeynes@1099: g_free(iso); nkeynes@1099: return NULL; nkeynes@1099: } nkeynes@1099: nkeynes@1099: iso->volume_seq_no = ISO_GET_DE16(iso, pvd->volume_seq_le); nkeynes@1099: memcpy( iso->volume_label, pvd->volume_id, 32 ); nkeynes@1099: for( i=32; i>0 && iso->volume_label[i-1] == ' '; i-- ); nkeynes@1099: iso->volume_label[i] = '\0'; nkeynes@1099: nkeynes@1099: iso->root_dir = isofs_reader_read_dir( iso, nkeynes@1099: ISO_GET_DE32(iso, pvd->root_dirent.file_lba_le), nkeynes@1099: ISO_GET_DE32(iso, pvd->root_dirent.file_size_le) ); nkeynes@1099: if( iso->root_dir == NULL ) { nkeynes@1099: SET_ERROR( err, EINVAL, "Unable to read root directory from ISO9660 filesystem" ); nkeynes@1099: g_free(iso); nkeynes@1099: return NULL; nkeynes@1099: } nkeynes@1099: nkeynes@1099: sector_source_ref( source ); nkeynes@1099: return iso; nkeynes@1099: } nkeynes@1099: nkeynes@1099: isofs_reader_t isofs_reader_new_from_disc( cdrom_disc_t disc, cdrom_lba_t lba, ERROR *err ) nkeynes@1099: { nkeynes@1099: return isofs_reader_new( &disc->source, 0, lba, err ); nkeynes@1099: } nkeynes@1099: nkeynes@1099: isofs_reader_t isofs_reader_new_from_track( cdrom_disc_t disc, cdrom_track_t track, ERROR *err ) nkeynes@1099: { nkeynes@1099: return isofs_reader_new( &disc->source, 0, track->lba, err ); nkeynes@1099: } nkeynes@1099: nkeynes@1099: isofs_reader_t isofs_reader_new_from_source( sector_source_t source, ERROR *err ) nkeynes@1099: { nkeynes@1099: return isofs_reader_new( source, 0, 0, err ); nkeynes@1099: } nkeynes@1099: nkeynes@1099: void isofs_reader_destroy( isofs_reader_t iso ) nkeynes@1099: { nkeynes@1099: isofs_reader_destroy_dir( iso->root_dir ); nkeynes@1099: iso->root_dir = NULL; nkeynes@1099: sector_source_unref( iso->source ); nkeynes@1099: iso->source = NULL; nkeynes@1099: g_free( iso ); nkeynes@1099: } nkeynes@1099: nkeynes@1099: isofs_reader_dir_t isofs_reader_get_root_dir( isofs_reader_t iso ) nkeynes@1099: { nkeynes@1099: return iso->root_dir; nkeynes@1099: } nkeynes@1099: nkeynes@1099: void isofs_reader_print_dir( FILE *f, isofs_reader_dir_t dir ) nkeynes@1099: { nkeynes@1099: fprintf( f, "Total %d files\n", dir->num_entries ); nkeynes@1099: for( unsigned i=0; inum_entries; i++ ) { nkeynes@1099: fprintf( f, "%7d %s\n", dir->entries[i].size, dir->entries[i].name ); nkeynes@1099: } nkeynes@1099: nkeynes@1099: }