2fc73dd9b2b688776d81177434111c69910af046
[vlc.git] / modules / access / vcd / cdrom.c
1 /****************************************************************************
2  * cdrom.c: cdrom tools
3  *****************************************************************************
4  * Copyright (C) 1998-2001 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Johan Bilien <jobi@via.ecp.fr>
8  *          Gildas Bazin <gbazin@netcourrier.com>
9  *          Jon Lech Johansen <jon-vl@nanocrew.net>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34 #include <vlc_access.h>
35 #include <vlc_charset.h>
36 #include <limits.h>
37
38 #ifdef HAVE_UNISTD_H
39 #   include <unistd.h>
40 #endif
41
42 #include <errno.h>
43 #ifdef HAVE_SYS_TYPES_H
44 #   include <sys/types.h>
45 #endif
46 #ifdef HAVE_SYS_STAT_H
47 #   include <sys/stat.h>
48 #endif
49 #ifdef HAVE_FCNTL_H
50 #   include <fcntl.h>
51 #endif
52 #ifdef HAVE_ARPA_INET_H
53 #   include <arpa/inet.h>
54 #endif
55
56 #if defined( SYS_BSDI )
57 #   include <dvd.h>
58 #elif defined ( __APPLE__ )
59 #   include <CoreFoundation/CFBase.h>
60 #   include <IOKit/IOKitLib.h>
61 #   include <IOKit/storage/IOCDTypes.h>
62 #   include <IOKit/storage/IOCDMedia.h>
63 #   include <IOKit/storage/IOCDMediaBSDClient.h>
64 #elif defined( HAVE_SCSIREQ_IN_SYS_SCSIIO_H )
65 #   include <inttypes.h>
66 #   include <sys/cdio.h>
67 #   include <sys/scsiio.h>
68 #elif defined( HAVE_IOC_TOC_HEADER_IN_SYS_CDIO_H )
69 #   include <sys/cdio.h>
70 #   include <sys/cdrio.h>
71 #elif defined( WIN32 )
72 #   include <windows.h>
73 #   include <winioctl.h>
74 #elif defined (__linux__)
75 #   include <sys/ioctl.h>
76 #   include <linux/cdrom.h>
77 #else
78 #   error FIXME
79 #endif
80
81 #include "cdrom_internals.h"
82 #include "cdrom.h"
83 #include <vlc_charset.h>
84 #include <vlc_meta.h>
85
86 /*****************************************************************************
87  * ioctl_Open: Opens a VCD device or file and returns an opaque handle
88  *****************************************************************************/
89 vcddev_t *ioctl_Open( vlc_object_t *p_this, const char *psz_dev )
90 {
91     int i_ret;
92     int b_is_file;
93     vcddev_t *p_vcddev;
94 #ifndef WIN32
95     struct stat fileinfo;
96 #endif
97
98     if( !psz_dev ) return NULL;
99
100     /*
101      *  Initialize structure with default values
102      */
103     p_vcddev = malloc( sizeof(*p_vcddev) );
104     if( p_vcddev == NULL )
105         return NULL;
106     p_vcddev->i_vcdimage_handle = -1;
107     p_vcddev->psz_dev = NULL;
108     b_is_file = 1;
109
110     /*
111      *  Check if we are dealing with a device or a file (vcd image)
112      */
113 #ifdef WIN32
114     if( (strlen( psz_dev ) == 2 && psz_dev[1] == ':') )
115     {
116         b_is_file = 0;
117     }
118
119 #else
120     if( stat( psz_dev, &fileinfo ) < 0 )
121     {
122         free( p_vcddev );
123         return NULL;
124     }
125
126     /* Check if this is a block/char device */
127     if( S_ISBLK( fileinfo.st_mode ) || S_ISCHR( fileinfo.st_mode ) )
128         b_is_file = 0;
129 #endif
130
131     if( b_is_file )
132     {
133         i_ret = OpenVCDImage( p_this, psz_dev, p_vcddev );
134     }
135     else
136     {
137         /*
138          *  open the vcd device
139          */
140
141 #ifdef WIN32
142         i_ret = win32_vcd_open( p_this, psz_dev, p_vcddev );
143 #else
144         p_vcddev->i_device_handle = -1;
145         p_vcddev->i_device_handle = utf8_open( psz_dev, O_RDONLY | O_NONBLOCK );
146         i_ret = (p_vcddev->i_device_handle == -1) ? -1 : 0;
147 #endif
148     }
149
150     if( i_ret == 0 )
151     {
152         p_vcddev->psz_dev = (char *)strdup( psz_dev );
153     }
154     else
155     {
156         free( p_vcddev );
157         p_vcddev = NULL;
158     }
159
160     return p_vcddev;
161 }
162
163 /*****************************************************************************
164  * ioctl_Close: Closes an already opened VCD device or file.
165  *****************************************************************************/
166 void ioctl_Close( vlc_object_t * p_this, vcddev_t *p_vcddev )
167 {
168     free( p_vcddev->psz_dev );
169
170     if( p_vcddev->i_vcdimage_handle != -1 )
171     {
172         /*
173          *  vcd image mode
174          */
175
176         CloseVCDImage( p_this, p_vcddev );
177         return;
178     }
179
180     /*
181      *  vcd device mode
182      */
183
184 #ifdef WIN32
185     if( p_vcddev->h_device_handle )
186         CloseHandle( p_vcddev->h_device_handle );
187 #else
188     if( p_vcddev->i_device_handle != -1 )
189         close( p_vcddev->i_device_handle );
190 #endif
191     free( p_vcddev );
192 }
193
194 /*****************************************************************************
195  * ioctl_GetTracksMap: Read the Table of Content, fill in the pp_sectors map
196  *                     if pp_sectors is not null and return the number of
197  *                     tracks available.
198  *****************************************************************************/
199 int ioctl_GetTracksMap( vlc_object_t *p_this, const vcddev_t *p_vcddev,
200                         int **pp_sectors )
201 {
202     int i_tracks = 0;
203
204     if( p_vcddev->i_vcdimage_handle != -1 )
205     {
206         /*
207          *  vcd image mode
208          */
209
210         i_tracks = p_vcddev->i_tracks;
211
212         if( pp_sectors )
213         {
214             *pp_sectors = calloc( i_tracks + 1, sizeof(**pp_sectors) );
215             if( *pp_sectors == NULL )
216                 return 0;
217             memcpy( *pp_sectors, p_vcddev->p_sectors,
218                     (i_tracks + 1) * sizeof(**pp_sectors) );
219         }
220
221         return i_tracks;
222     }
223     else
224     {
225
226         /*
227          *  vcd device mode
228          */
229
230 #if defined( __APPLE__ )
231
232         CDTOC *pTOC;
233         int i_descriptors;
234
235         if( ( pTOC = darwin_getTOC( p_this, p_vcddev ) ) == NULL )
236         {
237             msg_Err( p_this, "failed to get the TOC" );
238             return 0;
239         }
240
241         i_descriptors = CDTOCGetDescriptorCount( pTOC );
242         i_tracks = darwin_getNumberOfTracks( pTOC, i_descriptors );
243
244         if( pp_sectors )
245         {
246             int i, i_leadout = -1;
247             CDTOCDescriptor *pTrackDescriptors;
248             u_char track;
249
250             *pp_sectors = calloc( i_tracks + 1, sizeof(**pp_sectors) );
251             if( *pp_sectors == NULL )
252             {
253                 darwin_freeTOC( pTOC );
254                 return 0;
255             }
256
257             pTrackDescriptors = pTOC->descriptors;
258
259             for( i_tracks = 0, i = 0; i < i_descriptors; i++ )
260             {
261                 track = pTrackDescriptors[i].point;
262
263                 if( track == 0xA2 )
264                     i_leadout = i;
265
266                 if( track > CD_MAX_TRACK_NO || track < CD_MIN_TRACK_NO )
267                     continue;
268
269                 (*pp_sectors)[i_tracks++] =
270                     CDConvertMSFToLBA( pTrackDescriptors[i].p );
271             }
272
273             if( i_leadout == -1 )
274             {
275                 msg_Err( p_this, "leadout not found" );
276                 free( *pp_sectors );
277                 darwin_freeTOC( pTOC );
278                 return 0;
279             }
280
281             /* set leadout sector */
282             (*pp_sectors)[i_tracks] =
283                 CDConvertMSFToLBA( pTrackDescriptors[i_leadout].p );
284         }
285
286         darwin_freeTOC( pTOC );
287
288 #elif defined( WIN32 )
289         DWORD dwBytesReturned;
290         CDROM_TOC cdrom_toc;
291
292         if( DeviceIoControl( p_vcddev->h_device_handle, IOCTL_CDROM_READ_TOC,
293                              NULL, 0, &cdrom_toc, sizeof(CDROM_TOC),
294                              &dwBytesReturned, NULL ) == 0 )
295         {
296             msg_Err( p_this, "could not read TOCHDR" );
297             return 0;
298         }
299
300         i_tracks = cdrom_toc.LastTrack - cdrom_toc.FirstTrack + 1;
301
302         if( pp_sectors )
303         {
304             *pp_sectors = calloc( i_tracks + 1, sizeof(**pp_sectors) );
305             if( *pp_sectors == NULL )
306                 return 0;
307
308             for( int i = 0 ; i <= i_tracks ; i++ )
309             {
310                 (*pp_sectors)[ i ] = MSF_TO_LBA2(
311                                            cdrom_toc.TrackData[i].Address[1],
312                                            cdrom_toc.TrackData[i].Address[2],
313                                            cdrom_toc.TrackData[i].Address[3] );
314                 msg_Dbg( p_this, "p_sectors: %i, %i", i, (*pp_sectors)[i]);
315              }
316         }
317
318 #elif defined( HAVE_IOC_TOC_HEADER_IN_SYS_CDIO_H ) \
319        || defined( HAVE_SCSIREQ_IN_SYS_SCSIIO_H )
320         struct ioc_toc_header tochdr;
321         struct ioc_read_toc_entry toc_entries;
322
323         if( ioctl( p_vcddev->i_device_handle, CDIOREADTOCHEADER, &tochdr )
324             == -1 )
325         {
326             msg_Err( p_this, "could not read TOCHDR" );
327             return 0;
328         }
329
330         i_tracks = tochdr.ending_track - tochdr.starting_track + 1;
331
332         if( pp_sectors )
333         {
334              int i;
335
336              *pp_sectors = calloc( i_tracks + 1, sizeof(**pp_sectors) );
337              if( *pp_sectors == NULL )
338                  return 0;
339
340              toc_entries.address_format = CD_LBA_FORMAT;
341              toc_entries.starting_track = 0;
342              toc_entries.data_len = ( i_tracks + 1 ) *
343                                         sizeof( struct cd_toc_entry );
344              toc_entries.data = (struct cd_toc_entry *)
345                                     malloc( toc_entries.data_len );
346              if( toc_entries.data == NULL )
347              {
348                  free( *pp_sectors );
349                  return 0;
350              }
351
352              /* Read the TOC */
353              if( ioctl( p_vcddev->i_device_handle, CDIOREADTOCENTRYS,
354                         &toc_entries ) == -1 )
355              {
356                  msg_Err( p_this, "could not read the TOC" );
357                  free( *pp_sectors );
358                  free( toc_entries.data );
359                  return 0;
360              }
361
362              /* Fill the p_sectors structure with the track/sector matches */
363              for( i = 0 ; i <= i_tracks ; i++ )
364              {
365 #if defined( HAVE_SCSIREQ_IN_SYS_SCSIIO_H )
366                  /* FIXME: is this ok? */
367                  (*pp_sectors)[ i ] = toc_entries.data[i].addr.lba;
368 #else
369                  (*pp_sectors)[ i ] = ntohl( toc_entries.data[i].addr.lba );
370 #endif
371              }
372         }
373 #else
374         struct cdrom_tochdr   tochdr;
375         struct cdrom_tocentry tocent;
376
377         /* First we read the TOC header */
378         if( ioctl( p_vcddev->i_device_handle, CDROMREADTOCHDR, &tochdr )
379             == -1 )
380         {
381             msg_Err( p_this, "could not read TOCHDR" );
382             return 0;
383         }
384
385         i_tracks = tochdr.cdth_trk1 - tochdr.cdth_trk0 + 1;
386
387         if( pp_sectors )
388         {
389             int i;
390
391             *pp_sectors = calloc( i_tracks + 1, sizeof(**pp_sectors) );
392             if( *pp_sectors == NULL )
393                 return 0;
394
395             /* Fill the p_sectors structure with the track/sector matches */
396             for( i = 0 ; i <= i_tracks ; i++ )
397             {
398                 tocent.cdte_format = CDROM_LBA;
399                 tocent.cdte_track =
400                     ( i == i_tracks ) ? CDROM_LEADOUT : tochdr.cdth_trk0 + i;
401
402                 if( ioctl( p_vcddev->i_device_handle, CDROMREADTOCENTRY,
403                            &tocent ) == -1 )
404                 {
405                     msg_Err( p_this, "could not read TOCENTRY" );
406                     free( *pp_sectors );
407                     return 0;
408                 }
409
410                 (*pp_sectors)[ i ] = tocent.cdte_addr.lba;
411             }
412         }
413 #endif
414
415         return i_tracks;
416     }
417 }
418
419 /****************************************************************************
420  * ioctl_ReadSector: Read VCD or CDDA sectors
421  ****************************************************************************/
422 int ioctl_ReadSectors( vlc_object_t *p_this, const vcddev_t *p_vcddev,
423                        int i_sector, uint8_t *p_buffer, int i_nb, int i_type )
424 {
425     uint8_t *p_block;
426     int i;
427
428     if( i_type == VCD_TYPE )
429         p_block = malloc( VCD_SECTOR_SIZE * i_nb );
430     else
431         p_block = p_buffer;
432
433     if( p_vcddev->i_vcdimage_handle != -1 )
434     {
435         /*
436          *  vcd image mode
437          */
438         if( lseek( p_vcddev->i_vcdimage_handle, i_sector * VCD_SECTOR_SIZE,
439                    SEEK_SET ) == -1 )
440         {
441             msg_Err( p_this, "Could not lseek to sector %d", i_sector );
442             if( i_type == VCD_TYPE ) free( p_block );
443             return -1;
444         }
445
446         if( read( p_vcddev->i_vcdimage_handle, p_block, VCD_SECTOR_SIZE * i_nb)
447             == -1 )
448         {
449             msg_Err( p_this, "Could not read sector %d", i_sector );
450             if( i_type == VCD_TYPE ) free( p_block );
451             return -1;
452         }
453
454     }
455     else
456     {
457
458         /*
459          *  vcd device mode
460          */
461
462 #if defined( __APPLE__ )
463         dk_cd_read_t cd_read;
464
465         memset( &cd_read, 0, sizeof(cd_read) );
466
467         cd_read.offset = i_sector * VCD_SECTOR_SIZE;
468         cd_read.sectorArea = kCDSectorAreaSync | kCDSectorAreaHeader |
469                              kCDSectorAreaSubHeader | kCDSectorAreaUser |
470                              kCDSectorAreaAuxiliary;
471         cd_read.sectorType = kCDSectorTypeUnknown;
472
473         cd_read.buffer = p_block;
474         cd_read.bufferLength = VCD_SECTOR_SIZE * i_nb;
475
476         if( ioctl( p_vcddev->i_device_handle, DKIOCCDREAD, &cd_read ) == -1 )
477         {
478             msg_Err( p_this, "could not read block %d", i_sector );
479             if( i_type == VCD_TYPE ) free( p_block );
480             return -1;
481         }
482
483 #elif defined( WIN32 )
484         DWORD dwBytesReturned;
485         RAW_READ_INFO cdrom_raw;
486
487         /* Initialize CDROM_RAW_READ structure */
488         cdrom_raw.DiskOffset.QuadPart = CD_SECTOR_SIZE * i_sector;
489         cdrom_raw.SectorCount = i_nb;
490         cdrom_raw.TrackMode =  i_type == VCD_TYPE ? XAForm2 : CDDA;
491
492         if( DeviceIoControl( p_vcddev->h_device_handle, IOCTL_CDROM_RAW_READ,
493                              &cdrom_raw, sizeof(RAW_READ_INFO), p_block,
494                              VCD_SECTOR_SIZE * i_nb, &dwBytesReturned,
495                              NULL ) == 0 )
496         {
497             if( i_type == VCD_TYPE )
498             {
499                 /* Retry in YellowMode2 */
500                 cdrom_raw.TrackMode = YellowMode2;
501                 if( DeviceIoControl( p_vcddev->h_device_handle,
502                                      IOCTL_CDROM_RAW_READ, &cdrom_raw,
503                                      sizeof(RAW_READ_INFO), p_block,
504                                      VCD_SECTOR_SIZE * i_nb, &dwBytesReturned,
505                                      NULL ) == 0 )
506                 {
507                     free( p_block );
508                     return -1;
509                 }
510             }
511             else return -1;
512         }
513
514 #elif defined( HAVE_SCSIREQ_IN_SYS_SCSIIO_H )
515         struct scsireq  sc;
516         int i_ret;
517
518         memset( &sc, 0, sizeof(sc) );
519         sc.cmd[0] = 0xBE;
520         sc.cmd[1] = i_type == VCD_TYPE ? SECTOR_TYPE_MODE2_FORM2:
521                                          SECTOR_TYPE_CDDA;
522         sc.cmd[2] = (i_sector >> 24) & 0xff;
523         sc.cmd[3] = (i_sector >> 16) & 0xff;
524         sc.cmd[4] = (i_sector >>  8) & 0xff;
525         sc.cmd[5] = (i_sector >>  0) & 0xff;
526         sc.cmd[6] = (i_nb >> 16) & 0xff;
527         sc.cmd[7] = (i_nb >>  8) & 0xff;
528         sc.cmd[8] = (i_nb      ) & 0xff;
529         sc.cmd[9] = i_type == VCD_TYPE ? READ_CD_RAW_MODE2 : READ_CD_USERDATA;
530         sc.cmd[10] = 0; /* sub channel */
531         sc.cmdlen = 12;
532         sc.databuf = (caddr_t)p_block;
533         sc.datalen = VCD_SECTOR_SIZE * i_nb;
534         sc.senselen = sizeof( sc.sense );
535         sc.flags = SCCMD_READ;
536         sc.timeout = 10000;
537
538         i_ret = ioctl( p_vcddev->i_device_handle, SCIOCCOMMAND, &sc );
539         if( i_ret == -1 )
540         {
541             msg_Err( p_this, "SCIOCCOMMAND failed" );
542             if( i_type == VCD_TYPE ) free( p_block );
543             return -1;
544         }
545         if( sc.retsts || sc.error )
546         {
547             msg_Err( p_this, "SCSI command failed: status %d error %d",
548                              sc.retsts, sc.error );
549             if( i_type == VCD_TYPE ) free( p_block );
550            return -1;
551         }
552
553 #elif defined( HAVE_IOC_TOC_HEADER_IN_SYS_CDIO_H )
554         int i_size = VCD_SECTOR_SIZE;
555
556         if( ioctl( p_vcddev->i_device_handle, CDRIOCSETBLOCKSIZE, &i_size )
557             == -1 )
558         {
559             msg_Err( p_this, "Could not set block size" );
560             if( i_type == VCD_TYPE ) free( p_block );
561             return( -1 );
562         }
563
564         if( lseek( p_vcddev->i_device_handle,
565                    i_sector * VCD_SECTOR_SIZE, SEEK_SET ) == -1 )
566         {
567             msg_Err( p_this, "Could not lseek to sector %d", i_sector );
568             if( i_type == VCD_TYPE ) free( p_block );
569             return( -1 );
570         }
571
572         if( read( p_vcddev->i_device_handle,
573                   p_block, VCD_SECTOR_SIZE * i_nb ) == -1 )
574         {
575             msg_Err( p_this, "Could not read sector %d", i_sector );
576             if( i_type == VCD_TYPE ) free( p_block );
577             return( -1 );
578         }
579
580 #else
581         for( i = 0; i < i_nb; i++ )
582         {
583             int i_dummy = i_sector + i + 2 * CD_FRAMES;
584
585 #define p_msf ((struct cdrom_msf0 *)(p_block + i * VCD_SECTOR_SIZE))
586             p_msf->minute =   i_dummy / (CD_FRAMES * CD_SECS);
587             p_msf->second = ( i_dummy % (CD_FRAMES * CD_SECS) ) / CD_FRAMES;
588             p_msf->frame =  ( i_dummy % (CD_FRAMES * CD_SECS) ) % CD_FRAMES;
589 #undef p_msf
590
591             if( ioctl( p_vcddev->i_device_handle, CDROMREADRAW,
592                        p_block + i * VCD_SECTOR_SIZE ) == -1 )
593             {
594                 msg_Err( p_this, "could not read block %i from disc",
595                          i_sector );
596
597                 if( i == 0 )
598                 {
599                     if( i_type == VCD_TYPE ) free( p_block );
600                     return( -1 );
601                 }
602                 else break;
603             }
604         }
605 #endif
606     }
607
608     /* For VCDs, we don't want to keep the header and footer of the
609      * sectors read */
610     if( i_type == VCD_TYPE )
611     {
612         for( i = 0; i < i_nb; i++ )
613         {
614             memcpy( p_buffer + i * VCD_DATA_SIZE,
615                     p_block + i * VCD_SECTOR_SIZE + VCD_DATA_START,
616                     VCD_DATA_SIZE );
617         }
618         free( p_block );
619     }
620
621     return( 0 );
622 }
623
624 /****************************************************************************
625  * Private functions
626  ****************************************************************************/
627
628 /****************************************************************************
629  * OpenVCDImage: try to open a vcd image from a .cue file
630  ****************************************************************************/
631 static int OpenVCDImage( vlc_object_t * p_this, const char *psz_dev,
632                          vcddev_t *p_vcddev )
633 {
634     int i_ret = -1;
635     char *p_pos;
636     char *psz_vcdfile = NULL;
637     char *psz_cuefile = NULL;
638     FILE *cuefile     = NULL;
639     int *p_sectors    = NULL;
640     char line[1024];
641     bool b_found      = false;
642
643     /* Check if we are dealing with a .cue file */
644     p_pos = strrchr( psz_dev, '.' );
645     if( p_pos && !strcmp( p_pos, ".cue" ) )
646     {
647         /* psz_dev must be the cue file. Let's assume there's a .bin
648          * file with the same filename */
649         psz_vcdfile = malloc( p_pos - psz_dev + 5 /* ".bin" */ );
650         strncpy( psz_vcdfile, psz_dev, p_pos - psz_dev );
651         strcpy( psz_vcdfile + (p_pos - psz_dev), ".bin");
652         psz_cuefile = strdup( psz_dev );
653     }
654     else
655     {
656         /* psz_dev must be the actual vcd file. Let's assume there's a .cue
657          * file with the same filename */
658         if( p_pos )
659         {
660             psz_cuefile = malloc( p_pos - psz_dev + 5 /* ".cue" */ );
661             strncpy( psz_cuefile, psz_dev, p_pos - psz_dev );
662             strcpy( psz_cuefile + (p_pos - psz_dev), ".cue");
663         }
664         else
665         {
666             if( asprintf( &psz_cuefile, "%s.cue", psz_dev ) == -1 )
667                 psz_cuefile = NULL;
668         }
669         /* If we need to look up the .cue file, then we don't have to look for the vcd */
670         psz_vcdfile = strdup( psz_dev );
671     }
672
673     /* Open the cue file and try to parse it */
674     msg_Dbg( p_this,"trying .cue file: %s", psz_cuefile );
675     cuefile = utf8_fopen( psz_cuefile, "rt" );
676     if( cuefile == NULL )
677     {
678         msg_Dbg( p_this, "could not find .cue file" );
679         goto error;
680     }
681
682     msg_Dbg( p_this,"guessing vcd image file: %s", psz_vcdfile );
683     p_vcddev->i_vcdimage_handle = utf8_open( psz_vcdfile,
684                                     O_RDONLY | O_NONBLOCK | O_BINARY );
685  
686     while( fgets( line, 1024, cuefile ) && !b_found )
687     {
688         /* We have a cue file, but no valid vcd file yet */
689         char filename[1024];
690         char type[16];
691         int i_temp = sscanf( line, "FILE \"%1023[^\"]\" %15s", filename, type );
692         *p_pos = 0;
693         switch( i_temp )
694         {
695             case 2:
696                 msg_Dbg( p_this, "the cue file says the data file is %s", type );
697                 if( strcasecmp( type, "BINARY" ) )
698                     goto error; /* Error if not binary, otherwise treat as case 1 */
699             case 1:
700                 if( p_vcddev->i_vcdimage_handle == -1 )
701                 {
702                     msg_Dbg( p_this, "we could not find the data file, but we found a new path" );
703                     free( psz_vcdfile);
704                     if( *filename != '/' && ((p_pos = strrchr( psz_cuefile, '/' ))
705                         || (p_pos = strrchr( psz_cuefile, '\\' ) )) )
706                     {
707                         psz_vcdfile = malloc( strlen(filename) +
708                                       (p_pos - psz_cuefile + 1) + 1 );
709                         strncpy( psz_vcdfile, psz_cuefile, (p_pos - psz_cuefile + 1) );
710                         strcpy( psz_vcdfile + (p_pos - psz_cuefile + 1), filename );
711                     } else psz_vcdfile = strdup( filename );
712                     msg_Dbg( p_this,"using vcd image file: %s", psz_vcdfile );
713                     p_vcddev->i_vcdimage_handle = utf8_open( psz_vcdfile,
714                                         O_RDONLY | O_NONBLOCK | O_BINARY );
715                 }
716                 b_found = true;
717             default:
718                 break;
719         }
720     }
721
722     if( p_vcddev->i_vcdimage_handle == -1)
723         goto error;
724
725     /* Try to parse the i_tracks and p_sectors info so we can just forget
726      * about the cuefile */
727     size_t i_tracks = 0;
728
729     while( fgets( line, 1024, cuefile ) && i_tracks < INT_MAX-1 )
730     {
731         /* look for a TRACK line */
732         char psz_dummy[9];
733         if( !sscanf( line, "%9s", psz_dummy ) || strcmp(psz_dummy, "TRACK") )
734             continue;
735
736         /* look for an INDEX line */
737         while( fgets( line, 1024, cuefile ) )
738         {
739             int i_num, i_min, i_sec, i_frame;
740
741             if( (sscanf( line, "%*9s %2u %2u:%2u:%2u", &i_num,
742                          &i_min, &i_sec, &i_frame ) != 4) || (i_num != 1) )
743                 continue;
744
745             int *buf = realloc (p_sectors, (i_tracks + 1) * sizeof (*buf));
746             if (buf == NULL)
747                 goto error;
748             p_sectors = buf;
749             p_sectors[i_tracks] = MSF_TO_LBA(i_min, i_sec, i_frame);
750             msg_Dbg( p_this, "vcd track %i begins at sector:%i",
751                      (int)i_tracks, (int)p_sectors[i_tracks] );
752             i_tracks++;
753             break;
754         }
755     }
756
757     /* fill in the last entry */
758     int *buf = realloc (p_sectors, (i_tracks + 1) * sizeof (*buf));
759     if (buf == NULL)
760         goto error;
761     p_sectors = buf;
762     p_sectors[i_tracks] = lseek(p_vcddev->i_vcdimage_handle, 0, SEEK_END)
763                                  / VCD_SECTOR_SIZE;
764     msg_Dbg( p_this, "vcd track %i, begins at sector:%i",
765              (int)i_tracks, (int)p_sectors[i_tracks] );
766     p_vcddev->i_tracks = ++i_tracks;
767     p_vcddev->p_sectors = p_sectors;
768     i_ret = 0;
769
770 error:
771     if( cuefile ) fclose( cuefile );
772     free( p_sectors );
773     free( psz_cuefile );
774     free( psz_vcdfile );
775
776     return i_ret;
777 }
778
779 /****************************************************************************
780  * CloseVCDImage: closes a vcd image opened by OpenVCDImage
781  ****************************************************************************/
782 static void CloseVCDImage( vlc_object_t * p_this, vcddev_t *p_vcddev )
783 {
784     VLC_UNUSED( p_this );
785     if( p_vcddev->i_vcdimage_handle != -1 )
786         close( p_vcddev->i_vcdimage_handle );
787     else
788         return;
789
790     free( p_vcddev->p_sectors );
791 }
792
793 #if defined( __APPLE__ )
794 /****************************************************************************
795  * darwin_getTOC: get the TOC
796  ****************************************************************************/
797 static CDTOC *darwin_getTOC( vlc_object_t * p_this, const vcddev_t *p_vcddev )
798 {
799     mach_port_t port;
800     char *psz_devname;
801     kern_return_t ret;
802     CDTOC *pTOC = NULL;
803     io_iterator_t iterator;
804     io_registry_entry_t service;
805     CFMutableDictionaryRef properties;
806     CFDataRef data;
807
808     /* get the device name */
809     if( ( psz_devname = strrchr( p_vcddev->psz_dev, '/') ) != NULL )
810         ++psz_devname;
811     else
812         psz_devname = p_vcddev->psz_dev;
813
814     /* unraw the device name */
815     if( *psz_devname == 'r' )
816         ++psz_devname;
817
818     /* get port for IOKit communication */
819     if( ( ret = IOMasterPort( MACH_PORT_NULL, &port ) ) != KERN_SUCCESS )
820     {
821         msg_Err( p_this, "IOMasterPort: 0x%08x", ret );
822         return( NULL );
823     }
824
825     /* get service iterator for the device */
826     if( ( ret = IOServiceGetMatchingServices(
827                     port, IOBSDNameMatching( port, 0, psz_devname ),
828                     &iterator ) ) != KERN_SUCCESS )
829     {
830         msg_Err( p_this, "IOServiceGetMatchingServices: 0x%08x", ret );
831         return( NULL );
832     }
833
834     /* first service */
835     service = IOIteratorNext( iterator );
836     IOObjectRelease( iterator );
837
838     /* search for kIOCDMediaClass */
839     while( service && !IOObjectConformsTo( service, kIOCDMediaClass ) )
840     {
841         if( ( ret = IORegistryEntryGetParentIterator( service,
842                         kIOServicePlane, &iterator ) ) != KERN_SUCCESS )
843         {
844             msg_Err( p_this, "IORegistryEntryGetParentIterator: 0x%08x", ret );
845             IOObjectRelease( service );
846             return( NULL );
847         }
848
849         IOObjectRelease( service );
850         service = IOIteratorNext( iterator );
851         IOObjectRelease( iterator );
852     }
853
854     if( !service )
855     {
856         msg_Err( p_this, "search for kIOCDMediaClass came up empty" );
857         return( NULL );
858     }
859
860     /* create a CF dictionary containing the TOC */
861     if( ( ret = IORegistryEntryCreateCFProperties( service, &properties,
862                     kCFAllocatorDefault, kNilOptions ) ) != KERN_SUCCESS )
863     {
864         msg_Err( p_this, "IORegistryEntryCreateCFProperties: 0x%08x", ret );
865         IOObjectRelease( service );
866         return( NULL );
867     }
868
869     /* get the TOC from the dictionary */
870     if( ( data = (CFDataRef) CFDictionaryGetValue( properties,
871                                     CFSTR(kIOCDMediaTOCKey) ) ) != NULL )
872     {
873         CFRange range;
874         CFIndex buf_len;
875
876         buf_len = CFDataGetLength( data ) + 1;
877         range = CFRangeMake( 0, buf_len );
878
879         if( ( pTOC = malloc( buf_len ) ) != NULL )
880         {
881             CFDataGetBytes( data, range, (u_char *)pTOC );
882         }
883     }
884     else
885     {
886         msg_Err( p_this, "CFDictionaryGetValue failed" );
887     }
888
889     CFRelease( properties );
890     IOObjectRelease( service );
891
892     return( pTOC );
893 }
894
895 /****************************************************************************
896  * darwin_getNumberOfTracks: get number of tracks in TOC
897  ****************************************************************************/
898 static int darwin_getNumberOfTracks( CDTOC *pTOC, int i_descriptors )
899 {
900     u_char track;
901     int i, i_tracks = 0;
902     CDTOCDescriptor *pTrackDescriptors = NULL;
903
904     pTrackDescriptors = (CDTOCDescriptor *)pTOC->descriptors;
905
906     for( i = i_descriptors; i > 0; i-- )
907     {
908         track = pTrackDescriptors[i].point;
909
910         if( track > CD_MAX_TRACK_NO || track < CD_MIN_TRACK_NO )
911             continue;
912
913         i_tracks++;
914     }
915
916     return( i_tracks );
917 }
918 #endif /* __APPLE__ */
919
920 #if defined( WIN32 )
921 /*****************************************************************************
922  * win32_vcd_open: open vcd drive
923  *****************************************************************************
924  * Use IOCTLs on WinNT/2K/XP.
925  *****************************************************************************/
926 static int win32_vcd_open( vlc_object_t * p_this, const char *psz_dev,
927                            vcddev_t *p_vcddev )
928 {
929     /* Initializations */
930     p_vcddev->h_device_handle = NULL;
931
932     char psz_win32_drive[7];
933
934     msg_Dbg( p_this, "using winNT/2K/XP ioctl layer" );
935
936     sprintf( psz_win32_drive, "\\\\.\\%c:", psz_dev[0] );
937
938     p_vcddev->h_device_handle = CreateFile( psz_win32_drive, GENERIC_READ,
939                                             FILE_SHARE_READ | FILE_SHARE_WRITE,
940                                             NULL, OPEN_EXISTING,
941                                             FILE_FLAG_NO_BUFFERING |
942                                             FILE_FLAG_RANDOM_ACCESS, NULL );
943     return (p_vcddev->h_device_handle == NULL) ? -1 : 0;
944 }
945
946 #endif /* WIN32 */
947
948 /* */
949 static void astrcat( char **ppsz_dst, char *psz_src )
950 {
951     char *psz_old = *ppsz_dst;
952
953     if( !psz_old )
954     {
955         *ppsz_dst = strdup( psz_src );
956     }
957     else if( psz_src )
958     {
959         if( asprintf( ppsz_dst, "%s%s", psz_old, psz_src ) < 0 )
960             *ppsz_dst = psz_old;
961         else
962             free( psz_old );
963     }
964 }
965
966 /* */
967 static int CdTextParse( vlc_meta_t ***ppp_tracks, int *pi_tracks,
968                         const uint8_t *p_buffer, int i_buffer )
969 {
970     char *pppsz_info[128][0x10];
971     int i_track_last = -1;
972     if( i_buffer < 4 )
973         return -1;
974
975     memset( pppsz_info, 0, sizeof(pppsz_info) );
976
977     for( int i = 0; i < (i_buffer-4)/18; i++ )
978     {
979         const uint8_t *p_block = &p_buffer[4 + 18*i];
980         char psz_text[12+1];
981
982         const int i_pack_type = p_block[0];
983         if( i_pack_type < 0x80 || i_pack_type > 0x8f )
984             continue;
985
986         const int i_track_number = (p_block[1] >> 0)&0x7f;
987         const int i_extension_flag = ( p_block[1] >> 7)& 0x01;
988         if( i_extension_flag )
989             continue;
990
991         //const int i_sequence_number = p_block[2];
992         //const int i_charater_position = (p_block[3] >> 0) &0x0f;
993         //const int i_block_number = (p_block[3] >> 4) &0x07;
994         /* TODO unicode support
995          * I need a sample */
996         //const int i_unicode = ( p_block[3] >> 7)&0x01;
997         //const int i_crc = (p_block[4+12] << 8) | (p_block[4+13] << 0);
998
999         /* */
1000         memcpy( psz_text, &p_block[4], 12 );
1001         psz_text[12] = '\0';
1002
1003         /* */
1004         int i_track =  i_track_number;
1005         char *psz_track = &psz_text[0];
1006         while( i_track <= 127 && psz_track < &psz_text[12] )
1007         {
1008             //fprintf( stderr, "t=%d psz_track=%p end=%p", i_track, psz_track, &psz_text[12] );
1009             if( *psz_track )
1010             {
1011                 astrcat( &pppsz_info[i_track][i_pack_type-0x80], psz_track );
1012                 i_track_last = __MAX( i_track_last, i_track );
1013             }
1014
1015             i_track++;
1016             psz_track += 1 + strlen(psz_track);
1017         }
1018     }
1019
1020     if( i_track_last < 0 )
1021         return -1;
1022
1023     vlc_meta_t **pp_tracks = calloc( i_track_last+1, sizeof(*pp_tracks) );
1024     if( !pp_tracks )
1025         goto exit;
1026
1027     for( int j = 0; j < 0x10; j++ )
1028     {
1029         for( int i = 0; i <= i_track_last; i++ )
1030         {
1031             /* */
1032             if( pppsz_info[i][j] )
1033                 EnsureUTF8( pppsz_info[i][j] );
1034
1035             /* */
1036             const char *psz_default = pppsz_info[0][j];
1037             const char *psz_value = pppsz_info[i][j];
1038
1039             if( !psz_value && !psz_default )
1040                 continue;
1041             vlc_meta_t *p_track = pp_tracks[i];
1042             if( !p_track )
1043             {
1044                 p_track = pp_tracks[i] = vlc_meta_New();
1045                 if( !p_track )
1046                     continue;
1047             }
1048             switch( j )
1049             {
1050             case 0x00: /* Album/Title */
1051                 if( i == 0 )
1052                 {
1053                     vlc_meta_SetAlbum( p_track, psz_value );
1054                 }
1055                 else
1056                 {
1057                     if( psz_value )
1058                         vlc_meta_SetTitle( p_track, psz_value );
1059                     if( psz_default )
1060                         vlc_meta_SetAlbum( p_track, psz_default );
1061                 }
1062                 break;
1063             case 0x01: /* Performer */
1064                 vlc_meta_SetArtist( p_track,
1065                                     psz_value ? psz_value : psz_default );
1066                 break;
1067             case 0x05: /* Messages */
1068                 vlc_meta_SetDescription( p_track,
1069                                          psz_value ? psz_value : psz_default );
1070                 break;
1071             case 0x07: /* Genre */
1072                 vlc_meta_SetGenre( p_track,
1073                                    psz_value ? psz_value : psz_default );
1074                 break;
1075             /* FIXME unsupported:
1076              * 0x02: songwriter
1077              * 0x03: composer
1078              * 0x04: arrenger
1079              * 0x06: disc id */
1080             }
1081         }
1082     }
1083     /* */
1084 exit:
1085     for( int j = 0; j < 0x10; j++ )
1086         for( int i = 0; i <= i_track_last; i++ )
1087             free( pppsz_info[i][j] );
1088
1089     *ppp_tracks = pp_tracks;
1090     *pi_tracks = i_track_last+1;
1091     return pp_tracks ? 0 : -1;
1092 }
1093
1094 #if defined( __APPLE__ ) || \
1095     defined( HAVE_IOC_TOC_HEADER_IN_SYS_CDIO_H ) || \
1096     defined( HAVE_SCSIREQ_IN_SYS_SCSIIO_H )
1097 static int CdTextRead( vlc_object_t *p_object, const vcddev_t *p_vcddev,
1098                        uint8_t **pp_buffer, int *pi_buffer )
1099 {
1100     VLC_UNUSED( p_object );
1101     VLC_UNUSED( p_vcddev );
1102     VLC_UNUSED( pp_buffer );
1103     VLC_UNUSED( pi_buffer );
1104     return -1;
1105 }
1106 #elif defined( WIN32 )
1107 static int CdTextRead( vlc_object_t *p_object, const vcddev_t *p_vcddev,
1108                        uint8_t **pp_buffer, int *pi_buffer )
1109 {
1110     VLC_UNUSED( p_object );
1111
1112     CDROM_READ_TOC_EX TOCEx;
1113     memset(&TOCEx, 0, sizeof(TOCEx));
1114     TOCEx.Format = CDROM_READ_TOC_EX_FORMAT_CDTEXT;
1115
1116     const int i_header_size = __MAX( 4, MINIMUM_CDROM_READ_TOC_EX_SIZE );
1117     uint8_t header[i_header_size];
1118     DWORD i_read;
1119     if( !DeviceIoControl( p_vcddev->h_device_handle, IOCTL_CDROM_READ_TOC_EX,
1120                           &TOCEx, sizeof(TOCEx), header, i_header_size, &i_read, 0 ) )
1121         return -1;
1122
1123     const int i_text = 2 + (header[0] << 8) + header[1];
1124     if( i_text <= 4 )
1125         return -1;
1126
1127     /* Read complete CD-TEXT */
1128     uint8_t *p_text = calloc( 1, i_text );
1129     if( !p_text )
1130         return VLC_EGENERIC;
1131
1132     if( !DeviceIoControl( p_vcddev->h_device_handle, IOCTL_CDROM_READ_TOC_EX,
1133                           &TOCEx, sizeof(TOCEx), p_text, i_text, &i_read, 0 ) )
1134     {
1135         free( p_text );
1136         return VLC_EGENERIC;
1137     }
1138
1139     /* */
1140     *pp_buffer = p_text;
1141     *pi_buffer = i_text;
1142     return VLC_SUCCESS;
1143 }
1144 #else
1145 static int CdTextRead( vlc_object_t *p_object, const vcddev_t *p_vcddev,
1146                        uint8_t **pp_buffer, int *pi_buffer )
1147 {
1148     VLC_UNUSED( p_object );
1149
1150     if( p_vcddev->i_device_handle == -1 )
1151         return -1;
1152
1153     struct cdrom_generic_command gc;
1154     uint8_t header[4];
1155
1156     /* Read CD-TEXT size */
1157     memset( header, 0, sizeof(header) );
1158     memset( &gc, 0, sizeof(gc) );
1159     gc.cmd[0] = 0x43;   /* Read TOC */
1160     gc.cmd[1] = 0x02;   /* MSF */
1161     gc.cmd[2] = 5;      /* CD-Text */
1162     gc.cmd[7] = ( sizeof(header) >> 8 ) & 0xff;
1163     gc.cmd[8] = ( sizeof(header) >> 0 ) & 0xff;
1164
1165     gc.buflen = sizeof(header);
1166     gc.buffer = header;
1167     gc.data_direction = CGC_DATA_READ;
1168     gc.timeout = 1000;
1169
1170     if( ioctl( p_vcddev->i_device_handle, CDROM_SEND_PACKET, &gc ) == -1 )
1171         return VLC_EGENERIC;
1172
1173     /* If the size is less than 4 it is an error, if it 4 then
1174      * it means no text data */
1175     const int i_text = 2 + (header[0] << 8) + header[1];
1176     if( i_text <= 4 )
1177         return VLC_EGENERIC;
1178
1179     /* Read complete CD-TEXT */
1180     uint8_t *p_text = calloc( 1, i_text );
1181     if( !p_text )
1182         return VLC_EGENERIC;
1183
1184     memset( &gc, 0, sizeof(gc) );
1185     gc.cmd[0] = 0x43;   /* Read TOC */
1186     gc.cmd[1] = 0x02;   /* MSF */
1187     gc.cmd[2] = 5;      /* CD-Text */
1188     gc.cmd[7] = ( i_text >> 8 ) & 0xff;
1189     gc.cmd[8] = ( i_text >> 0 ) & 0xff;
1190
1191     gc.buflen = i_text;
1192     gc.buffer = p_text;
1193     gc.data_direction = CGC_DATA_READ;
1194     gc.timeout = 1000;
1195
1196     if( ioctl( p_vcddev->i_device_handle, CDROM_SEND_PACKET, &gc ) == -1 )
1197     {
1198         free( p_text );
1199         return VLC_EGENERIC;
1200     }
1201
1202     /* */
1203     *pp_buffer = p_text;
1204     *pi_buffer = i_text;
1205     return VLC_SUCCESS;
1206 }
1207 #endif
1208
1209 int ioctl_GetCdText( vlc_object_t *p_object, const vcddev_t *p_vcddev,
1210                      vlc_meta_t ***ppp_tracks, int *pi_tracks )
1211 {
1212     uint8_t *p_text;
1213     int i_text;
1214
1215     if( p_vcddev->i_vcdimage_handle != -1 )
1216         return -1;
1217
1218     if( CdTextRead( p_object, p_vcddev, &p_text, &i_text ) )
1219         return -1;
1220
1221     CdTextParse( ppp_tracks, pi_tracks, p_text, i_text );
1222     free( p_text );
1223     return 0;
1224 }
1225