Search
lxdream.org :: lxdream/src/drivers/cdrom/isoread.c
lxdream 0.9.1
released Jun 29
Download Now
filename src/drivers/cdrom/isoread.c
changeset 1099:566cdeb157ec
author nkeynes
date Wed Feb 10 18:16:19 2010 +1000 (10 years ago)
permissions -rw-r--r--
last change First draft of basic ISO9660 filesystem reader
view annotate diff log raw
     1 /**
     2  * $Id$
     3  *
     4  * ISO9660 filesystem reading support
     5  *
     6  * Copyright (c) 2010 Nathan Keynes.
     7  *
     8  * This program is free software; you can redistribute it and/or modify
     9  * it under the terms of the GNU General Public License as published by
    10  * the Free Software Foundation; either version 2 of the License, or
    11  * (at your option) any later version.
    12  *
    13  * This program is distributed in the hope that it will be useful,
    14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    16  * GNU General Public License for more details.
    17  */
    19 #include "drivers/cdrom/cdrom.h"
    20 #include "drivers/cdrom/isoread.h"
    21 #include "drivers/cdrom/iso_impl.h"
    23 #include <string.h>
    24 #include <errno.h>
    26 static char isofs_magic[5] = { 'C', 'D', '0', '0', '1' };
    28 #define ISO_DIR_TAG 0x52494449
    30 struct isofs_reader_dir {
    31     uint32_t tag;
    32     isofs_reader_dir_t parent;
    33     size_t num_entries;
    34     struct isofs_reader_dirent entries[];
    35 };
    37 struct isofs_reader {
    38     /**
    39      * Base sector source to read the filesystem from (must support Mode 1 reads)
    40      */
    41     sector_source_t source;
    43     /**
    44      * Offset of the source relative to the start of the (notional) disc -
    45      * this is subtracted from all source lba addresses.
    46      */
    47     cdrom_lba_t source_offset;
    49     /**
    50      * Start of the ISO9660 filesystem relative to the start of the disc.
    51      * (The actual superblock is at fs_start+16)
    52      */
    53     cdrom_lba_t fs_start;
    55     /**
    56      * If TRUE, read the little-endian side of the FS, otherwise the big-endian
    57      * side. (They should normally give the same result, but in case it matters...)
    58      */
    59     gboolean little_endian;
    61     /**
    62      * The volume sequence number, for multi-volume sets.
    63      */
    64     uint16_t volume_seq_no;
    66     char volume_label[33];
    68     /**
    69      * Filesystem root directory
    70      */
    71     isofs_reader_dir_t root_dir;
    72 };
    74 /**
    75  * Read a 16-bit dual-endian field using the defined endianness of the reader
    76  */
    77 #define ISO_GET_DE16( iso, field ) \
    78     ( ((iso)->little_endian) ? GINT16_FROM_LE(field) : GINT16_FROM_BE(*((&field)+1)) )
    80 /**
    81  * Read a 32-bit dual-endian field using the defined endianness of the reader
    82  */
    83 #define ISO_GET_DE32( iso, field ) \
    84     ( ((iso)->little_endian) ? GINT32_FROM_LE(field) : GINT32_FROM_BE(*((&field)+1)) )
    87 static void isofs_reader_convert_dirent( isofs_reader_t iso, isofs_reader_dirent_t dest, iso_dirent_t src,
    88                                          char **strp )
    89 {
    90     dest->start_lba = ISO_GET_DE32(iso, src->file_lba_le);
    91     dest->size = ISO_GET_DE32(iso, src->file_size_le);
    92     dest->is_dir = (src->flags & ISO_FILE_DIR) ? TRUE : FALSE;
    93     dest->interleave_gap = src->gap_size;
    94     dest->interleave_size = src->unit_size;
    95     dest->name = *strp;
    96     memcpy( *strp, src->file_id, src->file_id_len );
    97     (*strp)[src->file_id_len] = '\0';
    98     *strp += src->file_id_len + 1;
    99 }
   101 /**
   102  * Read a directory from the disc into memory.
   103  */
   104 isofs_reader_dir_t isofs_reader_read_dir( isofs_reader_t iso, cdrom_lba_t lba, size_t size )
   105 {
   106     cdrom_count_t count = (size+2047)/2048;
   108     char buf[count*2048];
   110     if( isofs_reader_read_sectors( iso, lba, count, buf ) != CDROM_ERROR_OK )
   111         return NULL;
   113     size_t len = 0;
   114     unsigned num_entries = 0, i=0, offset=0;
   115     /* Compute number of entries and total string length */
   116     while( offset < size ) {
   117         struct iso_dirent *p = (struct iso_dirent *)&buf[offset];
   118         offset += p->record_len;
   119         if( offset > size || p->record_len < sizeof(struct iso_dirent) )
   120             break; // Bad record length
   121         if( p->file_id_len + sizeof(struct iso_dirent)-1 > p->record_len )
   122             break; // Bad fileid length
   123         if( p->file_id_len == 1 && (p->file_id[0] == 0 || p->file_id[0] == 1 ) )
   124             continue; /* self and parent-dir references */
   125         num_entries++;
   126         len += p->file_id_len + 1;
   127     }
   129     size_t table_len = num_entries * sizeof(struct isofs_reader_dirent);
   130     isofs_reader_dir_t dir = g_malloc0( sizeof(struct isofs_reader_dir) + table_len + len );
   131     dir->tag = ISO_DIR_TAG;
   132     dir->num_entries = num_entries;
   134     char *strp = (char *)&dir->entries[num_entries];
   135     offset = 0;
   136     for( i=0; i < num_entries; i++ ) {
   137         struct iso_dirent *p;
   138         do {
   139             p = (struct iso_dirent *)&buf[offset];
   140             offset += p->record_len;
   141             /* Skip over self and parent-dir references */
   142         } while( p->file_id_len == 1 && (p->file_id[0] == 0 || p->file_id[0] == 1 ) );
   144         isofs_reader_convert_dirent( iso, &dir->entries[i], p, &strp );
   146     }
   147     return dir;
   148 }
   150 static gboolean isofs_reader_dirent_match_exact( const char *file, const char *find )
   151 {
   152     return strcasecmp( file, find ) == 0;
   153 }
   155 static gboolean isofs_reader_dirent_match_unversioned( const char *file, const char *find )
   156 {
   157     char *semi = strchr(file, ';');
   158     if( semi == NULL ) {
   159         /* Unversioned ISO file */
   160         return strcasecmp( file, find ) == 0;
   161     } else {
   162         int len = semi - file;
   163         return strncasecmp( file, find, len ) == 0 && strlen(find) == len;
   164     }
   165 }
   167 /**
   168  * Search a directory for a given filename. If found, return the corresponding
   169  * dirent structure, otherwise NULL. Comparison is case-insensitive, and returns
   170  * the most recent (highest numbered) version of a file in case of multiple
   171  * versions unless the requested component is also explicitly versioned.
   172  *
   173  * For now just do a linear search, although we could do a binary search given
   174  * that the directory should be sorted.
   175  */
   176 static isofs_reader_dirent_t isofs_reader_get_file_component( isofs_reader_dir_t dir, const char *component )
   177 {
   179     if( strchr( component, ';' ) != NULL ) {
   180         for( unsigned i=0; i<dir->num_entries; i++ ) {
   181             if( isofs_reader_dirent_match_exact(dir->entries[i].name,component) ) {
   182                 return &dir->entries[i];
   183             }
   184         }
   185     } else {
   186         for( unsigned i=0; i<dir->num_entries; i++ ) {
   187             if( isofs_reader_dirent_match_unversioned(dir->entries[i].name,component) ) {
   188                 return &dir->entries[i];
   189             }
   190         }
   191     }
   192     return NULL;
   193 }
   195 isofs_reader_dirent_t isofs_reader_get_file( isofs_reader_t iso, const char *pathname )
   196 {
   197     int pathlen = strlen(pathname);
   198     char tmp[pathlen+1];
   199     char *p = tmp;
   200     isofs_reader_dir_t dir = iso->root_dir;
   202     memcpy( tmp, pathname, pathlen+1 );
   203     char *q = strchr(p, '/');
   204     while( q != NULL ) {
   205         *q = '\0';
   206         isofs_reader_dirent_t ent = isofs_reader_get_file_component( dir, p );
   207         if( ent == NULL || !ent->is_dir ) {
   208             return NULL;
   209         }
   210         if( ent->subdir == NULL ) {
   211             ent->subdir = dir = isofs_reader_read_dir( iso, ent->start_lba, ent->size );
   212             if( dir == NULL ) {
   213                 return NULL;
   214             }
   215         }
   217         p = q+1;
   218         q = strchr(p, '/');
   220     }
   221     return isofs_reader_get_file_component( dir, p );
   222 }
   224 cdrom_error_t isofs_reader_read_file( isofs_reader_t iso, isofs_reader_dirent_t file,
   225                                       size_t offset, size_t byte_count, unsigned char *buf )
   226 {
   227     char tmp[2048];
   229     if( offset + byte_count > file->size )
   230         return CDROM_ERROR_BADREAD;
   232     if( file->interleave_gap == 0 ) {
   233         cdrom_lba_t lba = file->start_lba + (offset>>11);
   234         lba += ((file->xa_size+2047)>>11); /* Skip XA record if present */
   236         if( (offset & 2047) != 0 ) {
   237             /* Read an unaligned start block */
   238             cdrom_error_t status = isofs_reader_read_sectors( iso, lba, 1, tmp );
   239             if( status != CDROM_ERROR_OK )
   240                 return status;
   241             unsigned align = offset & 2047;
   242             size_t length = 2048 - align;
   243             if( length >= byte_count ) {
   244                 memcpy( buf, &tmp[align], byte_count );
   245                 return CDROM_ERROR_OK;
   246             } else {
   247                 memcpy( buf, &tmp[align], length );
   248                 byte_count -= length;
   249                 buf += length;
   250                 lba++;
   251             }
   252         }
   253         /* Read the bulk of the data */
   254         cdrom_count_t sector_count = byte_count >> 11;
   255         if( sector_count > 0 ) {
   256             cdrom_error_t status = isofs_reader_read_sectors( iso, lba, sector_count, buf );
   257             if( status != CDROM_ERROR_OK )
   258                 return status;
   259             buf += (sector_count << 11);
   260             lba += sector_count;
   261         }
   262         /* Finally read a partial final block */
   263         if( (byte_count & 2047) != 0 ) {
   264             cdrom_error_t status = isofs_reader_read_sectors( iso, lba, 1, tmp );
   265             if( status != CDROM_ERROR_OK )
   266                 return status;
   267             memcpy( buf, tmp, byte_count & 2047 );
   268         }
   269         return CDROM_ERROR_OK;
   270     } else {
   271         // ERROR("Interleaved files not supported");
   272         return CDROM_ERROR_BADREAD;
   273     }
   274 }
   276 void isofs_reader_destroy_dir( isofs_reader_dir_t dir )
   277 {
   278     dir->tag = 0;
   279     for( unsigned i=0; i<dir->num_entries; i++ ) {
   280         if( dir->entries[i].subdir != NULL ) {
   281             isofs_reader_dir_t subdir = dir->entries[i].subdir;
   282             dir->entries[i].subdir = NULL;
   283             isofs_reader_destroy_dir(dir);
   284         }
   285     }
   286     g_free(dir);
   287 }
   289 cdrom_error_t isofs_reader_read_sectors( isofs_reader_t iso, cdrom_lba_t lba, cdrom_count_t count,
   290                                          unsigned char *buf )
   291 {
   292     if( lba < iso->source_offset )
   293         return CDROM_ERROR_BADREAD;
   294     return sector_source_read_sectors( iso->source, lba - iso->source_offset, count,
   295            CDROM_READ_MODE2_FORM1|CDROM_READ_DATA, buf, NULL );
   296 }
   299 isofs_reader_t isofs_reader_new( sector_source_t source, cdrom_lba_t offset, cdrom_lba_t start, ERROR *err )
   300 {
   301     char buf[2048];
   302     iso_pvd_t pvd = (iso_pvd_t)&buf;
   303     unsigned i = 0;
   305     isofs_reader_t iso = g_malloc0( sizeof(struct isofs_reader) );
   306     if( iso == NULL ) {
   307         SET_ERROR( err, ENOMEM, "Unable to allocate memory" );
   308         return NULL;
   309     }
   310     iso->source = source;
   311     iso->source_offset = offset;
   312     iso->fs_start = start;
   313     iso->little_endian = TRUE;
   315     do {
   316         /* Find the primary volume descriptor */
   317         cdrom_error_t status = isofs_reader_read_sectors( iso, iso->fs_start + ISO_SUPERBLOCK_OFFSET + i, 1, buf );
   318         if( status != CDROM_ERROR_OK ) {
   319             SET_ERROR( err, EBADF, "Unable to read superblock from ISO9660 filesystem" );
   320             g_free(iso);
   321             return NULL;
   322         }
   323         if( memcmp(pvd->tag, isofs_magic, 5) != 0 || /* Not an ISO volume descriptor */
   324                 pvd->desc_type == ISO_TERMINAL_DESCRIPTOR ) { /* Reached the end of the descriptor list */
   325             SET_ERROR( err, EINVAL, "ISO9660 filesystem not found" );
   326             g_free(iso);
   327             return NULL;
   328         }
   329         i++;
   330     } while( pvd->desc_type != ISO_PRIMARY_DESCRIPTOR );
   332     if( pvd->desc_version != 1 ) {
   333         SET_ERROR( err, EINVAL, "Incompatible ISO9660 filesystem" );
   334         g_free(iso);
   335         return NULL;
   336     }
   338     iso->volume_seq_no = ISO_GET_DE16(iso, pvd->volume_seq_le);
   339     memcpy( iso->volume_label, pvd->volume_id, 32 );
   340     for( i=32; i>0 && iso->volume_label[i-1] == ' '; i-- );
   341     iso->volume_label[i] = '\0';
   343     iso->root_dir = isofs_reader_read_dir( iso,
   344             ISO_GET_DE32(iso, pvd->root_dirent.file_lba_le),
   345             ISO_GET_DE32(iso, pvd->root_dirent.file_size_le) );
   346     if( iso->root_dir == NULL ) {
   347         SET_ERROR( err, EINVAL, "Unable to read root directory from ISO9660 filesystem" );
   348         g_free(iso);
   349         return NULL;
   350     }
   352     sector_source_ref( source );
   353     return iso;
   354 }
   356 isofs_reader_t isofs_reader_new_from_disc( cdrom_disc_t disc, cdrom_lba_t lba, ERROR *err )
   357 {
   358     return isofs_reader_new( &disc->source, 0, lba, err );
   359 }
   361 isofs_reader_t isofs_reader_new_from_track( cdrom_disc_t disc, cdrom_track_t track, ERROR *err )
   362 {
   363     return isofs_reader_new( &disc->source, 0, track->lba, err );
   364 }
   366 isofs_reader_t isofs_reader_new_from_source( sector_source_t source, ERROR *err )
   367 {
   368     return isofs_reader_new( source, 0, 0, err );
   369 }
   371 void isofs_reader_destroy( isofs_reader_t iso )
   372 {
   373     isofs_reader_destroy_dir( iso->root_dir );
   374     iso->root_dir = NULL;
   375     sector_source_unref( iso->source );
   376     iso->source = NULL;
   377     g_free( iso );
   378 }
   380 isofs_reader_dir_t isofs_reader_get_root_dir( isofs_reader_t iso )
   381 {
   382     return iso->root_dir;
   383 }
   385 void isofs_reader_print_dir( FILE *f, isofs_reader_dir_t dir )
   386 {
   387     fprintf( f, "Total %d files\n", dir->num_entries );
   388     for( unsigned i=0; i<dir->num_entries; i++ ) {
   389         fprintf( f, "%7d %s\n", dir->entries[i].size, dir->entries[i].name );
   390     }
   392 }
.