Search
lxdream.org :: lxdream/src/drivers/cdrom/cdrom.c
lxdream 0.9.1
released Jun 29
Download Now
filename src/drivers/cdrom/cdrom.c
changeset 1097:d4807997e450
next1099:566cdeb157ec
author nkeynes
date Sun Jan 31 18:35:06 2010 +1000 (12 years ago)
permissions -rw-r--r--
last change Refactor CDROM host support
- Completely separate GDROM hardware (in gdrom/gdrom.c) from generic CDROM
support (now in drivers/cdrom)
- Add concept of 'sector sources' that can be mixed and matched to create
cdrom discs (makes support of arbitrary disc types much simpler)
file annotate diff log raw
nkeynes@1097
     1
/**
nkeynes@1097
     2
 * $Id$
nkeynes@1097
     3
 *
nkeynes@1097
     4
 * Copyright (c) 2009 Nathan Keynes.
nkeynes@1097
     5
 *
nkeynes@1097
     6
 * This program is free software; you can redistribute it and/or modify
nkeynes@1097
     7
 * it under the terms of the GNU General Public License as published by
nkeynes@1097
     8
 * the Free Software Foundation; either version 2 of the License, or
nkeynes@1097
     9
 * (at your option) any later version.
nkeynes@1097
    10
 *
nkeynes@1097
    11
 * This program is distributed in the hope that it will be useful,
nkeynes@1097
    12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
nkeynes@1097
    13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
nkeynes@1097
    14
 * GNU General Public License for more details.
nkeynes@1097
    15
 */
nkeynes@1097
    16
nkeynes@1097
    17
#include <assert.h>
nkeynes@1097
    18
#include <errno.h>
nkeynes@1097
    19
#include <fcntl.h>
nkeynes@1097
    20
#include <stdio.h>
nkeynes@1097
    21
#include <string.h>
nkeynes@1097
    22
#include <glib/gmem.h>
nkeynes@1097
    23
#include <glib/gstrfuncs.h>
nkeynes@1097
    24
#include "lxdream.h"
nkeynes@1097
    25
#include "drivers/cdrom/cdrom.h"
nkeynes@1097
    26
#include "drivers/cdrom/cdimpl.h"
nkeynes@1097
    27
nkeynes@1097
    28
extern struct cdrom_disc_factory linux_cdrom_drive_factory;
nkeynes@1097
    29
extern struct cdrom_disc_factory nrg_disc_factory;
nkeynes@1097
    30
extern struct cdrom_disc_factory cdi_disc_factory;
nkeynes@1097
    31
extern struct cdrom_disc_factory gdi_disc_factory;
nkeynes@1097
    32
nkeynes@1097
    33
cdrom_disc_factory_t cdrom_disc_factories[] = {
nkeynes@1097
    34
#ifdef HAVE_LINUX_CDROM
nkeynes@1097
    35
        &linux_cdrom_drive_factory,
nkeynes@1097
    36
#endif
nkeynes@1097
    37
        &nrg_disc_factory,
nkeynes@1097
    38
        &cdi_disc_factory,
nkeynes@1097
    39
        &gdi_disc_factory,
nkeynes@1097
    40
        NULL };
nkeynes@1097
    41
nkeynes@1097
    42
/********************* Implementation Support functions ************************/
nkeynes@1097
    43
nkeynes@1097
    44
cdrom_error_t default_image_read_blocks( sector_source_t source, cdrom_lba_t lba, cdrom_count_t count,
nkeynes@1097
    45
                                         unsigned char *buf )
nkeynes@1097
    46
{
nkeynes@1097
    47
    assert( 0 && "read_blocks called on a cdrom disc" );
nkeynes@1097
    48
    return CDROM_ERROR_BADREAD;
nkeynes@1097
    49
}
nkeynes@1097
    50
nkeynes@1097
    51
cdrom_error_t default_image_read_sectors( sector_source_t source, cdrom_lba_t lba, cdrom_count_t count,
nkeynes@1097
    52
                                          cdrom_read_mode_t mode, unsigned char *buf, size_t *length )
nkeynes@1097
    53
{
nkeynes@1097
    54
    assert( IS_SECTOR_SOURCE_TYPE(source,DISC_SECTOR_SOURCE) );
nkeynes@1097
    55
    cdrom_disc_t disc = (cdrom_disc_t)source;
nkeynes@1097
    56
    size_t len = 0, tmplen;
nkeynes@1097
    57
    cdrom_count_t current = 0;
nkeynes@1097
    58
nkeynes@1097
    59
    while( current < count ) {
nkeynes@1097
    60
        cdrom_track_t track = cdrom_disc_get_track_by_lba( disc, lba + current );
nkeynes@1097
    61
        if( track == NULL )
nkeynes@1097
    62
            return CDROM_ERROR_BADREAD;
nkeynes@1097
    63
        uint32_t track_size = cdrom_disc_get_track_size( disc, track );
nkeynes@1097
    64
        cdrom_lba_t track_offset = lba + current - track->lba;
nkeynes@1097
    65
        cdrom_count_t sub_count = count - current;
nkeynes@1097
    66
        if( track_size - track_offset < sub_count )
nkeynes@1097
    67
            /* Read breaks across track boundaries. This will probably fail (due
nkeynes@1097
    68
             * to inter-track gaps), but try it just in case
nkeynes@1097
    69
             */
nkeynes@1097
    70
            sub_count = track_size - track_offset;
nkeynes@1097
    71
        cdrom_error_t err = track->source->read_sectors( track->source, track_offset, sub_count, mode, &buf[len], &tmplen );
nkeynes@1097
    72
        if( err != CDROM_ERROR_OK )
nkeynes@1097
    73
            return err;
nkeynes@1097
    74
        len += tmplen;
nkeynes@1097
    75
        current += sub_count;
nkeynes@1097
    76
    }
nkeynes@1097
    77
    *length = len;
nkeynes@1097
    78
    return CDROM_ERROR_OK;
nkeynes@1097
    79
}
nkeynes@1097
    80
nkeynes@1097
    81
void default_cdrom_disc_destroy( sector_source_t source )
nkeynes@1097
    82
{
nkeynes@1097
    83
    assert( IS_SECTOR_SOURCE_TYPE(source,DISC_SECTOR_SOURCE) );
nkeynes@1097
    84
    cdrom_disc_t disc = (cdrom_disc_t)source;
nkeynes@1097
    85
    int i;
nkeynes@1097
    86
nkeynes@1097
    87
    for( i=0; i<disc->track_count; i++ ) {
nkeynes@1097
    88
        sector_source_unref( disc->track[i].source );
nkeynes@1097
    89
    }
nkeynes@1097
    90
    sector_source_unref( disc->base_source );
nkeynes@1097
    91
    g_free( (char *)disc->name );
nkeynes@1097
    92
nkeynes@1097
    93
    default_sector_source_destroy( source );
nkeynes@1097
    94
}
nkeynes@1097
    95
nkeynes@1097
    96
cdrom_disc_t cdrom_disc_init( cdrom_disc_t disc, const char *filename )
nkeynes@1097
    97
{
nkeynes@1097
    98
    sector_source_init( &disc->source, DISC_SECTOR_SOURCE, SECTOR_UNKNOWN, 0, default_image_read_blocks,
nkeynes@1097
    99
            default_cdrom_disc_destroy );
nkeynes@1097
   100
    disc->source.read_sectors = default_image_read_sectors;
nkeynes@1097
   101
    disc->disc_type = CDROM_DISC_NONE;
nkeynes@1097
   102
    disc->track_count = disc->session_count = 0;
nkeynes@1097
   103
    for( int i=0; i<99; i++ ) {
nkeynes@1097
   104
        disc->track[i].trackno = i+1;
nkeynes@1097
   105
    }
nkeynes@1097
   106
    if( filename != NULL )
nkeynes@1097
   107
        disc->name = g_strdup(filename);
nkeynes@1097
   108
    return disc;
nkeynes@1097
   109
}
nkeynes@1097
   110
nkeynes@1097
   111
cdrom_disc_t cdrom_disc_new( const char *name, ERROR *err )
nkeynes@1097
   112
{
nkeynes@1097
   113
    cdrom_disc_t disc = g_malloc0( sizeof(struct cdrom_disc) );
nkeynes@1097
   114
    if( disc != NULL ) {
nkeynes@1097
   115
        cdrom_disc_init( disc, name );
nkeynes@1097
   116
    } else {
nkeynes@1097
   117
        SET_ERROR(err, ENOMEM, "Unable to allocate memory for cdrom disc");
nkeynes@1097
   118
    }
nkeynes@1097
   119
    return disc;
nkeynes@1097
   120
}
nkeynes@1097
   121
nkeynes@1097
   122
/**
nkeynes@1097
   123
 * Construct a new image-based disc using the given filename as the base source.
nkeynes@1097
   124
 * TOC is initialized to the empty values.
nkeynes@1097
   125
 */
nkeynes@1097
   126
static cdrom_disc_t cdrom_disc_image_new( const char *filename, ERROR *err )
nkeynes@1097
   127
{
nkeynes@1097
   128
    cdrom_disc_t disc = cdrom_disc_new( filename, err );
nkeynes@1097
   129
    if( disc != NULL && filename != NULL ) {
nkeynes@1097
   130
        disc->base_source = file_sector_source_new_filename( filename, SECTOR_UNKNOWN, 0, FILE_SECTOR_FULL_FILE );
nkeynes@1097
   131
        if( disc->base_source == NULL ) {
nkeynes@1097
   132
            SET_ERROR( err, errno, "Unable to open cdrom file '%s': %s", filename, strerror(errno) );
nkeynes@1097
   133
            cdrom_disc_unref(disc);
nkeynes@1097
   134
            disc = NULL;
nkeynes@1097
   135
        } else {
nkeynes@1097
   136
            sector_source_ref(disc->base_source);
nkeynes@1097
   137
        }
nkeynes@1097
   138
nkeynes@1097
   139
    }
nkeynes@1097
   140
    return disc;
nkeynes@1097
   141
}
nkeynes@1097
   142
nkeynes@1097
   143
cdrom_lba_t cdrom_disc_compute_leadout( cdrom_disc_t disc )
nkeynes@1097
   144
{
nkeynes@1097
   145
    if( disc->track_count == 0 ) {
nkeynes@1097
   146
        disc->leadout = 0;
nkeynes@1097
   147
    } else {
nkeynes@1097
   148
        cdrom_track_t last_track = &disc->track[disc->track_count-1];
nkeynes@1097
   149
        if( last_track->source != NULL ) {
nkeynes@1097
   150
            cdrom_lba_t leadout = last_track->lba + last_track->source->size;
nkeynes@1097
   151
            if( leadout > disc->leadout )
nkeynes@1097
   152
                disc->leadout = leadout;
nkeynes@1097
   153
        }
nkeynes@1097
   154
    }
nkeynes@1097
   155
    return disc->leadout;
nkeynes@1097
   156
}
nkeynes@1097
   157
nkeynes@1097
   158
nkeynes@1097
   159
void cdrom_disc_set_default_disc_type( cdrom_disc_t disc )
nkeynes@1097
   160
{
nkeynes@1097
   161
    int type = CDROM_DISC_NONE, i;
nkeynes@1097
   162
    for( i=0; i<disc->track_count; i++ ) {
nkeynes@1097
   163
        if( (disc->track[i].flags & TRACK_FLAG_DATA == 0) ) {
nkeynes@1097
   164
            if( type == CDROM_DISC_NONE )
nkeynes@1097
   165
                type = CDROM_DISC_AUDIO;
nkeynes@1097
   166
        } else if( disc->track[i].source != NULL &&
nkeynes@1097
   167
                   (disc->track[i].source->mode == SECTOR_MODE1 ||
nkeynes@1097
   168
                    disc->track[i].source->mode == SECTOR_RAW_NONXA) ) {
nkeynes@1097
   169
            if( type != CDROM_DISC_XA )
nkeynes@1097
   170
                type = CDROM_DISC_NONXA;
nkeynes@1097
   171
        } else {
nkeynes@1097
   172
            type = CDROM_DISC_XA;
nkeynes@1097
   173
            break;
nkeynes@1097
   174
        }
nkeynes@1097
   175
    }
nkeynes@1097
   176
    disc->disc_type = type;
nkeynes@1097
   177
}
nkeynes@1097
   178
nkeynes@1097
   179
void cdrom_disc_clear_toc( cdrom_disc_t disc )
nkeynes@1097
   180
{
nkeynes@1097
   181
    disc->disc_type = CDROM_DISC_NONE;
nkeynes@1097
   182
    disc->leadout = 0;
nkeynes@1097
   183
    disc->track_count = 0;
nkeynes@1097
   184
    disc->session_count = 0;
nkeynes@1097
   185
    for( unsigned i=0; i< CDROM_MAX_TRACKS; i++ ) {
nkeynes@1097
   186
        if( disc->track[i].source != NULL ) {
nkeynes@1097
   187
            sector_source_unref( disc->track[i].source );
nkeynes@1097
   188
            disc->track[i].source = NULL;
nkeynes@1097
   189
        }
nkeynes@1097
   190
    }
nkeynes@1097
   191
}
nkeynes@1097
   192
nkeynes@1097
   193
gboolean cdrom_disc_read_toc( cdrom_disc_t disc, ERROR *err )
nkeynes@1097
   194
{
nkeynes@1097
   195
    /* First set the defaults for an empty disc */
nkeynes@1097
   196
    cdrom_disc_clear_toc(disc);
nkeynes@1097
   197
nkeynes@1097
   198
    if( disc->read_toc(disc, err ) ) {
nkeynes@1097
   199
        /* Success - update disc type and leadout if the TOC read didn't set them */
nkeynes@1097
   200
        if( disc->disc_type == CDROM_DISC_NONE )
nkeynes@1097
   201
            cdrom_disc_set_default_disc_type(disc);
nkeynes@1097
   202
        cdrom_disc_compute_leadout(disc);
nkeynes@1097
   203
        return TRUE;
nkeynes@1097
   204
    } else {
nkeynes@1097
   205
        /* Reset to an empty disc in case the reader left things in an
nkeynes@1097
   206
         * inconsistent state */
nkeynes@1097
   207
        cdrom_disc_clear_toc(disc);
nkeynes@1097
   208
        return FALSE;
nkeynes@1097
   209
    }
nkeynes@1097
   210
}
nkeynes@1097
   211
nkeynes@1097
   212
FILE *cdrom_disc_get_base_file( cdrom_disc_t disc )
nkeynes@1097
   213
{
nkeynes@1097
   214
    return file_sector_source_get_file(disc->base_source);
nkeynes@1097
   215
}
nkeynes@1097
   216
nkeynes@1097
   217
/*************************** Public functions ***************************/
nkeynes@1097
   218
nkeynes@1097
   219
cdrom_disc_t cdrom_disc_open( const char *inFilename, ERROR *err )
nkeynes@1097
   220
{
nkeynes@1097
   221
    const gchar *filename = inFilename;
nkeynes@1097
   222
    const gchar *ext = strrchr(filename, '.');
nkeynes@1097
   223
    int i;
nkeynes@1097
   224
    cdrom_disc_factory_t extclz = NULL;
nkeynes@1097
   225
nkeynes@1097
   226
    /* Ask the drive list if it recognizes the name first */
nkeynes@1097
   227
    cdrom_drive_t drive = cdrom_drive_find(inFilename);
nkeynes@1097
   228
    if( drive != NULL ) {
nkeynes@1097
   229
        return cdrom_drive_open(drive, err);
nkeynes@1097
   230
    }
nkeynes@1097
   231
nkeynes@1097
   232
    cdrom_disc_t disc = cdrom_disc_image_new( filename, err );
nkeynes@1097
   233
    if( disc == NULL )
nkeynes@1097
   234
        return NULL;
nkeynes@1097
   235
nkeynes@1097
   236
    /* check file extensions first */
nkeynes@1097
   237
    FILE *f = file_sector_source_get_file(disc->base_source);
nkeynes@1097
   238
    if( ext != NULL ) {
nkeynes@1097
   239
        ext++; /* Skip the '.' */
nkeynes@1097
   240
        for( i=0; cdrom_disc_factories[i] != NULL; i++ ) {
nkeynes@1097
   241
            if( cdrom_disc_factories[i]->extension != NULL &&
nkeynes@1097
   242
                    strcasecmp( cdrom_disc_factories[i]->extension, ext ) == 0 ) {
nkeynes@1097
   243
                extclz = cdrom_disc_factories[i];
nkeynes@1097
   244
                if( extclz->is_valid_file(f) ) {
nkeynes@1097
   245
                    disc->read_toc = extclz->read_toc;
nkeynes@1097
   246
                }
nkeynes@1097
   247
                break;
nkeynes@1097
   248
            }
nkeynes@1097
   249
        }
nkeynes@1097
   250
    }
nkeynes@1097
   251
nkeynes@1097
   252
    if( disc->read_toc == NULL ) {
nkeynes@1097
   253
        /* Okay, fall back to magic */
nkeynes@1097
   254
        for( i=0; cdrom_disc_factories[i] != NULL; i++ ) {
nkeynes@1097
   255
            if( cdrom_disc_factories[i] != extclz &&
nkeynes@1097
   256
                cdrom_disc_factories[i]->is_valid_file(f) ) {
nkeynes@1097
   257
                disc->read_toc = cdrom_disc_factories[i]->read_toc;
nkeynes@1097
   258
                break;
nkeynes@1097
   259
            }
nkeynes@1097
   260
        }
nkeynes@1097
   261
    }
nkeynes@1097
   262
nkeynes@1097
   263
    if( disc->read_toc != NULL && cdrom_disc_read_toc( disc, err ) ) {
nkeynes@1097
   264
        /* All good */
nkeynes@1097
   265
        return disc;
nkeynes@1097
   266
    } else {
nkeynes@1097
   267
        /* No handler found for file */
nkeynes@1097
   268
        cdrom_disc_unref( disc );
nkeynes@1097
   269
        SET_ERROR( err, EINVAL, "File '%s' could not be recognized as any known image file or device type" );
nkeynes@1097
   270
        return NULL;
nkeynes@1097
   271
    }
nkeynes@1097
   272
}
nkeynes@1097
   273
nkeynes@1097
   274
/**
nkeynes@1097
   275
 * Get the track information for the given track. If there is no such track,
nkeynes@1097
   276
 * return NULL;
nkeynes@1097
   277
 */
nkeynes@1097
   278
cdrom_track_t cdrom_disc_get_track( cdrom_disc_t disc, cdrom_trackno_t track )
nkeynes@1097
   279
{
nkeynes@1097
   280
    if( track < 1 || track >= disc->track_count )
nkeynes@1097
   281
        return NULL;
nkeynes@1097
   282
    return &disc->track[track-1];
nkeynes@1097
   283
}
nkeynes@1097
   284
nkeynes@1097
   285
/**
nkeynes@1097
   286
 * Get the track information for the first track of the given session. If there
nkeynes@1097
   287
 * is no such session, return NULL;
nkeynes@1097
   288
 */
nkeynes@1097
   289
cdrom_track_t cdrom_disc_get_session( cdrom_disc_t disc, cdrom_sessionno_t session )
nkeynes@1097
   290
{
nkeynes@1097
   291
    for( unsigned i=0; i< disc->track_count; i++ ) {
nkeynes@1097
   292
        if( disc->track[i].sessionno == session )
nkeynes@1097
   293
            return &disc->track[i];
nkeynes@1097
   294
    }
nkeynes@1097
   295
    return NULL;
nkeynes@1097
   296
}
nkeynes@1097
   297
nkeynes@1097
   298
cdrom_count_t cdrom_disc_get_track_size( cdrom_disc_t disc, cdrom_track_t track )
nkeynes@1097
   299
{
nkeynes@1097
   300
    if( track->trackno == disc->track_count )
nkeynes@1097
   301
        return disc->leadout - track->lba;
nkeynes@1097
   302
    else
nkeynes@1097
   303
        return disc->track[track->trackno].lba - track->lba;
nkeynes@1097
   304
}
nkeynes@1097
   305
nkeynes@1097
   306
cdrom_track_t cdrom_disc_get_last_track( cdrom_disc_t disc )
nkeynes@1097
   307
{
nkeynes@1097
   308
    if( disc->track_count == 0 )
nkeynes@1097
   309
        return NULL;
nkeynes@1097
   310
    return &disc->track[disc->track_count-1];
nkeynes@1097
   311
}
nkeynes@1097
   312
nkeynes@1097
   313
cdrom_track_t cdrom_disc_prev_track( cdrom_disc_t disc, cdrom_track_t track )
nkeynes@1097
   314
{
nkeynes@1097
   315
    if( track->trackno <= 1 )
nkeynes@1097
   316
        return NULL;
nkeynes@1097
   317
    return cdrom_disc_get_track( disc, track->trackno-1 );
nkeynes@1097
   318
}
nkeynes@1097
   319
nkeynes@1097
   320
cdrom_track_t cdrom_disc_next_track( cdrom_disc_t disc, cdrom_track_t track )
nkeynes@1097
   321
{
nkeynes@1097
   322
    if( track->trackno >= disc->track_count )
nkeynes@1097
   323
        return NULL;
nkeynes@1097
   324
    return cdrom_disc_get_track( disc, track->trackno+1 );
nkeynes@1097
   325
}
nkeynes@1097
   326
nkeynes@1097
   327
/**
nkeynes@1097
   328
 * Find the track containing the sector specified by LBA.
nkeynes@1097
   329
 * Note: this function does not check for media change.
nkeynes@1097
   330
 * @return The track, or NULL if no track contains the sector.
nkeynes@1097
   331
 */
nkeynes@1097
   332
cdrom_track_t cdrom_disc_get_track_by_lba( cdrom_disc_t disc, cdrom_lba_t lba )
nkeynes@1097
   333
{
nkeynes@1097
   334
    if( disc->track_count == 0 || disc->track[0].lba > lba || lba >= disc->leadout )
nkeynes@1097
   335
        return NULL; /* LBA outside disc bounds */
nkeynes@1097
   336
nkeynes@1097
   337
    for( unsigned i=1; i< disc->track_count; i++ ) {
nkeynes@1097
   338
        if( lba < disc->track[i].lba )
nkeynes@1097
   339
            return &disc->track[i-1];
nkeynes@1097
   340
    }
nkeynes@1097
   341
    return &disc->track[disc->track_count-1];
nkeynes@1097
   342
}
nkeynes@1097
   343
nkeynes@1097
   344
cdrom_error_t cdrom_disc_read_sectors( cdrom_disc_t disc, cdrom_lba_t lba, cdrom_count_t count,
nkeynes@1097
   345
                                       cdrom_read_mode_t mode, unsigned char *buf, size_t *length )
nkeynes@1097
   346
{
nkeynes@1097
   347
    return disc->source.read_sectors( &disc->source, lba, count, mode, buf, length );
nkeynes@1097
   348
}
nkeynes@1097
   349
nkeynes@1097
   350
/**
nkeynes@1097
   351
 * Check if the disc contains valid media.
nkeynes@1097
   352
 * @return CDROM_ERROR_OK if disc is present, otherwise CDROM_ERROR_NODISC
nkeynes@1097
   353
 */
nkeynes@1097
   354
cdrom_error_t cdrom_disc_check_media( cdrom_disc_t disc )
nkeynes@1097
   355
{
nkeynes@1097
   356
    if( disc == NULL )
nkeynes@1097
   357
        return CDROM_ERROR_NODISC;
nkeynes@1097
   358
    if( disc->check_media != NULL )
nkeynes@1097
   359
        disc->check_media(disc);
nkeynes@1097
   360
    return disc->disc_type == CDROM_DISC_NONE ? CDROM_ERROR_NODISC : CDROM_ERROR_OK;
nkeynes@1097
   361
}
nkeynes@1097
   362
nkeynes@1097
   363
void cdrom_disc_print_toc( FILE *f, cdrom_disc_t disc )
nkeynes@1097
   364
{
nkeynes@1097
   365
    int i;
nkeynes@1097
   366
    int session = 0;
nkeynes@1097
   367
nkeynes@1097
   368
    if( disc == NULL || disc->track_count == 0 ) {
nkeynes@1097
   369
        fprintf( f, "No disc\n" );
nkeynes@1097
   370
        return;
nkeynes@1097
   371
    }
nkeynes@1097
   372
    for( i=0; i<disc->track_count; i++ ) {
nkeynes@1097
   373
        cdrom_track_t track = &disc->track[i];
nkeynes@1097
   374
        if( track->sessionno != session ) {
nkeynes@1097
   375
            session = disc->track[i].sessionno;
nkeynes@1097
   376
            fprintf( f, "Session %d:\n", session );
nkeynes@1097
   377
        }
nkeynes@1097
   378
        fprintf( f, "  %02d. %6d %02x\n", track->trackno, track->lba, track->flags );
nkeynes@1097
   379
    }
nkeynes@1097
   380
}
nkeynes@1097
   381
nkeynes@1097
   382
void cdrom_disc_dump_toc( cdrom_disc_t disc )
nkeynes@1097
   383
{
nkeynes@1097
   384
    cdrom_disc_print_toc( stderr, disc );
nkeynes@1097
   385
}
nkeynes@1097
   386
.