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