packaging: use Recommends tag on vorbis-tools to not break installation
[platform/upstream/cdrkit.git] / packaging / cdinfo.c
1 /*
2  * CD Info 1.1 - prints various information about a CD,
3  *               detects the type of the CD.
4  *
5  *  (c) 1996,1997,1998  Gerd Knorr <kraxel@goldbach.in-berlin.de>
6  *       and             Heiko Eissfeldt <heiko@colossus.escape.de>
7  *
8  */
9
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <string.h>
14 #include <fcntl.h>
15 #include <errno.h>
16 #include <sys/ioctl.h>
17 #ifdef __linux__
18 # include <linux/version.h>
19 # include <linux/cdrom.h>
20 # if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,50)
21 #  include <linux/ucdrom.h>
22 # endif
23 #endif
24
25 #ifndef CDROM_LEADOUT
26 #define CDROM_LEADOUT (0xAA)
27 #endif
28
29
30 /*
31 Subject:   -65- How can I read an IRIX (EFS) CD-ROM on a machine which
32                 doesn't use EFS?
33 Date: 18 Jun 1995 00:00:01 EST
34
35   You want 'efslook', at
36   ftp://viz.tamu.edu/pub/sgi/software/efslook.tar.gz.
37
38 and
39 ! Robert E. Seastrom <rs@access4.digex.net>'s software (with source
40 ! code) for using an SGI CD-ROM on a Macintosh is at
41 ! ftp://bifrost.seastrom.com/pub/mac/CDROM-Jumpstart.sit151.hqx.
42
43 */
44
45 #define FS_NO_DATA              0   /* audio only */
46 #define FS_HIGH_SIERRA          1
47 #define FS_ISO_9660             2
48 #define FS_INTERACTIVE          3
49 #define FS_HFS                  4
50 #define FS_UFS                  5
51 #define FS_EXT2                 6
52 #define FS_ISO_HFS              7  /* both hfs & isofs filesystem */
53 #define FS_ISO_9660_INTERACTIVE 8  /* both CD-RTOS and isofs filesystem */
54 #define FS_3DO                  9
55 #define FS_UNKNOWN             15
56 #define FS_MASK                15
57
58 #define XA                     16
59 #define MULTISESSION           32
60 #define PHOTO_CD               64
61 #define HIDDEN_TRACK          128
62 #define CDTV                  256
63 #define BOOTABLE              512
64 #define VIDEOCDI             1024
65 #define ROCKRIDGE            2048
66 #define JOLIET               4096
67
68 #if 0
69 #define STRONG "\033[1m"
70 #define NORMAL "\033[0m"
71 #else
72 #define STRONG "__________________________________\n"
73 #define NORMAL ""
74 #endif
75
76 int filehandle;                                   /* Handle of /dev/>cdrom< */
77 int rc;                                                      /* return code */
78 int i,j;                                                           /* index */
79 int isofs_size = 0;                                      /* size of session */
80 int start_track;                                   /* first sector of track */
81 int ms_offset;                /* multisession offset found by track-walking */
82 int data_start;                                       /* start of data area */
83 int joliet_level = 0;
84
85 char buffer[2048];                                           /* for CD-Data */
86 char buffer2[2048];                                          /* for CD-Data */
87 char buffer3[2048];                                          /* for CD-Data */
88 char buffer4[2048];                                          /* for CD-Data */
89 char buffer5[2048];                                          /* for CD-Data */
90
91 char                       toc_header[2];               /* first/last Track */
92 struct cdrom_tocentry      *toc[CDROM_LEADOUT+1];            /* TOC-entries */
93 struct cdrom_mcn           mcn;
94 struct cdrom_multisession  ms;
95 struct cdrom_subchnl       sub;
96 int                        first_data = -1;        /* # of first data track */
97 int                        num_data = 0;                /* # of data tracks */
98 int                        first_audio = -1;      /* # of first audio track */
99 int                        num_audio = 0;              /* # of audio tracks */
100
101 /* ------------------------------------------------------------------------ */
102 /* some iso9660 fiddling                                                    */
103
104 #define DEBUG 0
105 int read_super(int offset)
106 {
107     /* sector 16, super block */
108     memset(buffer,0,2048);
109     if (0 > lseek(filehandle,2048*(offset+16),SEEK_SET))
110        return -1;
111 #if DEBUG
112     printf("about to read sector %u\n", offset + 16);
113 #endif
114     if (0 > read(filehandle,buffer,2048))
115        return -1;
116     return 0;
117 }
118
119 int read_super2(int offset)
120 {
121     /* sector 0, for photocd check */
122     memset(buffer2,0,2048);
123     if (0 > lseek(filehandle,2048*(offset+0),SEEK_SET))
124        return -1;
125 #if DEBUG
126     printf("about to read sector %u\n", offset + 0);
127 #endif
128     if (0 > read(filehandle,buffer2,2048))
129        return -1;
130     return 0;
131 }
132
133 int read_super3(int offset)
134 {
135     /* sector 4, for ufs check */
136     memset(buffer3,0,2048);
137     if (0 > lseek(filehandle,2048*(offset+4),SEEK_SET))
138        return -1;
139 #if DEBUG
140     printf("about to read sector %u\n", offset + 4);
141 #endif
142     if (0 > read(filehandle,buffer3,2048))
143        return -1;
144     return 0;
145 }
146
147 int read_super4(int offset)
148 {
149     /* sector 17, for bootable CD check */
150     memset(buffer4,0,2048);
151     if (0 > lseek(filehandle,2048*(offset+17),SEEK_SET))
152        return -1;
153 #if DEBUG
154     printf("about to read sector %u\n", offset + 17);
155 #endif
156     if (0 > read(filehandle,buffer4,2048))
157        return -1;
158     return 0;
159 }
160
161 int read_super5(int offset)
162 {
163     /* sector 150, for Video CD check */
164     memset(buffer5,0,2048);
165     if (0 > lseek(filehandle,2048*(offset+150),SEEK_SET))
166        return -1;
167 #if DEBUG
168     printf("about to read sector %u\n", offset + 150);
169 #endif
170     if (0 > read(filehandle,buffer5,2048))
171        return -1;
172     return 0;
173 }
174
175 int is_isofs(void)
176 {
177     return 0 == memcmp(&buffer[1],"CD001",5);
178 }
179
180 int is_hs(void)
181 {
182     return 0 == memcmp(&buffer[9],"CDROM",5);
183 }
184
185 int is_cdi(void)
186 {
187     return (0 == memcmp(&buffer[1],"CD-I",4));
188 }
189
190 int is_cd_rtos(void)
191 {
192     return (0 == memcmp(&buffer[8],"CD-RTOS",7));
193 }
194
195 int is_bridge(void)
196 {
197     return (0 == memcmp(&buffer[16],"CD-BRIDGE",9));
198 }
199
200 int is_xa(void)
201 {
202     return 0 == memcmp(&buffer[1024],"CD-XA001",8);
203 }
204
205 int is_cdtv(void)
206 {
207     return (0 == memcmp(&buffer[8],"CDTV",4));
208 }
209
210 int is_photocd(void)
211 {
212     return 0 == memcmp(&buffer2[64], "PPPPHHHHOOOOTTTTOOOO____CCCCDDDD", 24);
213 }
214
215 int is_hfs(void)
216 {
217     return (0 == memcmp(&buffer2[512],"PM",2)) ||
218            (0 == memcmp(&buffer2[512],"TS",2)) ||
219            (0 == memcmp(&buffer2[1024], "BD",2));
220 }
221
222 int is_ext2(void)
223 {
224     return 0 == memcmp(&buffer2[0x438],"\x53\xef",2);
225 }
226
227 int is_3do(void)
228 {
229     return (0 == memcmp(&buffer2[0],"\x01\x5a\x5a\x5a\x5a\x5a\x01", 7)) &&
230            (0 == memcmp(&buffer2[40], "CD-ROM", 6));
231 }
232
233 int is_ufs(void)
234 {
235     return 0 == memcmp(&buffer3[1372],"\x54\x19\x01\x0" ,4);
236 }
237
238 int is_bootable(void)
239 {
240     return 0 == memcmp(&buffer4[7],"EL TORITO",9);
241 }
242
243 int is_joliet(void)
244 {
245     return 2 == buffer4[0] && buffer4[88] == 0x25 && buffer4[89] == 0x2f;
246 }
247
248 int is_video_cdi(void)
249 {
250     return 0 == memcmp(&buffer5[0],"VIDEO_CD",8);
251 }
252
253 int get_size(void)              /* iso9660 volume space in 2048 byte units */
254 {
255     return ((buffer[80] & 0xff) |
256             ((buffer[81] & 0xff) << 8) |
257             ((buffer[82] & 0xff) << 16) |
258             ((buffer[83] & 0xff) << 24));
259 }
260
261 int get_joliet_level( void )
262 {
263         switch (buffer4[90]) {
264                 case 0x40: return 1;
265                 case 0x43: return 2;
266                 case 0x45: return 3;
267         }
268         return 0;
269 }
270
271 int guess_filesystem(int start_session)
272 {
273     int ret = 0;
274
275     if (read_super(start_session) < 0)
276         return ret;
277
278 #define _DEBUG 0
279 #if _DEBUG
280     /* buffer is defined */
281     if (is_cdi())     printf("CD-I, ");
282     if (is_cd_rtos()) printf("CD-RTOS, ");
283     if (is_isofs())   printf("ISOFS, ");
284     if (is_hs())      printf("HS, ");
285     if (is_bridge())  printf("BRIDGE, ");
286     if (is_xa())      printf("XA, ");
287     if (is_cdtv())    printf("CDTV, ");
288     puts("");
289 #endif
290
291     /* filesystem */
292     if (is_cdi() && is_cd_rtos() && !is_bridge() && !is_xa()) {
293         return FS_INTERACTIVE;
294     } else {    /* read sector 0 ONLY, when NO greenbook CD-I !!!! */
295
296         if (read_super2(start_session) < 0)
297             return ret;
298
299 #if _DEBUG
300         /* buffer2 is defined */
301         if (is_photocd()) printf("PHOTO CD, ");
302         if (is_hfs()) printf("HFS, ");
303         if (is_ext2()) printf("EXT2 FS, ");
304         if (is_3do()) printf("3DO, ");
305         puts("");
306 #endif
307         if (is_hs())
308             ret |= FS_HIGH_SIERRA;
309         else if (is_isofs()) {
310             if (is_cd_rtos() && is_bridge())
311                 ret = FS_ISO_9660_INTERACTIVE;
312             else if (is_hfs())
313                 ret = FS_ISO_HFS;
314             else
315                 ret = FS_ISO_9660;
316             isofs_size = get_size();
317 #if 0
318             if (is_rockridge())
319                 ret |= ROCKRIDGE;
320 #endif
321             if (read_super4(start_session) < 0)
322                 return ret;
323
324 #if _DEBUG
325             /* buffer4 is defined */
326             if (is_joliet()) printf("JOLIET, ");
327             puts("");
328             if (is_bootable()) printf("BOOTABLE, ");
329             puts("");
330 #endif
331             if (is_joliet()) {
332                 joliet_level = get_joliet_level();
333                 ret |= JOLIET;
334             }
335             if (is_bootable())
336                 ret |= BOOTABLE;
337
338             if (is_bridge() && is_xa() && is_isofs() && is_cd_rtos() &&
339                 !is_photocd()) {
340                 if (read_super5(start_session) < 0)
341                     return ret;
342
343 #if _DEBUG
344                 /* buffer5 is defined */
345                 if (is_video_cdi()) printf("VIDEO-CDI, ");
346                 puts("");
347 #endif
348                 if (is_video_cdi())
349                     ret |= VIDEOCDI;
350             }
351         } else if (is_hfs())
352             ret |= FS_HFS;
353         else if (is_ext2())
354             ret |= FS_EXT2;
355         else if (is_3do())
356             ret |= FS_3DO;
357         else {
358
359             if (read_super3(start_session) < 0)
360                 return ret;
361
362 #if _DEBUG
363             /* buffer3 is defined */
364             if (is_ufs()) printf("UFS, ");
365             puts("");
366 #endif
367             if (is_ufs())
368                 ret |= FS_UFS;
369             else
370                 ret |= FS_UNKNOWN;
371         }
372     }
373     /* other checks */
374     if (is_xa())
375         ret |= XA;
376     if (is_photocd())
377         ret |= PHOTO_CD;
378     if (is_cdtv())
379         ret |= CDTV;
380     return ret;
381 }
382
383 /* ------------------------------------------------------------------------ */
384 /* cddb                                                                     */
385
386 int
387 cddb_sum(int n)
388 {
389     int ret=0;
390     
391     for (;;) {
392         ret += n%10;
393         n    = n/10;
394         if (!n)
395             return ret;
396     }
397 }
398
399 unsigned long
400 cddb_discid()
401 {
402     int i,t,n=0;
403
404     for (i = 1; i <= toc_header[1]; i++) {
405         n += cddb_sum(toc[i]->cdte_addr.msf.minute * 60 +
406                       toc[i]->cdte_addr.msf.second);
407     }
408     
409     t = + toc[CDROM_LEADOUT]->cdte_addr.msf.minute * 60 
410         + toc[CDROM_LEADOUT]->cdte_addr.msf.second
411         - toc[1]->cdte_addr.msf.minute * 60 
412         - toc[1]->cdte_addr.msf.second;
413
414     return ((n % 0xff) << 24 | t << 8 | toc_header[1]);
415 }
416
417 /* ------------------------------------------------------------------------ */
418
419 char *devname = "/dev/cdrom";
420 char *progname;
421
422 int
423 main(int argc, char *argv[])
424 {
425     int fs=0;
426     int need_lf;
427     progname = strrchr(argv[0],'/');
428     progname = progname ? progname+1 : argv[0];
429
430     if (argc > 1) {
431         if (0 == strncmp(argv[1],"/dev/",5))
432             devname = argv[1];
433         else {
434             devname=malloc(6+strlen(argv[1]));
435             sprintf(devname,"/dev/%s",argv[1]);
436         }
437     }
438
439     printf("CD Info 1.1 | (c) 1996-98 Gerd Knorr & Heiko Eißfeldt\n");
440
441     /* open device */
442     filehandle = open(devname,O_RDONLY);
443     if (filehandle == -1) {
444         fprintf(stderr,"%s: %s: %s\n",progname, devname, strerror(errno));
445         exit(1);
446     }
447
448 #ifdef CDROMREADTOCHDR
449     /* read the number of tracks from CD*/
450     if (ioctl(filehandle,CDROMREADTOCHDR,&toc_header)) {
451         fprintf(stderr,"%s: read TOC ioctl failed, give up\n",progname);
452         return(0);
453     }
454     
455     printf(STRONG "track list (%i - %i)\n" NORMAL,
456            toc_header[0],toc_header[1]);
457     
458     /* read toc */
459     printf(" nr: msf      lba      ctrl adr  type\n");
460     for (i = toc_header[0]; i <= CDROM_LEADOUT; i++) {
461         toc[i] = malloc(sizeof(struct cdrom_tocentry));
462         if (toc[i] == NULL) {
463             fprintf(stderr, "out of memory\n");
464             exit(1);
465         }
466         memset(toc[i],0,sizeof(struct cdrom_tocentry));
467         toc[i]->cdte_track  = i;
468         toc[i]->cdte_format = CDROM_MSF;
469         if (ioctl(filehandle,CDROMREADTOCENTRY,toc[i])) {
470             fprintf(stderr,
471                     "%s: read TOC entry ioctl failed for track %i, give up\n",
472                     progname,toc[i]->cdte_track);
473             exit(1);
474         }
475         printf("%3d: %02d:%02d:%02d (%06d) 0x%x 0x%x %s%s\n",
476                (int)toc[i]->cdte_track,
477                (int)toc[i]->cdte_addr.msf.minute,
478                (int)toc[i]->cdte_addr.msf.second,
479                (int)toc[i]->cdte_addr.msf.frame,
480                (int)toc[i]->cdte_addr.msf.frame +
481                (int)toc[i]->cdte_addr.msf.second*75 + 
482                (int)toc[i]->cdte_addr.msf.minute*75*60 - 150,
483                (int)toc[i]->cdte_ctrl,
484                (int)toc[i]->cdte_adr,
485                (toc[i]->cdte_ctrl & CDROM_DATA_TRACK) ? "data ":"audio",
486                CDROM_LEADOUT == i ? " (leadout)" : "");
487         if (i == CDROM_LEADOUT)
488             break;
489         if (toc[i]->cdte_ctrl & CDROM_DATA_TRACK) {
490             num_data++;
491             if (-1 == first_data)
492                 first_data = toc[i]->cdte_track;
493         } else {
494             num_audio++;
495             if (-1 == first_audio)
496                 first_audio = toc[i]->cdte_track;
497         }
498         /* skip to leadout */
499         if (i == (int)(toc_header[1]))
500             i = CDROM_LEADOUT-1;
501     }
502 #endif 
503     printf(STRONG "what ioctl's report\n" NORMAL);
504
505 #ifdef CDROM_GET_MCN
506     /* get mcn */
507     printf("get mcn     : "); fflush(stdout);
508     if (ioctl(filehandle,CDROM_GET_MCN,&mcn))
509         printf("FAILED\n");
510     else
511         printf("%s\n",mcn.medium_catalog_number);
512 #endif
513
514 #ifdef CDROM_DISC_STATUS
515     /* get disk status */
516     printf("disc status : "); fflush(stdout);
517     switch (ioctl(filehandle,CDROM_DISC_STATUS,0)) {
518     case CDS_NO_INFO: printf("no info\n"); break;
519     case CDS_NO_DISC: printf("no disc\n"); break;
520     case CDS_AUDIO:   printf("audio\n"); break;
521     case CDS_DATA_1:  printf("data mode 1\n"); break;
522     case CDS_DATA_2:  printf("data mode 2\n"); break;
523     case CDS_XA_2_1:  printf("XA mode 1\n"); break;
524     case CDS_XA_2_2:  printf("XA mode 2\n"); break;
525     default:          printf("unknown (failed?)\n");
526     }
527 #endif 
528     
529 #ifdef CDROMMULTISESSION
530     /* get multisession */
531     printf("multisession: "); fflush(stdout);
532     ms.addr_format = CDROM_LBA;
533     if (ioctl(filehandle,CDROMMULTISESSION,&ms))
534         printf("FAILED\n");
535     else
536         printf("%d%s\n",ms.addr.lba,ms.xa_flag?" XA":"");
537 #endif 
538
539 #ifdef CDROMSUBCHNL
540     /* get audio status from subchnl */
541     printf("audio status: "); fflush(stdout);
542     sub.cdsc_format = CDROM_MSF;
543     if (ioctl(filehandle,CDROMSUBCHNL,&sub))
544         printf("FAILED\n");
545     else {
546         switch (sub.cdsc_audiostatus) {
547         case CDROM_AUDIO_INVALID:    printf("invalid\n");   break;
548         case CDROM_AUDIO_PLAY:       printf("playing");     break;
549         case CDROM_AUDIO_PAUSED:     printf("paused");      break;
550         case CDROM_AUDIO_COMPLETED:  printf("completed\n"); break;
551         case CDROM_AUDIO_ERROR:      printf("error\n");     break;
552         case CDROM_AUDIO_NO_STATUS:  printf("no status\n"); break;
553         default:                     printf("Oops: unknown\n");
554         }
555         if (sub.cdsc_audiostatus == CDROM_AUDIO_PLAY ||
556             sub.cdsc_audiostatus == CDROM_AUDIO_PAUSED) {
557             printf(" at: %02d:%02d abs / %02d:%02d track %d\n",
558                    sub.cdsc_absaddr.msf.minute,
559                    sub.cdsc_absaddr.msf.second,
560                    sub.cdsc_reladdr.msf.minute,
561                    sub.cdsc_reladdr.msf.second,
562                    sub.cdsc_trk);
563         }
564     }
565 #endif 
566     printf(STRONG "try to find out what sort of CD this is\n" NORMAL);
567
568     /* try to find out what sort of CD we have */
569     if (0 == num_data) {
570         /* no data track, may be a "real" audio CD or hidden track CD */
571         start_track = (int)toc[1]->cdte_addr.msf.frame +
572             (int)toc[1]->cdte_addr.msf.second*75 + 
573             (int)toc[1]->cdte_addr.msf.minute*75*60 - 150;
574         /* CD-I/Ready says start_track <= 30*75 then CDDA */
575         if (start_track > 100 /* 100 is just a guess */) {
576             fs = guess_filesystem(0);
577             if ((fs & FS_MASK) != FS_UNKNOWN)
578                 fs |= HIDDEN_TRACK;
579             else {
580                 fs &= ~FS_MASK; /* del filesystem info */
581                 printf("Oops: %i unused sectors at start, but hidden track check failed.\n",start_track);
582             }
583         }
584     } else {
585         /* we have data track(s) */
586         for (j = 2, i = first_data; i <= toc_header[1]; i++) {
587             if (!(toc[i]->cdte_ctrl & CDROM_DATA_TRACK))
588                 break;
589             start_track = (i == 1) ? 0 :
590                     (int)toc[i]->cdte_addr.msf.frame +
591                     (int)toc[i]->cdte_addr.msf.second*75 + 
592                     (int)toc[i]->cdte_addr.msf.minute*75*60
593                     -150;
594             /* save the start of the data area */
595             if (i == first_data)
596                 data_start = start_track;
597             
598             /* skip tracks which belong to the current walked session */
599             if (start_track < data_start + isofs_size)
600                 continue;
601
602             fs = guess_filesystem(start_track);
603             if (!(((fs & FS_MASK) == FS_ISO_9660 ||
604                    (fs & FS_MASK) == FS_ISO_HFS ||
605         /*         (fs & FS_MASK) == FS_ISO_9660_INTERACTIVE) && (fs & XA))) */
606                    (fs & FS_MASK) == FS_ISO_9660_INTERACTIVE)))
607                 break;  /* no method for non-iso9660 multisessions */
608
609             if (i > 1) {
610                 /* track is beyond last session -> new session found */
611                 ms_offset = start_track;
612                 printf("session #%d starts at track %2i, offset %6i, isofs size %6i\n",
613                        j++,toc[i]->cdte_track,start_track,isofs_size);
614                 printf("iso9660: %i MB size, label `%.32s'\n",
615                        isofs_size/512,buffer+40);
616                 fs |= MULTISESSION;
617             }
618         }
619     }
620
621     switch(fs & FS_MASK) {
622     case FS_NO_DATA:
623         if (num_audio > 0)
624             printf("Audio CD, cddb disc ID is %08lx\n",cddb_discid());
625         break;
626     case FS_ISO_9660:
627         printf("CD-ROM with iso9660 fs");
628         if (fs & JOLIET)
629            printf(" and joliet extension level %d", joliet_level);
630         if (fs & ROCKRIDGE)
631            printf(" and rockridge extensions");
632         printf("\n");
633         break;
634     case FS_ISO_9660_INTERACTIVE:
635         printf("CD-ROM with CD-RTOS and iso9660 fs\n");
636         break;
637     case FS_HIGH_SIERRA:
638         printf("CD-ROM with high sierra fs\n");
639         break;
640     case FS_INTERACTIVE:
641         printf("CD-Interactive%s\n", num_audio > 0 ? "/Ready" : "");
642         break;
643     case FS_HFS:
644         printf("CD-ROM with Macintosh HFS\n");
645         break;
646     case FS_ISO_HFS:
647         printf("CD-ROM with both Macintosh HFS and iso9660 fs\n");
648         break;
649     case FS_UFS:
650         printf("CD-ROM with Unix UFS\n");
651         break;
652     case FS_EXT2:
653         printf("CD-ROM with Linux second extended filesystem\n");
654         break;
655     case FS_3DO:
656         printf("CD-ROM with Panasonic 3DO filesystem\n");
657         break;
658     case FS_UNKNOWN:
659         printf("CD-ROM with unknown filesystem\n");
660         break;
661     }
662     switch(fs & FS_MASK) {
663     case FS_ISO_9660:
664     case FS_ISO_9660_INTERACTIVE:
665     case FS_ISO_HFS:
666         printf("iso9660: %i MB size, label `%.32s'\n",
667                isofs_size/512,buffer+40);
668         break;
669     }
670     need_lf = 0;
671     if (first_data == 1 && num_audio > 0)
672         need_lf += printf("mixed mode CD   ");
673     if (fs & XA)
674         need_lf += printf("XA sectors   ");
675     if (fs & MULTISESSION)
676         need_lf += printf("Multisession, offset = %i   ",ms_offset);
677     if (fs & HIDDEN_TRACK)
678         need_lf += printf("Hidden Track   ");
679     if (fs & PHOTO_CD)
680         need_lf += printf("%sPhoto CD   ", num_audio > 0 ? " Portfolio " : "");
681     if (fs & CDTV)
682         need_lf += printf("Commodore CDTV   ");
683     if (first_data > 1)
684         need_lf += printf("CD-Plus/Extra   ");
685     if (fs & BOOTABLE)
686         need_lf += printf("bootable CD   ");
687     if (fs & VIDEOCDI && num_audio == 0)
688         need_lf += printf("Video CD   ");
689     if (need_lf) puts("");
690
691     exit(0);
692 }