cdbackup.c
author nathan
Sun, 01 Dec 2013 13:30:48 +0100
branchtrunk
changeset 17 4879fdaac574
parent 15 a9348bf5f6e7
child 18 b00641305fed
permissions -rw-r--r--
workaround cdrecords layerbreak oddity
     1 /* cdbackup.c
     2 Copyright (c) 2000-2012 Craig Condit, Stefan Huelswitt.
     3 
     4 Redistribution and use in source and binary forms, with or without
     5 modification, are permitted provided that the following conditions are met: 
     6 
     7 1. Redistributions of source code must retain the above copyright notice,
     8    this list of conditions and the following disclaimer. 
     9 2. Redistributions in binary form must reproduce the above copyright notice,
    10    this list of conditions and the following disclaimer in the documentation
    11    and/or other materials provided with the distribution. 
    12 
    13 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
    14 OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    15 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    16 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
    17 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    18 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    19 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    20 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    21 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    22 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    23 SUCH DAMAGE.
    24 */
    25 
    26 #define _LARGEFILE64_SOURCE
    27 #define _GNU_SOURCE
    28 
    29 #include <stdlib.h>
    30 #include <stdio.h>
    31 #include <stdarg.h>
    32 #include <unistd.h>
    33 #include <fcntl.h>
    34 #include <string.h>
    35 #include <time.h>
    36 #include <errno.h>
    37 #include <sys/wait.h>
    38 #include <sys/ioctl.h>
    39 #include <netinet/in.h>
    40 
    41 #ifndef sun
    42 #include <getopt.h>
    43 #endif
    44 
    45 #include "cdbackup.h"
    46 #include "cdrom.h"
    47 #include "virtual.h"
    48 #include "misc.h"
    49 #include "debug.h"
    50 #include "version.h"
    51 
    52 /* defaults */
    53 char *prg_name ="cdbackup";
    54 char *cd_dev   ="/dev/burner";
    55 char *cdr_dev  =0;                     /* no default here, too dangerous */
    56 char *cd_label ="CDBackup Track";
    57 int   cd_speed =4;
    58 long  cd_len   =-1;                    /* blocks */
    59 int   padsize  =15;                    /* blocks */
    60 int   multidisk=0;
    61 char *multicmd =0;
    62 int   verbose  =0;
    63 int   xamode2  =0;
    64 int   crc      =1;
    65 int   debug    =0;
    66 int   virtual  =0;
    67 char *virt_name=0;
    68 int   virt_dump=0;
    69 int   dvd      =0;
    70 char *exename  ="cdrecord";
    71 
    72 char **cdrec_opt=0;
    73 int    cdrec_opt_count=0;
    74 
    75 long long totalSize=0;
    76 int disknum=1;
    77 int secs;
    78 long layerbreak=0;
    79 struct tm curtime;  /* global, so multi-disks get all the same time */
    80 int auto_size=0;
    81 
    82 /****************************************************************************/
    83 
    84 char *make_arg(const char *format, ...)
    85 {
    86   char *ptr;
    87   va_list ap;
    88   va_start(ap,format);
    89   if(vasprintf(&ptr,format,ap)<0) {
    90     serror("No memory for cdrecord args\n");
    91     ptr=0;
    92     }
    93   va_end(ap);
    94   return ptr;
    95 }
    96 
    97 void start_cdrecord(void)
    98 {
    99   char **args, **p;
   100   int l;
   101   
   102   if(!(p=args=calloc(cdrec_opt_count+32,sizeof(char *))))
   103     serror("No memory for cdrecord args\n");
   104 
   105   *p++=exename;
   106 
   107   if(virt_dump || dvd) {
   108     *p++="-dao";
   109     *p++=make_arg("tsize=%ds",secs);
   110     /* work around the oddidity that cdrecord refuses to burn the disk, if
   111        the tracksize is less than the first layer size on dual-layer disks */
   112     if(layerbreak>0 && secs<layerbreak)
   113       *p++=make_arg("driveropts=layerbreak=%ld",layerbreak);
   114     }
   115   else {
   116     *p++="-multi";
   117     *p++="-tao";
   118     *p++=make_arg("padsize=%ds",padsize);
   119     }
   120 
   121   *p++=make_arg("speed=%d",cd_speed);
   122   *p++=make_arg("dev=%s",cdr_dev);
   123 
   124   for(l=0 ; l<cdrec_opt_count ; l++) *p++=cdrec_opt[l];
   125 
   126   if(xamode2 && !dvd) *p++="-xa2"; else *p++="-data";
   127   *p++="-";
   128   *p++=0;
   129 
   130   if(debug) {
   131     fprintf(stderr,"%s: cdrecord command:",prg_name);
   132     for(p=args ; *p ; p++) fprintf(stderr," %s",*p);
   133     fprintf(stderr,"\n");
   134     }
   135 
   136   execvp(exename,args);
   137   error("Exec failed (cdrecord)");
   138 }
   139 
   140 long atip_cdrecord(void)
   141 {
   142   char *cmd;
   143   FILE *p;
   144   long size=-1;
   145   
   146   if(asprintf(&cmd,"%s 2>&1 dev=%s -atip",exename,cdr_dev)<0) {
   147     fprintf(stderr,"%s: error making atip command: %s\n",prg_name,strerror(errno));
   148     return -1;
   149     }
   150   DEBUG("%s: cdrecord atip command: %s\n",prg_name,cmd);
   151 
   152   p=popen(cmd,"r");
   153   if(!p) fprintf(stderr,"%s: atip command failed\n",prg_name);
   154   else {
   155     char buff[256];
   156     while(fgets(buff,sizeof(buff),p)) {
   157       if(dvd) {
   158          /* get layerbreak position */
   159          if(!strncmp(buff,"layer break at:",15)) layerbreak=strtol(&buff[15],NULL,10);
   160          /* DVD-R */
   161 	 if(!strncmp(buff,"rzone size:",11)) size=strtol(&buff[11],NULL,10);
   162 	 /* DVD+R */
   163 	 else if(!strncmp(buff,"phys size:...",13)) size=strtol(&buff[13],NULL,10);
   164 	 }
   165       else if(!strncmp(buff,"  ATIP start of lead out:",25)) size=strtol(&buff[25],NULL,10);
   166       }
   167     }
   168   pclose(p);
   169   free(cmd);
   170   if(size>0 && verbose) {
   171     char buff[16];
   172     fprintf(stderr,"%s: auto-detected media size %s (%ld blocks, layerbreak at %ld)\n",prg_name,FlexSize(buff,(long long)size*CD_FRAMESIZE),size,layerbreak);
   173     }
   174   return size;
   175 }
   176 
   177 /****************************************************************************/
   178 
   179 void parse_cmdline(char argc, char *argv[]) 
   180 {
   181   int i;
   182   char *val;
   183 
   184   /* get some default from the environment */
   185   val=getenv("CDR_DEVICE");
   186   if(val) {
   187     cdr_dev=strdup(val);
   188     DEBUG("cdbackup: using recording device %s from CDR_DEVICE\n",cdr_dev);
   189     }
   190   val=getenv("CDR_SPEED");
   191   if(val) {
   192     cd_speed=strtol(val,NULL,10);
   193     DEBUG("cdbackup: using speed %d from CDR_SPEED\n",cd_speed);
   194     }
   195   
   196   while ((i=getopt(argc,argv,"d:r:l:s:p:a:c:mvVXDCi:wRE:"))>0) {
   197     switch (i) {
   198        case 'V': fprintf(stderr,"cdbackup "VERSION" (compiled "__DATE__")\n"
   199 	                        "Copyright (C) 2000-2010\n"
   200 			        "This is free software; see the source for copying conditions.\n"
   201 			        "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n"
   202 			        "PARTICULAR PURPOSE.\n");
   203                  exit(0);
   204        case 'v': verbose=1; break;
   205        case 'm': multidisk=1; break;
   206        case 'X': xamode2=1; break;
   207        case 'c': multicmd=optarg; break;
   208        case 'd': cd_dev=optarg; break;
   209        case 'r': cdr_dev=optarg; break;
   210        case 'a': cd_label=optarg; break;
   211        case 'C': crc=0; break;
   212        case 'i': virt_name=optarg; virtual=1; break;
   213        case 'w': virt_dump=1; break;
   214        case 'E': exename=optarg; break;
   215        case 'R': dvd=1;
   216                  DEBUG("cdbackup: DVD mode enabled\n");
   217                  break;
   218        case 'D': verbose=1; debug=1; 
   219                  DEBUG("cdbackup: DEBUG output enabled ("VERSION")\n");
   220                  break;
   221        case 'l': cd_len=(long)(FlexLen(optarg)/CD_FRAMESIZE);
   222                  break;
   223        case 's': errno=0; cd_speed=strtol(optarg,NULL,10);
   224                  if(errno==ERANGE || cd_speed<1) serror("Option -s: speed out of range (must be >=1)\n");
   225 	         break;
   226        case 'p': errno=0; padsize=strtol(optarg,NULL,10);
   227                  if(errno==ERANGE || padsize<15) serror("Option -p: padsize out of range (must be >=15)\n");
   228 	         break;
   229        default:  fprintf(stderr,
   230                          "Usage: %s [options ...] [-- cdrecord-options ...]\n"
   231                          "Reads from standard input, block formats and writes to CD-R(W).\n\n"
   232                          "  -d DEVICE      DEVICE for CD queries (default /dev/burner)\n"
   233                          "  -l N           set media size, disable auto-detect\n"
   234                          "  -r DEVICE      DEVICE for CD recording (e.g. 0,4,0)\n"
   235                          "  -s N           record CD at speed N (default 4)\n"
   236                          "  -X             enable CDROM XA2 mode in cdrecord\n"
   237                          "  -a LABEL       use LABEL as CD session title\n"
   238                          "  -p N           use a padsize of N sectors for the session (default 15)\n"
   239                          "  -m             enable multi-disk mode\n"
   240                          "  -c COMMAND     call COMMAND on disk change in multi-disk mode\n"
   241                          "  -C             disable checksum creation for datablocks\n"
   242                          "  -i IMAGE       use virtual image IMAGE for recording\n"
   243                          "  -w             dump virtual image to media\n"
   244                          "  -R             enables DVD mode\n"
   245                          "  -E EXE         set alternative cdrecord executable\n"
   246                          "  -v             be verbose\n"
   247                          "  -D             enable DEBUG output\n"
   248                          "  -V             prints version & exits\n"
   249                          "  --             pass rest of commandline to cdrecord\n"
   250                          "\n", prg_name);
   251                  exit(0);
   252        }
   253     }
   254 
   255   if(optind<argc) { /* save position/count of cdrecord options */
   256     cdrec_opt_count=argc-optind;
   257     cdrec_opt=&argv[optind];
   258     }
   259     
   260   if(cd_len<0) {
   261     auto_size=1;
   262     if(virtual && !virt_dump) serror("Can't auto-detect media size in virtual mode. Use option -l to set media size\n");
   263     }
   264   if(virtual && dvd && !virt_dump) {
   265      fprintf(stderr,"Option -R ignored in virtual mode\n");
   266      dvd=0;
   267      }
   268   if(dvd) {
   269     if(xamode2) fprintf(stderr,"Option -X ignored in DVD mode\n");
   270     padsize=0;
   271     }
   272   if(virt_dump && !virtual) serror("To dump an image you must supply the image name with -i\n");
   273   if(!cdr_dev && (!virtual || virt_dump)) serror("You must specify a device for cdrecord with -r\n");
   274 }
   275 
   276 /****************************************************************************/
   277 
   278 void autosize(void)
   279 {
   280   if(auto_size) {
   281     cd_len=atip_cdrecord();
   282     if(cd_len<0) serror("Media size detection failed. Use option -l to set media size\n");
   283     }
   284 }
   285 
   286 /****************************************************************************/
   287 
   288 void dump(void)
   289 {
   290   int n, cont;
   291   char buffer[CD_FRAMESIZE];
   292   long long grandTotal;
   293 
   294   do {
   295     int change;
   296     do {
   297       autosize();
   298       virtual=1;
   299       Vopen(1); n=VreadToc(0);
   300       if(n<1) serror("It's not usefull to dump an empty image");
   301       secs=Vsize(); cont=VhasCont();
   302       if(cd_len<secs) serror("Image doesn't fit to this media");
   303       Vseek(-1); VprepareDump();
   304 
   305       virtual=0; change=0;
   306       Vopen(0); n=VreadToc(0); VprintSpace();
   307       if(n!=0) {
   308         fprintf(stderr,"Can't dump to non-empty disk! Try another disk\n");
   309         change=1;
   310         }
   311 
   312       if(change) {
   313         Vclose();
   314         diskchange(multicmd,cd_dev);
   315         }
   316       } while(change);
   317 
   318     if(verbose)
   319       fprintf(stderr,"%s: Dumping image (%d blocks) to %s\n",prg_name,secs,VdevName());
   320     VnewTrack();
   321 
   322     grandTotal=0;
   323     while(secs>0) {
   324       VvirtRead(buffer);
   325       Vwrite(buffer); grandTotal+=CD_FRAMESIZE;
   326       secs--;
   327       }
   328 
   329     VcloseTrack(0);
   330 
   331     totalSize+=grandTotal;
   332     if(verbose) {
   333       char str1[16], str2[16];
   334       fprintf(stderr,"%s: Dumping finished. %s written (%s on this disk)\n",
   335               prg_name,FlexSize(str1,totalSize),FlexSize(str2,grandTotal));
   336       }
   337 
   338     if(multidisk==0) {
   339       if(cont) fprintf(stderr,"Multi-disk not enabled, ignoring continuation image(s)!\n");
   340       cont=0;
   341       }
   342     else if(cont) {
   343       disknum++;
   344       diskchange(multicmd,cd_dev);
   345       }
   346     } while(cont);
   347 }
   348 
   349 /****************************************************************************/
   350 
   351 int backup(void)
   352 {
   353   long long grandTotal=0;
   354   struct header_block header;
   355   int flags, datasize, result=0;
   356 
   357   char buffer[CD_FRAMESIZE];
   358   struct data_block *db=(struct data_block *)&buffer[0];
   359 
   360   flags=F_NONE;
   361   datasize=DATASIZE;
   362   if(crc) { flags|=F_CRC; datasize-=4; }
   363 
   364   sprintf(buffer,"%04d%02d%02d%02d%02d",curtime.tm_year+1900,
   365     curtime.tm_mon+1,curtime.tm_mday,curtime.tm_hour,curtime.tm_min);
   366   
   367   strncpy(header.id_str,HDR_STRING,32); header.id_str[32]=0;
   368   strncpy(header.vol_id,cd_label,32); header.vol_id[32]=0;
   369   strncpy(header.t_stamp,buffer,12); header.t_stamp[12]=0;
   370   header.disk_set = disknum;
   371   header.flags = flags;
   372 
   373   if(verbose)
   374     fprintf(stderr,"%s: Recording to %s, multidisk %s, CRC %s, disk %d\n",
   375             prg_name,VdevName(),
   376             multidisk?"enabled":"disabled",
   377             crc?"enabled":"disabled",
   378             disknum); 
   379   secs=cd_len;
   380   VnewTrack();
   381 
   382   memset(buffer,0,CD_FRAMESIZE);
   383   memcpy(buffer,&header,sizeof(struct header_block));
   384   Vwrite(buffer); grandTotal+=CD_FRAMESIZE;
   385 
   386   do {
   387     int bytes;
   388 
   389     db->flags=flags;
   390     db->status=0;		      /* this isn't the last block (for now) */
   391     bytes=full_read(0,&buffer[DBSIZE],datasize);
   392     if(bytes!=datasize) db->status=1; /* EOF, this is the last block */
   393     db->datasize=htons(bytes);
   394 
   395     if(cd_avail<(CD_FRAMESIZE*2)) {   /* less than 2 block free */
   396       if(db->status==0) {             /* if not last block, mark disk as full */
   397         db->status=2;
   398         result=1;
   399         }
   400       }
   401     if(crc) {
   402       int l=crc32(buffer,bytes+DBSIZE);
   403       *((unsigned long *)(&buffer[CD_FRAMESIZE-4]))=l;
   404       }
   405     Vwrite(buffer); grandTotal+=CD_FRAMESIZE;
   406     } while(db->status==0);
   407 
   408   if(dvd && cd_avail>=CD_FRAMESIZE) { /* pad up the track with zeros */
   409     memset(buffer,0,CD_FRAMESIZE);
   410     if(verbose) fprintf(stderr,"%s: padding up the track\n",prg_name);
   411     while(cd_avail>=CD_FRAMESIZE) Vwrite(buffer);
   412     }
   413 
   414   VcloseTrack(multidisk==0 ? 0 : result);
   415 
   416   totalSize+=grandTotal;
   417   if(verbose) {
   418     char str1[16], str2[16];
   419     fprintf(stderr,"%s: Recording finished. %s written (%s on this disk)\n",
   420             prg_name,FlexSize(str1,totalSize),FlexSize(str2,grandTotal));
   421     }
   422   return result;
   423 }
   424 
   425 /****************************************************************************/
   426 
   427 int main(int argc, char *argv[]) 
   428 {
   429   int result, loop;
   430   time_t curtime_t;
   431 
   432   curtime_t=time(0); curtime=*localtime(&curtime_t);
   433   parse_cmdline(argc,argv);
   434 
   435   if(virt_dump) {
   436     dump();
   437     }
   438   else {
   439     do {
   440       do {
   441         autosize();
   442         Vopen(0); result=VreadToc(0); VprintSpace();
   443         loop=1;
   444 
   445         if(disknum>1 && result!=0) {
   446           Vclose();
   447           fprintf(stderr,"%s: Can't do multidisk continuation on non-empty disk! Try another disk\n", prg_name);
   448           diskchange(multicmd,cd_dev);
   449           }
   450         else if(cd_avail<(padsize+MIN_BLOCKS)*CD_FRAMESIZE) {
   451           Vclose();
   452           if(multidisk) {
   453             fprintf(stderr,"%s: Not enough free space on disk! Try another disk\n", prg_name);
   454             diskchange(multicmd,cd_dev);
   455             }
   456           else serror("Not enough free space on disk");
   457           }
   458         else loop=0;
   459         } while(loop);
   460 
   461       result=backup();
   462       if(result==1) {
   463         if(multidisk==0) serror("Disk full, multi-disk not enabled. Aborting");
   464 
   465         disknum++;
   466         if(!VisRegular()) diskchange(multicmd,cd_dev);
   467         }
   468       } while(result!=0);
   469     }
   470   return 0;
   471 }