1 /*
2 * affconvert.cpp:
3 *
4 * Convert raw -> aff
5 * aff -> raw
6 * aff -> aff (recompressing/uncompressing)
7 *
8 * Distributed under the Berkeley 4-part license
9 */
10
11
12
13 #include "affconfig.h"
14 #include "afflib.h"
15 #include "afflib_i.h" // we do enough mucking, we need the internal version
16 #include "utils.h"
17
18 #include <openssl/md5.h>
19 #include <openssl/sha.h>
20
21 #ifdef WIN32
22 #include "unix4win32.h"
23 #endif
24
25 #ifdef HAVE_SYS_TIME_H
26 #include <sys/time.h>
27 #endif
28
29 #include <sys/stat.h>
30 #include <string>
31
32 #ifdef HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif
35
36 #ifdef HAVE_GETOPT_H
37 #include <getopt.h>
38 #endif
39
40
41
42 const char *progname = "affconvert";
43
44 int image_pagesize = 16*1024*1024; // default seg size --- 16MB
45 int opt_compression_alg = AF_COMPRESSION_ALG_ZLIB;
46 int opt_compress_level = AF_COMPRESSION_DEFAULT;
47 int64_t bytes_to_convert = 0;
48 int opt_batch = 1;
49 int opt_zap = 0;
50 int opt_quiet = 0;
51 int opt_write_raw = 0; // output
52 int opt_probe_compressed = 1; // probe for compressed files
53 const char *opt_write_raw_ext = "raw";
54 const char *opt_outdir = 0;
55 const char *opt_aff_ext = "aff";
56 int64_t opt_maxsize = 0;
57 int opt_yes = 0;
58 int opt_debug = 0;
59 std::string command_line;
60
61
append(char * base,const char * str)62 char *append(char *base,const char *str)
63 {
64 base = (char *)realloc(base,strlen(base)+strlen(str)+1);
65 strcat(base,str); // can't fail
66 return base;
67 }
68
69
70
usage()71 void usage()
72 {
73 printf("%s version %s\n",progname,PACKAGE_VERSION);
74 printf("\n");
75 printf("usage: %s [options] file1 [... files] \n",progname);
76 printf("\n");
77 printf("Please, see more info in manpage.");
78 /* printf("examples:\n");
79 printf(" %s file1.iso --- convert file1.iso to file1.aff\n",progname);
80 printf(" %s file1.iso file2.iso file3.iso... --- batch convert files\n",progname);
81 printf(" %s -r -e iso image.aff --- convert image.aff to image.iso\n",progname);
82 printf(" %s -M4g -o/media/dvd.afd bigfile.aff --- split an AFF file into 4GB chunks for archiving to DVD\n",
83 progname);
84 //printf(" %s -p image.aff --- recompress image.aff to maximum compression\n",progname);
85 printf("\n");
86 printf("\nGeneral options:\n");
87 printf(" -q -- Quiet mode. Don't ask questions, don't print status.\n");
88
89 printf("\nAFF output options:\n");
90 printf(" -a ext -- use 'ext' for aff files (default is %s)\n",opt_aff_ext);
91 printf(" (use .afd for AFD files)\n");
92 printf(" -Mn[kgm] -- set maximum size of output file. Suffix with g, m or k.\n");
93 printf(" -sn -- set the image_pagesize (default %d)\n",image_pagesize);
94 printf(" -x -- don't compress AFF file.\n");
95 printf(" -O dir -- use 'dir' as the output directory\n");
96 printf(" -o file -- output to 'file' (can only convert one at a time)\n");
97 printf(" File is AFF is file ends .aff; otherwise assumes raw.\n");
98 printf(" -Xn -- Set compression to n; default is 7\n");
99 printf(" -L -- Use the LZMA compression algorithm (better but slower)\n");
100
101 printf("\nRaw output options:\n");
102 printf(" -r -- force raw output. \n");
103 printf(" -e ext -- use 'ext' for the raw files (default %s)\n",opt_write_raw_ext);
104 printf(" (implies -r)\n");
105
106 printf("\nDangerous input options:\n");
107 printf(" -z -- zap; delete the output file if it already exists.\n");
108 printf(" -Z -- Do not automatically probe for gzip/bzip2 compression.\n");
109 printf(" -y -- Always answer yes/no questions 'yes.'\n");
110 printf(" -V = Just print the version number and exit.\n");
111 */
112 printf("\n");
113 exit(0);
114 }
115
116
117 /* probe_gzip():
118 * Is this a gzip file?
119 * Right now it just looks at the file extension.
120 */
121
probe_gzip(const char * infile)122 int probe_gzip(const char *infile)
123 {
124 int len = strlen(infile);
125
126 if(len>3 && strcmp(infile+len-3,".gz")==0){
127 return 1;
128 }
129 return 0;
130 }
131
probe_bzip2(const char * infile)132 int probe_bzip2(const char *infile)
133 {
134 int len = strlen(infile);
135
136 if(len>4 && strcmp(infile+len-4,".bz2")==0){
137 return 1;
138 }
139 return 0;
140 }
141
142 /* yesno():
143 * As a yes/no question. Return 1 if yes, 0 if no.
144 */
145
yesno(const char * statement,const char * question,const char * affirmative)146 int yesno(const char *statement,const char *question,const char *affirmative)
147 {
148 if(opt_yes){
149 if(!opt_quiet) printf("%s. %s.\n",statement,affirmative);
150 return 1;
151 }
152
153 printf("%s. ",statement);
154 char buf[256];
155 do {
156 printf("%s [y/n]: ",question);
157 memset(buf,0,sizeof(buf));
158 if(fgets(buf,sizeof(buf)-1,stdin)==0) return 0;
159 if(buf[0]=='y' || buf[0]=='Y'){
160 printf("%s.\n",affirmative);
161 return 1;
162 }
163 } while(buf[0]!='n' && buf[0]!='N');
164 return 0;
165 }
166
167
168 /*
169 * Basic conversion:
170 * We have an input, which may be raw or aff,
171 * and we have an output, which may be raw or aff.
172 * We are going to want to read a segment at a time.
173 */
174
175
176 #include <algorithm>
177 #include <cstdlib>
178 #include <vector>
179 #include <string>
180
181 #ifdef HAVE_CSTRING
182 #include <cstring>
183 #endif
184
185
186 using namespace std;
187
188 /** Do the conversion.
189 * return 0 if success, code if fail.
190 */
convert(const char * infile,char * outfile)191 int convert(const char *infile,char *outfile)
192 {
193
194 if(opt_debug) fprintf(stderr,"convert(%s,%s)\n",infile,outfile);
195
196 if(infile && outfile && strcmp(infile,outfile)==0){
197 errx(1,"Can't convert a file to itself\n");
198 }
199
200 /****************************************************************
201 *** Open Input
202 ****************************************************************/
203
204 AFFILE *a_in = 0; // input file, if aff
205
206 #ifdef UNIX
207 /* Check to see if it is a gzip file... */
208 if(opt_probe_compressed
209 && probe_gzip(infile)
210 && yesno("infile looks like a gzip file","Uncompress it","Uncompressing")){
211 /* Open with a subprocess. We will need to use zlib when we move to Windows. */
212 if(af_hasmeta(infile)) return -1; // don't covert with shell metacharacters
213 char buf[256];
214 snprintf(buf,sizeof(buf),"gzcat %s",infile);
215 a_in = af_popen(buf,"r");
216 }
217
218 /* Check to see if it is a bzip2 file... */
219 if(!a_in
220 && opt_probe_compressed
221 && probe_bzip2(infile)
222 && yesno("infile looks like a bzip2 file","Uncompress it","Uncompressing")){
223 /* Open with a subprocess. We will need to use bzip2zlib when we move to Windows. */
224 if(af_hasmeta(infile)) return -1; // don't covert with shell metacharacters
225 char buf[256];
226 snprintf(buf,sizeof(buf),"bzcat %s",infile);
227 a_in = af_popen(buf,"r");
228 }
229 #endif
230
231 /* If the file isn't open, try to open it... */
232 if(!a_in){
233 a_in = af_open(infile,O_RDONLY,0);
234 if(!a_in) af_err(1,"%s",infile); // give up
235 if(af_identify(a_in)==AF_IDENTIFY_RAW){
236 af_set_pagesize(a_in,image_pagesize); // match the page size we want to use
237 }
238 else {
239 image_pagesize = a_in->image_pagesize; // that's what we are using
240 }
241 }
242
243 const char *ain_fn = af_filename(a_in);
244 struct stat si;
245 memset((char *)&si,0,sizeof(si));
246 if(ain_fn && stat(ain_fn,&si)){
247 warn("Cannot stat %s",ain_fn);
248 }
249
250
251 /****************************************************************
252 *** Open Ouptut
253 ****************************************************************/
254
255
256 if(opt_zap) unlink(outfile); // we were told to zap it
257
258 AFFILE *a_out = 0; // output file, if aff or raw...
259 if(access(outfile,F_OK)==0){
260 /* If outfile is a device, ask user... */
261 struct stat so;
262 if(stat(outfile,&so)){
263 err(1,"%s exists but can't be stat?",outfile);
264 }
265 if((so.st_mode & S_IFMT)==S_IFCHR ||
266 (so.st_mode & S_IFMT)==S_IFBLK){
267 char buf[1024];
268 snprintf(buf,sizeof(buf),"%s is a raw device.\n",outfile);
269 if(yesno(buf,"Overwrite raw device?","yes")){
270 goto doit;
271 }
272 }
273 fprintf(stderr,"%s: file exists. Delete it before converting.\n",outfile);
274 exit(-1);
275 }
276 /* Check for splitraw names */
277 if(af_ext_is(outfile,"afm")){
278 char file000[MAXPATHLEN+1];
279 strlcpy(file000,outfile,sizeof(file000));
280 char *cc = strrchr(file000,'.');
281 if(!cc) err(1,"Cannot file '.' in %s\n",file000);
282 for(int i=0;i<2;i++){
283 sprintf(cc,".%03d",i);
284 if(access(file000,F_OK)==0){
285 fprintf(stderr,"%s: file exists. Delete it before converting.\n",file000);
286 fprintf(stderr,"NOTE: -z option will not delete %s\n",file000);
287 return -1;
288 }
289 }
290 }
291
292 doit:;
293
294 if(opt_write_raw){
295 /* Easy way to make a raw output is to reopen an existing output file... */
296 FILE *f = fopen(outfile,"w+b");
297 if(!f){
298 err(1,"%s",outfile);
299 }
300 a_out = af_freopen(f);
301 }
302 else {
303 a_out = af_open(outfile,O_RDWR|O_CREAT|O_BINARY,0777);
304 if(!a_out) af_err(1,"%s",outfile);
305 if(opt_maxsize){
306 af_set_maxsize(a_out,opt_maxsize);
307 }
308
309 }
310 if(a_out == 0) af_err(1,"af_open: %s",outfile);
311
312 if(!opt_quiet) printf("convert %s --> %s\n",infile,outfile);
313
314 af_update_seg(a_out,AF_ACQUISITION_COMMAND_LINE,0,
315 (const u_char *)command_line.c_str(),
316 command_line.size());
317
318 /****************************************************************
319 *** Set up the AFF file (assuming it's an aff file)
320 *** stuff that we keep at the beginning of the file...
321 ****************************************************************/
322
323 MD5_CTX md5;
324 MD5_Init(&md5);
325
326 SHA_CTX sha;
327 SHA1_Init(&sha);
328
329 /* Setup writing */
330 if(a_in->image_pagesize){
331 image_pagesize = a_in->image_pagesize;
332 }
333 af_set_pagesize(a_out,image_pagesize);
334 af_set_sectorsize(a_out,a_in->image_sectorsize);
335
336 struct af_vnode_info vni;
337 af_vstat(a_out,&vni);
338 if(vni.supports_compression){
339 if(opt_compression_alg){
340 af_enable_compression(a_out,opt_compression_alg,opt_compress_level);
341 }
342 else{
343 af_enable_compression(a_out,0,0);
344 }
345 }
346
347 /* Get a list of all the metadata segments and the pages
348 * (if this is a raw file, then the vnode raw driver will give us those segments)
349 */
350
351 char segname[AF_MAX_NAME_LEN];
352 vector <string> metadata_segments;
353 vector <int64_t> pages;
354 af_rewind_seg(a_in); // start at the beginning
355 int64_t highest_pagenum = 0;
356 while(af_get_next_seg(a_in,segname,sizeof(segname),0,0,0)==0){
357 int64_t page_num = af_segname_page_number(segname);
358 if(page_num>=0){
359 pages.push_back(page_num);
360 if(page_num>highest_pagenum) highest_pagenum = page_num;
361 }
362 else {
363 metadata_segments.push_back(segname);
364 }
365 }
366
367 /* Copy over all of the metadata segments.
368 * But don't bother if we are creating raw output
369 */
370 if(opt_write_raw==0){
371 for(vector<string>::iterator i = metadata_segments.begin();
372 i != metadata_segments.end();
373 i++){
374 strlcpy(segname,i->c_str(),sizeof(segname));
375 size_t data_len = 0;
376 uint32_t arg;
377
378 /* First find out how big the segment is */
379 if(af_get_seg(a_in,segname,&arg,0,&data_len)){
380 warn("af_get_seg_1");
381 continue;
382 }
383 /* Now get the data */
384 unsigned char *data = (unsigned char *)malloc(data_len);
385 if(af_get_seg(a_in,segname,0,data,&data_len)){
386 warn("af_get_seg_2");
387 free(data);
388 continue;
389 }
390 /* Now put the data */
391 if(af_update_seg(a_out,segname,arg,data,data_len)){
392 err(1,"af_update_seg");
393 }
394 free(data);
395 }
396 }
397
398 /* Now sort the pages and copy them over. If there is no break,
399 * we can compute the hashes...
400 */
401 sort(pages.begin(),pages.end());
402
403 int64_t prev_pagenum = -1;
404 bool hash_valid = true;
405 uint64_t last_byte_in_image = 0;
406 uint64_t total_bytes_converted = 0;
407
408 bool copy_by_pages = af_has_pages(a_in);
409
410 unsigned char *data = (unsigned char *)malloc(image_pagesize);
411 if(copy_by_pages){
412 /* Copy over data one page at a time */
413 for(vector<int64_t>::iterator i = pages.begin(); i != pages.end(); i++){
414
415 int64_t pagenum = *i;
416
417 if(!opt_quiet) { printf("Converting page %" I64d " of %" I64d "\r",pagenum,highest_pagenum); fflush(stdout); }
418
419 size_t data_len = image_pagesize;
420 if(af_get_page(a_in,pagenum,data,&data_len)){
421 err(1,"af_get_page(file=%s,page=%" I64d ")",
422 af_filename(a_in),pagenum);
423 }
424 if(af_update_page(a_out,pagenum,data,data_len)){
425 err(1,"af_update_page(file=%s,page=%" I64d ")",
426 af_filename(a_out),pagenum);
427 }
428
429 if(pagenum != prev_pagenum + 1) hash_valid = false;
430
431 if(hash_valid && vni.supports_metadata){
432 MD5_Update(&md5,data,data_len);
433 SHA1_Update(&sha,data,data_len);
434 prev_pagenum = pagenum;
435 }
436 last_byte_in_image = (int64_t)image_pagesize * pagenum + (int64_t)data_len;
437 total_bytes_converted += data_len;
438 }
439 /* Go back and update the image size (necessary since I have been writing page-by-page) */
440 if(af_update_segq(a_out,AF_IMAGESIZE,last_byte_in_image)
441 && errno!=ENOTSUP){
442 err(1,"Could not update AF_IMAGESIZE");
443 }
444 } else {
445 /* No page support; Copy from beginning to end */
446 while(!af_eof(a_in)){
447 int data_len = af_read(a_in,data,image_pagesize);
448 if(data_len>0){
449 if(!opt_quiet){
450 printf("Writing to page %" I64d " with %d bytes read from input... \r",
451 total_bytes_converted / image_pagesize,data_len);
452 fflush(stdout);
453 }
454 if(af_write(a_out,data,data_len)!=data_len){
455 err(1,"af_write");
456 }
457 if(vni.supports_metadata){
458 MD5_Update(&md5,data,data_len);
459 SHA1_Update(&sha,data,data_len);
460 }
461 }
462 if(data_len<0) err(1,"af_read");
463 if(data_len==0){
464 if(!opt_quiet) printf("af_read returned 0. Reached a sparse region or end of pipe.\n");
465 break;
466 }
467 last_byte_in_image += data_len;
468 total_bytes_converted += data_len;
469 }
470 }
471 free(data);
472 if(!opt_quiet) printf("\n");
473
474 /* Write out the new hash if it is valid */
475 if(hash_valid && vni.supports_metadata){
476 u_char md5_buf[32],sha1_buf[40];
477 char buf[256];
478 MD5_Final(md5_buf,&md5);
479 if(af_update_seg(a_out,AF_MD5,0,md5_buf,16) && errno!=ENOTSUP){
480 err(1,"Could not update AF_MD5");
481 }
482 if(!opt_quiet) printf("md5: %s\n",af_hexbuf(buf,sizeof(buf),md5_buf,16,1));
483
484 SHA1_Final(sha1_buf,&sha);
485 if(af_update_seg(a_out,AF_SHA1,0,sha1_buf,20) && errno!=ENOTSUP){
486 err(1,"Could not update AF_SHA1");
487 }
488 if(!opt_quiet) printf("sha1: %s\n",af_hexbuf(buf,sizeof(buf),sha1_buf,20,1));
489 }
490
491 /* Finish the hash calculations and write to the db */
492 if(!opt_quiet){
493 printf("bytes converted: %" I64d " \n",total_bytes_converted);
494 /* If the vnode implementation tracked segments written, report it. */
495 if(a_out->pages_written || a_out->pages_compressed){
496 printf("Total pages: %" I64u " (%" I64u " compressed)\n",
497 a_out->pages_written,a_out->pages_compressed);
498 }
499 }
500
501 if(vni.supports_metadata){
502 /* make an AF_IMAGE_GID if it doesn't exist */
503 af_make_gid(a_out);
504 af_set_acquisition_date(a_out,si.st_mtime);
505 }
506
507 /* Make a copy of the a_out filename if we can get it */
508 #ifdef HAVE_UTIMES
509 char *a_out_fn=0; // output filename, to remember for utimes
510 const char *a_ = af_filename(a_out); // remember the output filename
511 if(a_){
512 a_out_fn = strdup(a_); // make a copy of it
513 }
514 #endif
515 if(af_close(a_out)) err(1,"af_close(a_out)");
516
517 if(!opt_quiet){
518 printf("Conversion finished.\n");
519 if(af_cannot_decrypt(a_in)){
520 printf("*** encrypted pages are present which could not be decrypted ***\n");
521 }
522 printf("\n\n");
523 }
524 if(af_close(a_in)) err(1,"af_close(a_in)");
525
526 /* Set the utime on the resulting file if we can stat it */
527 struct timeval times[2];
528
529 memset(times,0,sizeof(times));
530 times[0].tv_sec = si.st_atime;
531 times[1].tv_sec = si.st_mtime;
532 #ifdef HAVE_UTIMES
533 if(a_out_fn){
534 if(utimes(a_out_fn,times)) warn("utimes(%s):",outfile);
535 free(a_out_fn);
536 a_out_fn = 0;
537 }
538 #endif
539 return(0);
540 }
541
542
atoi64(const char * buf)543 int64_t atoi64(const char *buf)
544 {
545 int64_t r=0;
546 sscanf(buf,"%" I64d,&r);
547 return r;
548 }
549
atoi64m(const char * optarg)550 int64_t atoi64m(const char *optarg)
551 {
552 int multiplier;
553 switch(optarg[strlen(optarg)-1]){
554 case 'g':
555 case 'G':
556 multiplier=1024*1024*1024;break;
557 case 'm':
558 case 'M':
559 multiplier=1024*1024; break;
560 case 'k':
561 case 'K':
562 multiplier=1024; break;
563 case 'b':
564 case 'B':
565 multiplier=1;break;
566 default:
567 err(1,"Specify multiplier units of g, m, k or b in '%s'\n",optarg);
568 }
569 return atoi64(optarg) * multiplier;
570 }
571
572
main(int argc,char ** argv)573 int main(int argc,char **argv)
574 {
575 char *outfile = 0;
576 int ch;
577
578 command_line = aff::command_line(argc,argv);
579 while ((ch = getopt(argc, argv, "a:e:Lo:zqrs:xX:Zh?M:O::ydV")) != -1) {
580 switch (ch) {
581 case 'a':
582 opt_aff_ext = optarg;
583 break;
584 case 'e':
585 opt_write_raw++;
586 opt_write_raw_ext = optarg;
587 break;
588 case 'o':
589 outfile = optarg;
590 break;
591 case 'z':
592 opt_zap ++;
593 break;
594 case 'q':
595 opt_quiet++;
596 break;
597 case 'L':
598 opt_compression_alg = AF_COMPRESSION_ALG_LZMA;
599 break;
600 case 'r':
601 opt_write_raw++;
602 break;
603 case 's':
604 image_pagesize = atoi64m(optarg);
605 break;
606 case 'x':
607 opt_compression_alg=AF_COMPRESSION_ALG_NONE;
608 break;
609 case 'X':
610 opt_compress_level = atoi(optarg);
611 break;
612 case 'Z':
613 opt_probe_compressed = 0;
614 break;
615 case 'y':
616 opt_yes = 1;
617 break;
618 case 'M':
619 opt_maxsize = atoi64m(optarg);
620 break;
621 case 'O':
622 if(!optarg) err(1,"-O flag requires a directory");
623 opt_outdir = optarg;
624 break;
625 case 'd':
626 opt_debug++;
627 break;
628 case 'h':
629 case '?':
630 default:
631 usage();
632 exit(0);
633 case 'V':
634 printf("%s version %s\n",progname,PACKAGE_VERSION);
635 exit(0);
636
637 }
638 }
639 argc -= optind;
640 argv += optind;
641
642 if(argc<1){
643 usage();
644 }
645
646 if(outfile){
647 return convert(*argv,outfile);
648 }
649
650 /* Check for "-o filename" at the end of the command line... */
651 if(argc==3 && !strcmp(argv[1],"-o")){
652 return convert(argv[0],argv[2]);
653 }
654
655 /* Convert each file*/
656
657 while(*argv){
658 char outfile[MAXPATHLEN+1];
659 memset(outfile,0,sizeof(outfile));
660
661 const char *ext = opt_write_raw ? opt_write_raw_ext : opt_aff_ext;
662 char *infile = *argv;
663 argv++;
664 argc--;
665
666 /* Copy over the filename and change the extension */
667 strlcpy(outfile,infile,sizeof(outfile));
668 char *cc = strrchr(outfile,'.'); // to strip off extension
669 if(cc){
670 /* Found an extension; copy over mine. */
671 strlcpy(cc+1,ext,sizeof(outfile)-(cc-outfile));
672 }
673 else {
674 /* No extension; make one */
675 strlcat(outfile,".",sizeof(outfile));
676 strlcat(outfile,ext,sizeof(outfile));
677 }
678
679 /* The user might want us to put things
680 * in a different directory. Pull off the filename...
681 */
682 if(opt_outdir){
683 cc = strrchr(outfile,'/');
684 char filename[MAXPATHLEN];
685 if(cc){
686 strlcpy(filename,cc+1,sizeof(filename)); // just the filename
687 }
688 else{
689 strlcpy(filename,outfile,sizeof(filename)); // the outfile is the filename
690 }
691 strlcpy(outfile,opt_outdir,sizeof(outfile));
692 strlcat(outfile,"/",sizeof(outfile));
693 strlcat(outfile,filename,sizeof(outfile));
694 }
695 if(convert(infile,outfile)){
696 exit(1);
697 }
698 }
699 exit(0);
700 }
701