add cdinfo.c
authorErwan Le Blond <erwan.LEBLOND@eurogiciel.fr>
Tue, 5 Mar 2013 17:28:09 +0000 (18:28 +0100)
committerErwan Le Blond <erwan.LEBLOND@eurogiciel.fr>
Tue, 5 Mar 2013 17:28:09 +0000 (18:28 +0100)
packaging/cdinfo.c [new file with mode: 0644]

diff --git a/packaging/cdinfo.c b/packaging/cdinfo.c
new file mode 100644 (file)
index 0000000..43e3a62
--- /dev/null
@@ -0,0 +1,692 @@
+/*
+ * CD Info 1.1 - prints various information about a CD,
+ *               detects the type of the CD.
+ *
+ *  (c) 1996,1997,1998  Gerd Knorr <kraxel@goldbach.in-berlin.de>
+ *       and             Heiko Eissfeldt <heiko@colossus.escape.de>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#ifdef __linux__
+# include <linux/version.h>
+# include <linux/cdrom.h>
+# if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,50)
+#  include <linux/ucdrom.h>
+# endif
+#endif
+
+#ifndef CDROM_LEADOUT
+#define CDROM_LEADOUT (0xAA)
+#endif
+
+
+/*
+Subject:   -65- How can I read an IRIX (EFS) CD-ROM on a machine which
+                doesn't use EFS?
+Date: 18 Jun 1995 00:00:01 EST
+
+  You want 'efslook', at
+  ftp://viz.tamu.edu/pub/sgi/software/efslook.tar.gz.
+
+and
+! Robert E. Seastrom <rs@access4.digex.net>'s software (with source
+! code) for using an SGI CD-ROM on a Macintosh is at
+! ftp://bifrost.seastrom.com/pub/mac/CDROM-Jumpstart.sit151.hqx.
+
+*/
+
+#define FS_NO_DATA              0   /* audio only */
+#define FS_HIGH_SIERRA         1
+#define FS_ISO_9660            2
+#define FS_INTERACTIVE         3
+#define FS_HFS                 4
+#define FS_UFS                 5
+#define FS_EXT2                        6
+#define FS_ISO_HFS              7  /* both hfs & isofs filesystem */
+#define FS_ISO_9660_INTERACTIVE 8  /* both CD-RTOS and isofs filesystem */
+#define FS_3DO                 9
+#define FS_UNKNOWN            15
+#define FS_MASK                       15
+
+#define XA                    16
+#define MULTISESSION          32
+#define PHOTO_CD              64
+#define HIDDEN_TRACK          128
+#define CDTV                 256
+#define BOOTABLE                     512
+#define VIDEOCDI                    1024
+#define ROCKRIDGE            2048
+#define JOLIET               4096
+
+#if 0
+#define STRONG "\033[1m"
+#define NORMAL "\033[0m"
+#else
+#define STRONG "__________________________________\n"
+#define NORMAL ""
+#endif
+
+int filehandle;                                   /* Handle of /dev/>cdrom< */
+int rc;                                                      /* return code */
+int i,j;                                                           /* index */
+int isofs_size = 0;                                      /* size of session */
+int start_track;                                   /* first sector of track */
+int ms_offset;                /* multisession offset found by track-walking */
+int data_start;                                       /* start of data area */
+int joliet_level = 0;
+
+char buffer[2048];                                           /* for CD-Data */
+char buffer2[2048];                                          /* for CD-Data */
+char buffer3[2048];                                          /* for CD-Data */
+char buffer4[2048];                                          /* for CD-Data */
+char buffer5[2048];                                          /* for CD-Data */
+
+char                       toc_header[2];               /* first/last Track */
+struct cdrom_tocentry      *toc[CDROM_LEADOUT+1];            /* TOC-entries */
+struct cdrom_mcn           mcn;
+struct cdrom_multisession  ms;
+struct cdrom_subchnl       sub;
+int                        first_data = -1;        /* # of first data track */
+int                        num_data = 0;                /* # of data tracks */
+int                        first_audio = -1;      /* # of first audio track */
+int                        num_audio = 0;              /* # of audio tracks */
+
+/* ------------------------------------------------------------------------ */
+/* some iso9660 fiddling                                                    */
+
+#define DEBUG 0
+int read_super(int offset)
+{
+    /* sector 16, super block */
+    memset(buffer,0,2048);
+    if (0 > lseek(filehandle,2048*(offset+16),SEEK_SET))
+       return -1;
+#if DEBUG
+    printf("about to read sector %u\n", offset + 16);
+#endif
+    if (0 > read(filehandle,buffer,2048))
+       return -1;
+    return 0;
+}
+
+int read_super2(int offset)
+{
+    /* sector 0, for photocd check */
+    memset(buffer2,0,2048);
+    if (0 > lseek(filehandle,2048*(offset+0),SEEK_SET))
+       return -1;
+#if DEBUG
+    printf("about to read sector %u\n", offset + 0);
+#endif
+    if (0 > read(filehandle,buffer2,2048))
+       return -1;
+    return 0;
+}
+
+int read_super3(int offset)
+{
+    /* sector 4, for ufs check */
+    memset(buffer3,0,2048);
+    if (0 > lseek(filehandle,2048*(offset+4),SEEK_SET))
+       return -1;
+#if DEBUG
+    printf("about to read sector %u\n", offset + 4);
+#endif
+    if (0 > read(filehandle,buffer3,2048))
+       return -1;
+    return 0;
+}
+
+int read_super4(int offset)
+{
+    /* sector 17, for bootable CD check */
+    memset(buffer4,0,2048);
+    if (0 > lseek(filehandle,2048*(offset+17),SEEK_SET))
+       return -1;
+#if DEBUG
+    printf("about to read sector %u\n", offset + 17);
+#endif
+    if (0 > read(filehandle,buffer4,2048))
+       return -1;
+    return 0;
+}
+
+int read_super5(int offset)
+{
+    /* sector 150, for Video CD check */
+    memset(buffer5,0,2048);
+    if (0 > lseek(filehandle,2048*(offset+150),SEEK_SET))
+       return -1;
+#if DEBUG
+    printf("about to read sector %u\n", offset + 150);
+#endif
+    if (0 > read(filehandle,buffer5,2048))
+       return -1;
+    return 0;
+}
+
+int is_isofs(void)
+{
+    return 0 == memcmp(&buffer[1],"CD001",5);
+}
+
+int is_hs(void)
+{
+    return 0 == memcmp(&buffer[9],"CDROM",5);
+}
+
+int is_cdi(void)
+{
+    return (0 == memcmp(&buffer[1],"CD-I",4));
+}
+
+int is_cd_rtos(void)
+{
+    return (0 == memcmp(&buffer[8],"CD-RTOS",7));
+}
+
+int is_bridge(void)
+{
+    return (0 == memcmp(&buffer[16],"CD-BRIDGE",9));
+}
+
+int is_xa(void)
+{
+    return 0 == memcmp(&buffer[1024],"CD-XA001",8);
+}
+
+int is_cdtv(void)
+{
+    return (0 == memcmp(&buffer[8],"CDTV",4));
+}
+
+int is_photocd(void)
+{
+    return 0 == memcmp(&buffer2[64], "PPPPHHHHOOOOTTTTOOOO____CCCCDDDD", 24);
+}
+
+int is_hfs(void)
+{
+    return (0 == memcmp(&buffer2[512],"PM",2)) ||
+           (0 == memcmp(&buffer2[512],"TS",2)) ||
+          (0 == memcmp(&buffer2[1024], "BD",2));
+}
+
+int is_ext2(void)
+{
+    return 0 == memcmp(&buffer2[0x438],"\x53\xef",2);
+}
+
+int is_3do(void)
+{
+    return (0 == memcmp(&buffer2[0],"\x01\x5a\x5a\x5a\x5a\x5a\x01", 7)) &&
+           (0 == memcmp(&buffer2[40], "CD-ROM", 6));
+}
+
+int is_ufs(void)
+{
+    return 0 == memcmp(&buffer3[1372],"\x54\x19\x01\x0" ,4);
+}
+
+int is_bootable(void)
+{
+    return 0 == memcmp(&buffer4[7],"EL TORITO",9);
+}
+
+int is_joliet(void)
+{
+    return 2 == buffer4[0] && buffer4[88] == 0x25 && buffer4[89] == 0x2f;
+}
+
+int is_video_cdi(void)
+{
+    return 0 == memcmp(&buffer5[0],"VIDEO_CD",8);
+}
+
+int get_size(void)             /* iso9660 volume space in 2048 byte units */
+{
+    return ((buffer[80] & 0xff) |
+           ((buffer[81] & 0xff) << 8) |
+           ((buffer[82] & 0xff) << 16) |
+           ((buffer[83] & 0xff) << 24));
+}
+
+int get_joliet_level( void )
+{
+       switch (buffer4[90]) {
+               case 0x40: return 1;
+               case 0x43: return 2;
+               case 0x45: return 3;
+       }
+       return 0;
+}
+
+int guess_filesystem(int start_session)
+{
+    int ret = 0;
+
+    if (read_super(start_session) < 0)
+       return ret;
+
+#define _DEBUG 0
+#if _DEBUG
+    /* buffer is defined */
+    if (is_cdi())     printf("CD-I, ");
+    if (is_cd_rtos()) printf("CD-RTOS, ");
+    if (is_isofs())   printf("ISOFS, ");
+    if (is_hs())      printf("HS, ");
+    if (is_bridge())  printf("BRIDGE, ");
+    if (is_xa())      printf("XA, ");
+    if (is_cdtv())    printf("CDTV, ");
+    puts("");
+#endif
+
+    /* filesystem */
+    if (is_cdi() && is_cd_rtos() && !is_bridge() && !is_xa()) {
+        return FS_INTERACTIVE;
+    } else {   /* read sector 0 ONLY, when NO greenbook CD-I !!!! */
+
+        if (read_super2(start_session) < 0)
+           return ret;
+
+#if _DEBUG
+       /* buffer2 is defined */
+       if (is_photocd()) printf("PHOTO CD, ");
+       if (is_hfs()) printf("HFS, ");
+       if (is_ext2()) printf("EXT2 FS, ");
+       if (is_3do()) printf("3DO, ");
+       puts("");
+#endif
+        if (is_hs())
+           ret |= FS_HIGH_SIERRA;
+       else if (is_isofs()) {
+           if (is_cd_rtos() && is_bridge())
+               ret = FS_ISO_9660_INTERACTIVE;
+           else if (is_hfs())
+               ret = FS_ISO_HFS;
+           else
+               ret = FS_ISO_9660;
+           isofs_size = get_size();
+#if 0
+           if (is_rockridge())
+               ret |= ROCKRIDGE;
+#endif
+            if (read_super4(start_session) < 0)
+               return ret;
+
+#if _DEBUG
+           /* buffer4 is defined */
+           if (is_joliet()) printf("JOLIET, ");
+           puts("");
+           if (is_bootable()) printf("BOOTABLE, ");
+           puts("");
+#endif
+           if (is_joliet()) {
+               joliet_level = get_joliet_level();
+               ret |= JOLIET;
+           }
+           if (is_bootable())
+               ret |= BOOTABLE;
+
+           if (is_bridge() && is_xa() && is_isofs() && is_cd_rtos() &&
+                !is_photocd()) {
+                if (read_super5(start_session) < 0)
+                   return ret;
+
+#if _DEBUG
+               /* buffer5 is defined */
+               if (is_video_cdi()) printf("VIDEO-CDI, ");
+               puts("");
+#endif
+               if (is_video_cdi())
+                   ret |= VIDEOCDI;
+           }
+       } else if (is_hfs())
+           ret |= FS_HFS;
+       else if (is_ext2())
+           ret |= FS_EXT2;
+       else if (is_3do())
+           ret |= FS_3DO;
+       else {
+
+            if (read_super3(start_session) < 0)
+               return ret;
+
+#if _DEBUG
+           /* buffer3 is defined */
+           if (is_ufs()) printf("UFS, ");
+           puts("");
+#endif
+           if (is_ufs())
+               ret |= FS_UFS;
+           else
+               ret |= FS_UNKNOWN;
+       }
+    }
+    /* other checks */
+    if (is_xa())
+        ret |= XA;
+    if (is_photocd())
+       ret |= PHOTO_CD;
+    if (is_cdtv())
+       ret |= CDTV;
+    return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+/* cddb                                                                     */
+
+int
+cddb_sum(int n)
+{
+    int ret=0;
+    
+    for (;;) {
+       ret += n%10;
+       n    = n/10;
+       if (!n)
+           return ret;
+    }
+}
+
+unsigned long
+cddb_discid()
+{
+    int i,t,n=0;
+
+    for (i = 1; i <= toc_header[1]; i++) {
+       n += cddb_sum(toc[i]->cdte_addr.msf.minute * 60 +
+                     toc[i]->cdte_addr.msf.second);
+    }
+    
+    t = + toc[CDROM_LEADOUT]->cdte_addr.msf.minute * 60 
+       + toc[CDROM_LEADOUT]->cdte_addr.msf.second
+       - toc[1]->cdte_addr.msf.minute * 60 
+       - toc[1]->cdte_addr.msf.second;
+
+    return ((n % 0xff) << 24 | t << 8 | toc_header[1]);
+}
+
+/* ------------------------------------------------------------------------ */
+
+char *devname = "/dev/cdrom";
+char *progname;
+
+int
+main(int argc, char *argv[])
+{
+    int fs=0;
+    int need_lf;
+    progname = strrchr(argv[0],'/');
+    progname = progname ? progname+1 : argv[0];
+
+    if (argc > 1) {
+       if (0 == strncmp(argv[1],"/dev/",5))
+           devname = argv[1];
+       else {
+           devname=malloc(6+strlen(argv[1]));
+           sprintf(devname,"/dev/%s",argv[1]);
+       }
+    }
+
+    printf("CD Info 1.1 | (c) 1996-98 Gerd Knorr & Heiko Eißfeldt\n");
+
+    /* open device */
+    filehandle = open(devname,O_RDONLY);
+    if (filehandle == -1) {
+       fprintf(stderr,"%s: %s: %s\n",progname, devname, strerror(errno));
+       exit(1);
+    }
+
+#ifdef CDROMREADTOCHDR
+    /* read the number of tracks from CD*/
+    if (ioctl(filehandle,CDROMREADTOCHDR,&toc_header)) {
+       fprintf(stderr,"%s: read TOC ioctl failed, give up\n",progname);
+       return(0);
+    }
+    
+    printf(STRONG "track list (%i - %i)\n" NORMAL,
+          toc_header[0],toc_header[1]);
+    
+    /* read toc */
+    printf(" nr: msf      lba      ctrl adr  type\n");
+    for (i = toc_header[0]; i <= CDROM_LEADOUT; i++) {
+       toc[i] = malloc(sizeof(struct cdrom_tocentry));
+       if (toc[i] == NULL) {
+           fprintf(stderr, "out of memory\n");
+           exit(1);
+       }
+       memset(toc[i],0,sizeof(struct cdrom_tocentry));
+       toc[i]->cdte_track  = i;
+       toc[i]->cdte_format = CDROM_MSF;
+       if (ioctl(filehandle,CDROMREADTOCENTRY,toc[i])) {
+           fprintf(stderr,
+                   "%s: read TOC entry ioctl failed for track %i, give up\n",
+                   progname,toc[i]->cdte_track);
+           exit(1);
+       }
+       printf("%3d: %02d:%02d:%02d (%06d) 0x%x 0x%x %s%s\n",
+              (int)toc[i]->cdte_track,
+              (int)toc[i]->cdte_addr.msf.minute,
+              (int)toc[i]->cdte_addr.msf.second,
+              (int)toc[i]->cdte_addr.msf.frame,
+              (int)toc[i]->cdte_addr.msf.frame +
+              (int)toc[i]->cdte_addr.msf.second*75 + 
+              (int)toc[i]->cdte_addr.msf.minute*75*60 - 150,
+              (int)toc[i]->cdte_ctrl,
+              (int)toc[i]->cdte_adr,
+              (toc[i]->cdte_ctrl & CDROM_DATA_TRACK) ? "data ":"audio",
+              CDROM_LEADOUT == i ? " (leadout)" : "");
+       if (i == CDROM_LEADOUT)
+           break;
+       if (toc[i]->cdte_ctrl & CDROM_DATA_TRACK) {
+           num_data++;
+           if (-1 == first_data)
+               first_data = toc[i]->cdte_track;
+       } else {
+           num_audio++;
+           if (-1 == first_audio)
+               first_audio = toc[i]->cdte_track;
+       }
+       /* skip to leadout */
+       if (i == (int)(toc_header[1]))
+           i = CDROM_LEADOUT-1;
+    }
+#endif 
+    printf(STRONG "what ioctl's report\n" NORMAL);
+
+#ifdef CDROM_GET_MCN
+    /* get mcn */
+    printf("get mcn     : "); fflush(stdout);
+    if (ioctl(filehandle,CDROM_GET_MCN,&mcn))
+       printf("FAILED\n");
+    else
+       printf("%s\n",mcn.medium_catalog_number);
+#endif
+
+#ifdef CDROM_DISC_STATUS
+    /* get disk status */
+    printf("disc status : "); fflush(stdout);
+    switch (ioctl(filehandle,CDROM_DISC_STATUS,0)) {
+    case CDS_NO_INFO: printf("no info\n"); break;
+    case CDS_NO_DISC: printf("no disc\n"); break;
+    case CDS_AUDIO:   printf("audio\n"); break;
+    case CDS_DATA_1:  printf("data mode 1\n"); break;
+    case CDS_DATA_2:  printf("data mode 2\n"); break;
+    case CDS_XA_2_1:  printf("XA mode 1\n"); break;
+    case CDS_XA_2_2:  printf("XA mode 2\n"); break;
+    default:          printf("unknown (failed?)\n");
+    }
+#endif 
+    
+#ifdef CDROMMULTISESSION
+    /* get multisession */
+    printf("multisession: "); fflush(stdout);
+    ms.addr_format = CDROM_LBA;
+    if (ioctl(filehandle,CDROMMULTISESSION,&ms))
+       printf("FAILED\n");
+    else
+       printf("%d%s\n",ms.addr.lba,ms.xa_flag?" XA":"");
+#endif 
+
+#ifdef CDROMSUBCHNL
+    /* get audio status from subchnl */
+    printf("audio status: "); fflush(stdout);
+    sub.cdsc_format = CDROM_MSF;
+    if (ioctl(filehandle,CDROMSUBCHNL,&sub))
+       printf("FAILED\n");
+    else {
+       switch (sub.cdsc_audiostatus) {
+       case CDROM_AUDIO_INVALID:    printf("invalid\n");   break;
+       case CDROM_AUDIO_PLAY:       printf("playing");     break;
+       case CDROM_AUDIO_PAUSED:     printf("paused");      break;
+       case CDROM_AUDIO_COMPLETED:  printf("completed\n"); break;
+       case CDROM_AUDIO_ERROR:      printf("error\n");     break;
+       case CDROM_AUDIO_NO_STATUS:  printf("no status\n"); break;
+       default:                     printf("Oops: unknown\n");
+       }
+       if (sub.cdsc_audiostatus == CDROM_AUDIO_PLAY ||
+           sub.cdsc_audiostatus == CDROM_AUDIO_PAUSED) {
+           printf(" at: %02d:%02d abs / %02d:%02d track %d\n",
+                  sub.cdsc_absaddr.msf.minute,
+                  sub.cdsc_absaddr.msf.second,
+                  sub.cdsc_reladdr.msf.minute,
+                  sub.cdsc_reladdr.msf.second,
+                  sub.cdsc_trk);
+       }
+    }
+#endif 
+    printf(STRONG "try to find out what sort of CD this is\n" NORMAL);
+
+    /* try to find out what sort of CD we have */
+    if (0 == num_data) {
+       /* no data track, may be a "real" audio CD or hidden track CD */
+       start_track = (int)toc[1]->cdte_addr.msf.frame +
+           (int)toc[1]->cdte_addr.msf.second*75 + 
+           (int)toc[1]->cdte_addr.msf.minute*75*60 - 150;
+       /* CD-I/Ready says start_track <= 30*75 then CDDA */
+       if (start_track > 100 /* 100 is just a guess */) {
+           fs = guess_filesystem(0);
+           if ((fs & FS_MASK) != FS_UNKNOWN)
+               fs |= HIDDEN_TRACK;
+           else {
+               fs &= ~FS_MASK; /* del filesystem info */
+               printf("Oops: %i unused sectors at start, but hidden track check failed.\n",start_track);
+           }
+       }
+    } else {
+        /* we have data track(s) */
+       for (j = 2, i = first_data; i <= toc_header[1]; i++) {
+           if (!(toc[i]->cdte_ctrl & CDROM_DATA_TRACK))
+               break;
+           start_track = (i == 1) ? 0 :
+                   (int)toc[i]->cdte_addr.msf.frame +
+                   (int)toc[i]->cdte_addr.msf.second*75 + 
+                   (int)toc[i]->cdte_addr.msf.minute*75*60
+                   -150;
+           /* save the start of the data area */
+           if (i == first_data)
+               data_start = start_track;
+           
+           /* skip tracks which belong to the current walked session */
+           if (start_track < data_start + isofs_size)
+               continue;
+
+           fs = guess_filesystem(start_track);
+           if (!(((fs & FS_MASK) == FS_ISO_9660 ||
+                  (fs & FS_MASK) == FS_ISO_HFS ||
+       /*         (fs & FS_MASK) == FS_ISO_9660_INTERACTIVE) && (fs & XA))) */
+                  (fs & FS_MASK) == FS_ISO_9660_INTERACTIVE)))
+               break;  /* no method for non-iso9660 multisessions */
+
+           if (i > 1) {
+               /* track is beyond last session -> new session found */
+               ms_offset = start_track;
+               printf("session #%d starts at track %2i, offset %6i, isofs size %6i\n",
+                      j++,toc[i]->cdte_track,start_track,isofs_size);
+               printf("iso9660: %i MB size, label `%.32s'\n",
+                      isofs_size/512,buffer+40);
+               fs |= MULTISESSION;
+           }
+       }
+    }
+
+    switch(fs & FS_MASK) {
+    case FS_NO_DATA:
+       if (num_audio > 0)
+           printf("Audio CD, cddb disc ID is %08lx\n",cddb_discid());
+       break;
+    case FS_ISO_9660:
+       printf("CD-ROM with iso9660 fs");
+       if (fs & JOLIET)
+          printf(" and joliet extension level %d", joliet_level);
+       if (fs & ROCKRIDGE)
+          printf(" and rockridge extensions");
+       printf("\n");
+       break;
+    case FS_ISO_9660_INTERACTIVE:
+       printf("CD-ROM with CD-RTOS and iso9660 fs\n");
+       break;
+    case FS_HIGH_SIERRA:
+       printf("CD-ROM with high sierra fs\n");
+       break;
+    case FS_INTERACTIVE:
+       printf("CD-Interactive%s\n", num_audio > 0 ? "/Ready" : "");
+       break;
+    case FS_HFS:
+       printf("CD-ROM with Macintosh HFS\n");
+       break;
+    case FS_ISO_HFS:
+       printf("CD-ROM with both Macintosh HFS and iso9660 fs\n");
+       break;
+    case FS_UFS:
+       printf("CD-ROM with Unix UFS\n");
+       break;
+    case FS_EXT2:
+       printf("CD-ROM with Linux second extended filesystem\n");
+       break;
+    case FS_3DO:
+       printf("CD-ROM with Panasonic 3DO filesystem\n");
+       break;
+    case FS_UNKNOWN:
+       printf("CD-ROM with unknown filesystem\n");
+       break;
+    }
+    switch(fs & FS_MASK) {
+    case FS_ISO_9660:
+    case FS_ISO_9660_INTERACTIVE:
+    case FS_ISO_HFS:
+       printf("iso9660: %i MB size, label `%.32s'\n",
+              isofs_size/512,buffer+40);
+       break;
+    }
+    need_lf = 0;
+    if (first_data == 1 && num_audio > 0)
+       need_lf += printf("mixed mode CD   ");
+    if (fs & XA)
+       need_lf += printf("XA sectors   ");
+    if (fs & MULTISESSION)
+       need_lf += printf("Multisession, offset = %i   ",ms_offset);
+    if (fs & HIDDEN_TRACK)
+       need_lf += printf("Hidden Track   ");
+    if (fs & PHOTO_CD)
+       need_lf += printf("%sPhoto CD   ", num_audio > 0 ? " Portfolio " : "");
+    if (fs & CDTV)
+       need_lf += printf("Commodore CDTV   ");
+    if (first_data > 1)
+       need_lf += printf("CD-Plus/Extra   ");
+    if (fs & BOOTABLE)
+       need_lf += printf("bootable CD   ");
+    if (fs & VIDEOCDI && num_audio == 0)
+       need_lf += printf("Video CD   ");
+    if (need_lf) puts("");
+
+    exit(0);
+}