Search
lxdream.org :: lxdream/src/drivers/cdrom/isoread.c :: diff
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
file annotate diff log raw
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/src/drivers/cdrom/isoread.c Wed Feb 10 18:16:19 2010 +1000
1.3 @@ -0,0 +1,392 @@
1.4 +/**
1.5 + * $Id$
1.6 + *
1.7 + * ISO9660 filesystem reading support
1.8 + *
1.9 + * Copyright (c) 2010 Nathan Keynes.
1.10 + *
1.11 + * This program is free software; you can redistribute it and/or modify
1.12 + * it under the terms of the GNU General Public License as published by
1.13 + * the Free Software Foundation; either version 2 of the License, or
1.14 + * (at your option) any later version.
1.15 + *
1.16 + * This program is distributed in the hope that it will be useful,
1.17 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.18 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.19 + * GNU General Public License for more details.
1.20 + */
1.21 +
1.22 +#include "drivers/cdrom/cdrom.h"
1.23 +#include "drivers/cdrom/isoread.h"
1.24 +#include "drivers/cdrom/iso_impl.h"
1.25 +
1.26 +#include <string.h>
1.27 +#include <errno.h>
1.28 +
1.29 +static char isofs_magic[5] = { 'C', 'D', '0', '0', '1' };
1.30 +
1.31 +#define ISO_DIR_TAG 0x52494449
1.32 +
1.33 +struct isofs_reader_dir {
1.34 + uint32_t tag;
1.35 + isofs_reader_dir_t parent;
1.36 + size_t num_entries;
1.37 + struct isofs_reader_dirent entries[];
1.38 +};
1.39 +
1.40 +struct isofs_reader {
1.41 + /**
1.42 + * Base sector source to read the filesystem from (must support Mode 1 reads)
1.43 + */
1.44 + sector_source_t source;
1.45 +
1.46 + /**
1.47 + * Offset of the source relative to the start of the (notional) disc -
1.48 + * this is subtracted from all source lba addresses.
1.49 + */
1.50 + cdrom_lba_t source_offset;
1.51 +
1.52 + /**
1.53 + * Start of the ISO9660 filesystem relative to the start of the disc.
1.54 + * (The actual superblock is at fs_start+16)
1.55 + */
1.56 + cdrom_lba_t fs_start;
1.57 +
1.58 + /**
1.59 + * If TRUE, read the little-endian side of the FS, otherwise the big-endian
1.60 + * side. (They should normally give the same result, but in case it matters...)
1.61 + */
1.62 + gboolean little_endian;
1.63 +
1.64 + /**
1.65 + * The volume sequence number, for multi-volume sets.
1.66 + */
1.67 + uint16_t volume_seq_no;
1.68 +
1.69 + char volume_label[33];
1.70 +
1.71 + /**
1.72 + * Filesystem root directory
1.73 + */
1.74 + isofs_reader_dir_t root_dir;
1.75 +};
1.76 +
1.77 +/**
1.78 + * Read a 16-bit dual-endian field using the defined endianness of the reader
1.79 + */
1.80 +#define ISO_GET_DE16( iso, field ) \
1.81 + ( ((iso)->little_endian) ? GINT16_FROM_LE(field) : GINT16_FROM_BE(*((&field)+1)) )
1.82 +
1.83 +/**
1.84 + * Read a 32-bit dual-endian field using the defined endianness of the reader
1.85 + */
1.86 +#define ISO_GET_DE32( iso, field ) \
1.87 + ( ((iso)->little_endian) ? GINT32_FROM_LE(field) : GINT32_FROM_BE(*((&field)+1)) )
1.88 +
1.89 +
1.90 +static void isofs_reader_convert_dirent( isofs_reader_t iso, isofs_reader_dirent_t dest, iso_dirent_t src,
1.91 + char **strp )
1.92 +{
1.93 + dest->start_lba = ISO_GET_DE32(iso, src->file_lba_le);
1.94 + dest->size = ISO_GET_DE32(iso, src->file_size_le);
1.95 + dest->is_dir = (src->flags & ISO_FILE_DIR) ? TRUE : FALSE;
1.96 + dest->interleave_gap = src->gap_size;
1.97 + dest->interleave_size = src->unit_size;
1.98 + dest->name = *strp;
1.99 + memcpy( *strp, src->file_id, src->file_id_len );
1.100 + (*strp)[src->file_id_len] = '\0';
1.101 + *strp += src->file_id_len + 1;
1.102 +}
1.103 +
1.104 +/**
1.105 + * Read a directory from the disc into memory.
1.106 + */
1.107 +isofs_reader_dir_t isofs_reader_read_dir( isofs_reader_t iso, cdrom_lba_t lba, size_t size )
1.108 +{
1.109 + cdrom_count_t count = (size+2047)/2048;
1.110 +
1.111 + char buf[count*2048];
1.112 +
1.113 + if( isofs_reader_read_sectors( iso, lba, count, buf ) != CDROM_ERROR_OK )
1.114 + return NULL;
1.115 +
1.116 + size_t len = 0;
1.117 + unsigned num_entries = 0, i=0, offset=0;
1.118 + /* Compute number of entries and total string length */
1.119 + while( offset < size ) {
1.120 + struct iso_dirent *p = (struct iso_dirent *)&buf[offset];
1.121 + offset += p->record_len;
1.122 + if( offset > size || p->record_len < sizeof(struct iso_dirent) )
1.123 + break; // Bad record length
1.124 + if( p->file_id_len + sizeof(struct iso_dirent)-1 > p->record_len )
1.125 + break; // Bad fileid length
1.126 + if( p->file_id_len == 1 && (p->file_id[0] == 0 || p->file_id[0] == 1 ) )
1.127 + continue; /* self and parent-dir references */
1.128 + num_entries++;
1.129 + len += p->file_id_len + 1;
1.130 + }
1.131 +
1.132 + size_t table_len = num_entries * sizeof(struct isofs_reader_dirent);
1.133 + isofs_reader_dir_t dir = g_malloc0( sizeof(struct isofs_reader_dir) + table_len + len );
1.134 + dir->tag = ISO_DIR_TAG;
1.135 + dir->num_entries = num_entries;
1.136 +
1.137 + char *strp = (char *)&dir->entries[num_entries];
1.138 + offset = 0;
1.139 + for( i=0; i < num_entries; i++ ) {
1.140 + struct iso_dirent *p;
1.141 + do {
1.142 + p = (struct iso_dirent *)&buf[offset];
1.143 + offset += p->record_len;
1.144 + /* Skip over self and parent-dir references */
1.145 + } while( p->file_id_len == 1 && (p->file_id[0] == 0 || p->file_id[0] == 1 ) );
1.146 +
1.147 + isofs_reader_convert_dirent( iso, &dir->entries[i], p, &strp );
1.148 +
1.149 + }
1.150 + return dir;
1.151 +}
1.152 +
1.153 +static gboolean isofs_reader_dirent_match_exact( const char *file, const char *find )
1.154 +{
1.155 + return strcasecmp( file, find ) == 0;
1.156 +}
1.157 +
1.158 +static gboolean isofs_reader_dirent_match_unversioned( const char *file, const char *find )
1.159 +{
1.160 + char *semi = strchr(file, ';');
1.161 + if( semi == NULL ) {
1.162 + /* Unversioned ISO file */
1.163 + return strcasecmp( file, find ) == 0;
1.164 + } else {
1.165 + int len = semi - file;
1.166 + return strncasecmp( file, find, len ) == 0 && strlen(find) == len;
1.167 + }
1.168 +}
1.169 +
1.170 +/**
1.171 + * Search a directory for a given filename. If found, return the corresponding
1.172 + * dirent structure, otherwise NULL. Comparison is case-insensitive, and returns
1.173 + * the most recent (highest numbered) version of a file in case of multiple
1.174 + * versions unless the requested component is also explicitly versioned.
1.175 + *
1.176 + * For now just do a linear search, although we could do a binary search given
1.177 + * that the directory should be sorted.
1.178 + */
1.179 +static isofs_reader_dirent_t isofs_reader_get_file_component( isofs_reader_dir_t dir, const char *component )
1.180 +{
1.181 +
1.182 + if( strchr( component, ';' ) != NULL ) {
1.183 + for( unsigned i=0; i<dir->num_entries; i++ ) {
1.184 + if( isofs_reader_dirent_match_exact(dir->entries[i].name,component) ) {
1.185 + return &dir->entries[i];
1.186 + }
1.187 + }
1.188 + } else {
1.189 + for( unsigned i=0; i<dir->num_entries; i++ ) {
1.190 + if( isofs_reader_dirent_match_unversioned(dir->entries[i].name,component) ) {
1.191 + return &dir->entries[i];
1.192 + }
1.193 + }
1.194 + }
1.195 + return NULL;
1.196 +}
1.197 +
1.198 +isofs_reader_dirent_t isofs_reader_get_file( isofs_reader_t iso, const char *pathname )
1.199 +{
1.200 + int pathlen = strlen(pathname);
1.201 + char tmp[pathlen+1];
1.202 + char *p = tmp;
1.203 + isofs_reader_dir_t dir = iso->root_dir;
1.204 +
1.205 + memcpy( tmp, pathname, pathlen+1 );
1.206 + char *q = strchr(p, '/');
1.207 + while( q != NULL ) {
1.208 + *q = '\0';
1.209 + isofs_reader_dirent_t ent = isofs_reader_get_file_component( dir, p );
1.210 + if( ent == NULL || !ent->is_dir ) {
1.211 + return NULL;
1.212 + }
1.213 + if( ent->subdir == NULL ) {
1.214 + ent->subdir = dir = isofs_reader_read_dir( iso, ent->start_lba, ent->size );
1.215 + if( dir == NULL ) {
1.216 + return NULL;
1.217 + }
1.218 + }
1.219 +
1.220 + p = q+1;
1.221 + q = strchr(p, '/');
1.222 +
1.223 + }
1.224 + return isofs_reader_get_file_component( dir, p );
1.225 +}
1.226 +
1.227 +cdrom_error_t isofs_reader_read_file( isofs_reader_t iso, isofs_reader_dirent_t file,
1.228 + size_t offset, size_t byte_count, unsigned char *buf )
1.229 +{
1.230 + char tmp[2048];
1.231 +
1.232 + if( offset + byte_count > file->size )
1.233 + return CDROM_ERROR_BADREAD;
1.234 +
1.235 + if( file->interleave_gap == 0 ) {
1.236 + cdrom_lba_t lba = file->start_lba + (offset>>11);
1.237 + lba += ((file->xa_size+2047)>>11); /* Skip XA record if present */
1.238 +
1.239 + if( (offset & 2047) != 0 ) {
1.240 + /* Read an unaligned start block */
1.241 + cdrom_error_t status = isofs_reader_read_sectors( iso, lba, 1, tmp );
1.242 + if( status != CDROM_ERROR_OK )
1.243 + return status;
1.244 + unsigned align = offset & 2047;
1.245 + size_t length = 2048 - align;
1.246 + if( length >= byte_count ) {
1.247 + memcpy( buf, &tmp[align], byte_count );
1.248 + return CDROM_ERROR_OK;
1.249 + } else {
1.250 + memcpy( buf, &tmp[align], length );
1.251 + byte_count -= length;
1.252 + buf += length;
1.253 + lba++;
1.254 + }
1.255 + }
1.256 + /* Read the bulk of the data */
1.257 + cdrom_count_t sector_count = byte_count >> 11;
1.258 + if( sector_count > 0 ) {
1.259 + cdrom_error_t status = isofs_reader_read_sectors( iso, lba, sector_count, buf );
1.260 + if( status != CDROM_ERROR_OK )
1.261 + return status;
1.262 + buf += (sector_count << 11);
1.263 + lba += sector_count;
1.264 + }
1.265 + /* Finally read a partial final block */
1.266 + if( (byte_count & 2047) != 0 ) {
1.267 + cdrom_error_t status = isofs_reader_read_sectors( iso, lba, 1, tmp );
1.268 + if( status != CDROM_ERROR_OK )
1.269 + return status;
1.270 + memcpy( buf, tmp, byte_count & 2047 );
1.271 + }
1.272 + return CDROM_ERROR_OK;
1.273 + } else {
1.274 + // ERROR("Interleaved files not supported");
1.275 + return CDROM_ERROR_BADREAD;
1.276 + }
1.277 +}
1.278 +
1.279 +void isofs_reader_destroy_dir( isofs_reader_dir_t dir )
1.280 +{
1.281 + dir->tag = 0;
1.282 + for( unsigned i=0; i<dir->num_entries; i++ ) {
1.283 + if( dir->entries[i].subdir != NULL ) {
1.284 + isofs_reader_dir_t subdir = dir->entries[i].subdir;
1.285 + dir->entries[i].subdir = NULL;
1.286 + isofs_reader_destroy_dir(dir);
1.287 + }
1.288 + }
1.289 + g_free(dir);
1.290 +}
1.291 +
1.292 +cdrom_error_t isofs_reader_read_sectors( isofs_reader_t iso, cdrom_lba_t lba, cdrom_count_t count,
1.293 + unsigned char *buf )
1.294 +{
1.295 + if( lba < iso->source_offset )
1.296 + return CDROM_ERROR_BADREAD;
1.297 + return sector_source_read_sectors( iso->source, lba - iso->source_offset, count,
1.298 + CDROM_READ_MODE2_FORM1|CDROM_READ_DATA, buf, NULL );
1.299 +}
1.300 +
1.301 +
1.302 +isofs_reader_t isofs_reader_new( sector_source_t source, cdrom_lba_t offset, cdrom_lba_t start, ERROR *err )
1.303 +{
1.304 + char buf[2048];
1.305 + iso_pvd_t pvd = (iso_pvd_t)&buf;
1.306 + unsigned i = 0;
1.307 +
1.308 + isofs_reader_t iso = g_malloc0( sizeof(struct isofs_reader) );
1.309 + if( iso == NULL ) {
1.310 + SET_ERROR( err, ENOMEM, "Unable to allocate memory" );
1.311 + return NULL;
1.312 + }
1.313 + iso->source = source;
1.314 + iso->source_offset = offset;
1.315 + iso->fs_start = start;
1.316 + iso->little_endian = TRUE;
1.317 +
1.318 + do {
1.319 + /* Find the primary volume descriptor */
1.320 + cdrom_error_t status = isofs_reader_read_sectors( iso, iso->fs_start + ISO_SUPERBLOCK_OFFSET + i, 1, buf );
1.321 + if( status != CDROM_ERROR_OK ) {
1.322 + SET_ERROR( err, EBADF, "Unable to read superblock from ISO9660 filesystem" );
1.323 + g_free(iso);
1.324 + return NULL;
1.325 + }
1.326 + if( memcmp(pvd->tag, isofs_magic, 5) != 0 || /* Not an ISO volume descriptor */
1.327 + pvd->desc_type == ISO_TERMINAL_DESCRIPTOR ) { /* Reached the end of the descriptor list */
1.328 + SET_ERROR( err, EINVAL, "ISO9660 filesystem not found" );
1.329 + g_free(iso);
1.330 + return NULL;
1.331 + }
1.332 + i++;
1.333 + } while( pvd->desc_type != ISO_PRIMARY_DESCRIPTOR );
1.334 +
1.335 + if( pvd->desc_version != 1 ) {
1.336 + SET_ERROR( err, EINVAL, "Incompatible ISO9660 filesystem" );
1.337 + g_free(iso);
1.338 + return NULL;
1.339 + }
1.340 +
1.341 + iso->volume_seq_no = ISO_GET_DE16(iso, pvd->volume_seq_le);
1.342 + memcpy( iso->volume_label, pvd->volume_id, 32 );
1.343 + for( i=32; i>0 && iso->volume_label[i-1] == ' '; i-- );
1.344 + iso->volume_label[i] = '\0';
1.345 +
1.346 + iso->root_dir = isofs_reader_read_dir( iso,
1.347 + ISO_GET_DE32(iso, pvd->root_dirent.file_lba_le),
1.348 + ISO_GET_DE32(iso, pvd->root_dirent.file_size_le) );
1.349 + if( iso->root_dir == NULL ) {
1.350 + SET_ERROR( err, EINVAL, "Unable to read root directory from ISO9660 filesystem" );
1.351 + g_free(iso);
1.352 + return NULL;
1.353 + }
1.354 +
1.355 + sector_source_ref( source );
1.356 + return iso;
1.357 +}
1.358 +
1.359 +isofs_reader_t isofs_reader_new_from_disc( cdrom_disc_t disc, cdrom_lba_t lba, ERROR *err )
1.360 +{
1.361 + return isofs_reader_new( &disc->source, 0, lba, err );
1.362 +}
1.363 +
1.364 +isofs_reader_t isofs_reader_new_from_track( cdrom_disc_t disc, cdrom_track_t track, ERROR *err )
1.365 +{
1.366 + return isofs_reader_new( &disc->source, 0, track->lba, err );
1.367 +}
1.368 +
1.369 +isofs_reader_t isofs_reader_new_from_source( sector_source_t source, ERROR *err )
1.370 +{
1.371 + return isofs_reader_new( source, 0, 0, err );
1.372 +}
1.373 +
1.374 +void isofs_reader_destroy( isofs_reader_t iso )
1.375 +{
1.376 + isofs_reader_destroy_dir( iso->root_dir );
1.377 + iso->root_dir = NULL;
1.378 + sector_source_unref( iso->source );
1.379 + iso->source = NULL;
1.380 + g_free( iso );
1.381 +}
1.382 +
1.383 +isofs_reader_dir_t isofs_reader_get_root_dir( isofs_reader_t iso )
1.384 +{
1.385 + return iso->root_dir;
1.386 +}
1.387 +
1.388 +void isofs_reader_print_dir( FILE *f, isofs_reader_dir_t dir )
1.389 +{
1.390 + fprintf( f, "Total %d files\n", dir->num_entries );
1.391 + for( unsigned i=0; i<dir->num_entries; i++ ) {
1.392 + fprintf( f, "%7d %s\n", dir->entries[i].size, dir->entries[i].name );
1.393 + }
1.394 +
1.395 +}
.