ref: e07252d769a2990f8883c30db1c04c649a550376
dir: /src/sox.c/
/* * Sox - The Swiss Army Knife of Audio Manipulation. * * This is the main function for the command line sox program. * * July 5, 1991 * Copyright 1991 Lance Norskog And Sundry Contributors * This source code is freely redistributable and may be used for * any purpose. This copyright notice must be maintained. * Lance Norskog And Sundry Contributors are not responsible for * the consequences of using this software. * * Change History: * * June 1, 1998 - Chris Bagwell (cbagwell@sprynet.com) * Added patch to get volume working again. Based on patch sent from * Matija Nalis <mnalis@public.srce.hr>. * Added command line switches to force format to ADPCM or GSM. * * September 12, 1998 - Chris Bagwell (cbagwell@sprynet.com) * Reworked code that handled effects. Wasn't correctly draining * stereo effects and a few other problems. * Made command usage (-h) show supported effects and file formats. * (this is partially from a patch by Leigh Smith * leigh@psychokiller.dialix.oz.au). * */ #include "st.h" #include <stdio.h> #include <string.h> #include <stdlib.h> /* for malloc() */ #include <errno.h> #include <sys/types.h> /* for fstat() */ #include <sys/stat.h> /* for fstat() */ #ifdef HAVE_UNISTD_H #include <unistd.h> /* for unlink() */ #endif #ifdef HAVE_GETOPT_H #include <getopt.h> #else #ifndef HAVE_GETOPT int getopt(int,char **,char *); extern char *optarg; extern int optind; #endif #endif #if defined(VMS) #define LASTCHAR ']' #elif defined(DOS) || defined(WIN32) #define LASTCHAR '\\' #else #define LASTCHAR '/' #endif /* * SOX main program. * * Rewrite for new nicer option syntax. July 13, 1991. * Rewrite for separate effects library. Sep. 15, 1991. * Incorporate Jimen Ching's fixes for real library operation: Aug 3, 1994. * Rewrite for multiple effects: Aug 24, 1994. */ static int dovolume = 0; /* User wants volume change */ static double volume = 1.0; /* Linear volume change */ static int clipped = 0; /* Volume change clipping errors */ static int writing = 0; /* are we writing to a file? */ static int soxpreview = 0; /* preview mode */ static LONG ibufl[BUFSIZ/2]; /* Left/right interleave buffers */ static LONG ibufr[BUFSIZ/2]; static LONG obufl[BUFSIZ/2]; static LONG obufr[BUFSIZ/2]; /* local forward declarations */ static void doopts(ft_t, int, char **); static void usage(char *) NORET; static int filetype(int); static void process(void); static void statistics(void); static LONG volumechange(LONG *buf, LONG ct, double vol); static void checkeffect(void); static int flow_effect_out(void); static int flow_effect(int); static int drain_effect(int); static ft_t informat = 0, outformat = 0; /* We parse effects into a temporary effects table and then place into * the real effects table. This makes it easier to reorder some effects * as needed. For instance, we can run a resampling effect before * converting a mono file to stereo. This allows the resample to work * on half the data. * * Real effects table only needs to be 2 entries bigger then the user * specified table. This is because at most we will need to add * a resample effect and an channel averaging effect. */ #define MAX_EFF 16 #define MAX_USER_EFF 14 /* * In efftab's, location 0 is always the input stream. * * If one was to support effects for quad-channel files, there would * need to be an effect tabel for each channel. */ static struct st_effect efftab[MAX_EFF]; /* left/mono channel effects */ static struct st_effect efftabR[MAX_EFF];/* right channel effects */ static int neffects; /* # of effects to run on data */ static struct st_effect user_efftab[MAX_USER_EFF]; static int nuser_effects; int main(argc, argv) int argc; char **argv; { int argc_effect; ft_t ft; myname = argv[0]; /* Loop over arguments and filenames, stop when an effect name is found */ while (optind < argc && st_checkeffect(argv[optind]) != ST_SUCCESS) { /* * Its possible to not specify the output filename by using "-e" option. * This allows effects to be ran on data but no output file to be * written. This loop basically ingores the "-e" option for input files. */ if (strcmp(argv[optind], "-e")) writing = 1; else { writing = 0; optind++; /* Move passed -e */ } ft = (ft_t)malloc(sizeof(struct st_soundstream)); st_initformat(ft); doopts(ft, argc, argv); if (optind < argc && writing) { ft->filename = argv[optind]; optind++; } else { ft->filename = 0; } /* * Determine if we will need to loop around again. If we don't * then we know we just grabbed the output file information. */ if (optind < argc && st_checkeffect(argv[optind]) != ST_SUCCESS) { informat = ft; if (! strcmp(informat->filename, "-")) informat->fp = stdin; else if ((informat->fp = fopen(informat->filename, READBINARY)) == NULL) st_fail("Can't open input file '%s': %s", informat->filename, strerror(errno)); #if defined(DUMB_FILESYSTEM) informat->seekable = 0; #else informat->seekable = (filetype(fileno(informat->fp)) == S_IFREG); #endif } else { outformat = ft; if (writing) { /* Hold off on opening file until the very last minute. * This allows us to verify header in input files are * what they should be and parse effect command lines. * That way if anything is found invalid, we will abort * without truncating any existing file that has the same * output filename. */ } } } /* Make sure we got all required filenames */ if (!informat || !informat->filename) usage("No input file?"); if (!outformat || (!outformat->filename && writing)) usage("No output file?"); /* Loop through the reset of the arguments looking for effects */ nuser_effects = 0; while (optind < argc) { if (nuser_effects >= MAX_USER_EFF) { st_fail("Sorry, too many effects specified.\n"); } argc_effect = st_geteffect_opt(&user_efftab[nuser_effects], argc - optind, &argv[optind]); if (argc_effect == ST_EOF) { int i1; fprintf(stderr, "%s: Known effects: ",myname); for (i1 = 1; st_effects[i1].name; i1++) fprintf(stderr, "%s ", st_effects[i1].name); fprintf(stderr, "\n"); st_fail("Effect '%s' is not known!", argv[optind]); } /* Skip past effect name */ optind++; (*user_efftab[nuser_effects].h->getopts)(&user_efftab[nuser_effects], argc_effect, &argv[optind]); /* Skip past the effect arguments */ optind += argc_effect; nuser_effects++; } /* Check global arguments */ /* negative volume is phase-reversal */ if (dovolume && volume < 0.0) st_report("Volume adjustment is negative. This will result in a phase change\n"); /* If file types have not been set with -t, set from file names. */ if (! informat->filetype) { if ((informat->filetype = strrchr(informat->filename, LASTCHAR)) != NULL) informat->filetype++; else informat->filetype = informat->filename; if ((informat->filetype = strrchr(informat->filetype, '.')) != NULL) informat->filetype++; else /* Default to "auto" */ informat->filetype = "auto"; } if (writing && ! outformat->filetype) { if ((outformat->filetype = strrchr(outformat->filename, LASTCHAR)) != NULL) outformat->filetype++; else outformat->filetype = outformat->filename; if ((outformat->filetype = strrchr(outformat->filetype, '.')) != NULL) outformat->filetype++; } /* Default the input comment to the filename. * The output comment will be assigned when the informat * structure is copied to the outformat. * FIXME: Should be a memory copy, not a pointer asignment. */ informat->comment = informat->filename; process(); statistics(); return(0); } #ifdef HAVE_GETOPT_H static char *getoptstr = "+r:v:t:c:phsuUAaigbwlfdDxV"; #else static char *getoptstr = "r:v:t:c:phsuUAaigbwlfdDxV"; #endif static void doopts(ft_t ft, int argc, char **argv) { int c; char *str; while ((c = getopt(argc, argv, getoptstr)) != -1) { switch(c) { case 'p': soxpreview++; break; case 'h': usage((char *)0); /* no return from above */ case 't': if (! ft) usage("-t"); ft->filetype = optarg; if (ft->filetype[0] == '.') ft->filetype++; break; case 'r': if (! ft) usage("-r"); str = optarg; #ifdef __alpha__ if ((! sscanf(str, "%u", &ft->info.rate)) || (ft->info.rate <= 0)) #else if ((! sscanf(str, "%lu", &ft->info.rate)) || (ft->info.rate <= 0)) #endif st_fail("-r must be given a positive integer"); break; case 'v': if (!ft || dovolume) usage("-v"); str = optarg; if (! sscanf(str, "%lf", &volume)) st_fail("Volume value '%s' is not a number", optarg); dovolume = 1; break; case 'c': if (! ft) usage("-c"); str = optarg; if (! sscanf(str, "%d", &ft->info.channels)) st_fail("-c must be given a number"); break; case 'b': if (! ft) usage("-b"); ft->info.size = ST_SIZE_BYTE; break; case 'w': if (! ft) usage("-w"); ft->info.size = ST_SIZE_WORD; break; case 'l': if (! ft) usage("-l"); ft->info.size = ST_SIZE_DWORD; break; case 'f': if (! ft) usage("-f"); ft->info.size = ST_SIZE_FLOAT; break; case 'd': if (! ft) usage("-d"); ft->info.size = ST_SIZE_DOUBLE; break; case 'D': if (! ft) usage("-D"); ft->info.size = ST_SIZE_IEEE; break; case 's': if (! ft) usage("-s"); ft->info.encoding = ST_ENCODING_SIGN2; break; case 'u': if (! ft) usage("-u"); ft->info.encoding = ST_ENCODING_UNSIGNED; break; case 'U': if (! ft) usage("-U"); ft->info.encoding = ST_ENCODING_ULAW; break; case 'A': if (! ft) usage("-A"); ft->info.encoding = ST_ENCODING_ALAW; break; case 'a': if (! ft) usage("-a"); ft->info.encoding = ST_ENCODING_ADPCM; break; case 'i': if (! ft) usage("-i"); ft->info.encoding = ST_ENCODING_IMA_ADPCM; break; case 'g': if (! ft) usage("-g"); ft->info.encoding = ST_ENCODING_GSM; break; case 'x': if (! ft) usage("-x"); ft->swap = 1; break; case 'V': verbose = 1; break; } } } /* * Process input file -> effect table -> output file * one buffer at a time */ static void process(void) { int e, f, flowstatus; if( st_gettype(informat) ) st_fail("bad input format"); if (writing) if ( st_gettype(outformat) ) st_fail("bad output format"); /* Read and write starters can change their formats. */ if ((* informat->h->startread)(informat) == ST_EOF) { st_fail(informat->st_errstr); } /* Go a head and assume 1 channel audio if nothing is detected. */ if (informat->info.channels == -1) informat->info.channels = 1; if ( st_checkformat(informat) ) st_fail("bad input format"); if (dovolume) st_report("Volume factor: %f\n", volume); st_report("Input file: using sample rate %lu\n\tsize %s, encoding %s, %d %s", informat->info.rate, st_sizes_str[informat->info.size], st_encodings_str[informat->info.encoding], informat->info.channels, (informat->info.channels > 1) ? "channels" : "channel"); if (informat->comment) st_report("Input file: comment \"%s\"\n", informat->comment); if (writing) { /* * There are two choices here: * 1) stomp the old file - normal shell "> file" behavior * 2) fail if the old file already exists - csh mode */ if (! strcmp(outformat->filename, "-")) { outformat->fp = stdout; /* stdout tends to be line-buffered. Override this */ /* to be Full Buffering. */ if (setvbuf (outformat->fp,NULL,_IOFBF,sizeof(char)*BUFSIZ)) { st_fail("Can't set write buffer"); } } else { outformat->fp = fopen(outformat->filename, WRITEBINARY); if (outformat->fp == NULL) st_fail("Can't open output file '%s': %s", outformat->filename, strerror(errno)); /* stdout tends to be line-buffered. Override this */ /* to be Full Buffering. */ if (setvbuf (outformat->fp,NULL,_IOFBF,sizeof(char)*BUFSIZ)) { st_fail("Can't set write buffer"); } } /* end of else != stdout */ #if defined(DUMB_FILESYSTEM) outformat->seekable = 0; #else outformat->seekable = (filetype(fileno(outformat->fp)) == S_IFREG); #endif st_copyformat(informat, outformat); if ((* outformat->h->startwrite)(outformat) == ST_EOF) { st_fail(outformat->st_errstr); } if (st_checkformat(outformat)) st_fail("bad output format"); st_report("Output file: using sample rate %lu\n\tsize %s, encoding %s, %d %s", outformat->info.rate, st_sizes_str[outformat->info.size], st_encodings_str[outformat->info.encoding], outformat->info.channels, (outformat->info.channels > 1) ? "channels" : "channel"); if (outformat->comment) st_report("Output file: comment \"%s\"\n", outformat->comment); } /* build efftab */ checkeffect(); /* Start all effects */ for(e = 1; e < neffects; e++) { (* efftab[e].h->start)(&efftab[e]); if (efftabR[e].name) (* efftabR[e].h->start)(&efftabR[e]); } /* Reserve an output buffer for all effects */ for(e = 0; e < neffects; e++) { efftab[e].obuf = (LONG *) malloc(BUFSIZ * sizeof(LONG)); if (efftab[e].obuf == NULL) { st_fail("could not allocate memory"); } if (efftabR[e].name) { efftabR[e].obuf = (LONG *) malloc(BUFSIZ * sizeof(LONG)); if (efftabR[e].obuf == NULL) { st_fail("could not allocate memory"); } } } /* * Just like errno, we must set st_errno to known values before * calling I/O operations. */ informat->st_errno = 0; outformat->st_errno = 0; /* Run input data through effects and get more until olen == 0 */ do { efftab[0].olen = (*informat->h->read)(informat, efftab[0].obuf, (LONG) BUFSIZ); efftab[0].odone = 0; if (efftab[0].olen == 0) break; /* Change the volume of this initial input data if needed. */ if (dovolume) clipped += volumechange(efftab[0].obuf, efftab[0].olen, volume); /* mark chain as empty */ for(e = 1; e < neffects; e++) efftab[e].odone = efftab[e].olen = 0; flowstatus = flow_effect_out(); /* Negative flowstatus says no more output will ever be generated. */ if (flowstatus < 0) break; } while (1); /* break; efftab[0].olen == 0 */ if (informat->st_errno) st_fail(informat->st_errstr); /* Drain the effects out first to last, * pushing residue through subsequent effects */ /* oh, what a tangled web we weave */ for(f = 1; f < neffects; f++) { while (1) { if (drain_effect(f) == 0) break; /* out of while (1) */ if (writing&&efftab[neffects-1].olen > 0) (* outformat->h->write)(outformat, efftab[neffects-1].obuf, (LONG) efftab[neffects-1].olen); if (efftab[f].olen != BUFSIZ) break; } } /* Very Important: * Effect stop is called BEFORE files close. * Effect may write out more data after. */ for (e = 1; e < neffects; e++) { (* efftab[e].h->stop)(&efftab[e]); if (efftabR[e].name) (* efftabR[e].h->stop)(&efftabR[e]); } if ((* informat->h->stopread)(informat) == ST_EOF) st_fail(informat->st_errstr); fclose(informat->fp); if (writing) { if ((* outformat->h->stopwrite)(outformat) == ST_EOF) st_fail(outformat->st_errstr); } if (writing) fclose(outformat->fp); } static int flow_effect_out(void) { int e, havedata, flowstatus = 0; do { ULONG w; /* run entire chain BACKWARDS: pull, don't push.*/ /* this is because buffering system isn't a nice queueing system */ for(e = neffects - 1; e > 0; e--) { flowstatus = flow_effect(e); if (flowstatus) break; } /* If outputing and output data was generated then write it */ if (writing&&(efftab[neffects-1].olen>efftab[neffects-1].odone)) { w = (* outformat->h->write)(outformat, efftab[neffects-1].obuf, (LONG) efftab[neffects-1].olen); efftab[neffects-1].odone = efftab[neffects-1].olen; } if (outformat->st_errno) st_fail(outformat->st_errstr); /* If any effect will never again produce data, give up. This * works because of the pull status: the effect won't be able to * shut us down until all downstream buffers have been emptied. */ if (flowstatus < 0) break; /* if stuff still in pipeline, set up to flow effects again */ havedata = 0; for(e = 0; e < neffects - 1; e++) if (efftab[e].odone < efftab[e].olen) { havedata = 1; break; } } while (havedata); return flowstatus; } static int flow_effect(e) int e; { LONG i, done, idone, odone, idonel, odonel, idoner, odoner; LONG *ibuf, *obuf; int effstatus; /* I have no input data ? */ if (efftab[e-1].odone == efftab[e-1].olen) return 0; if (! efftabR[e].name) { /* No stereo data, or effect can handle stereo data so * run effect over entire buffer. */ idone = efftab[e-1].olen - efftab[e-1].odone; odone = BUFSIZ; effstatus = (* efftab[e].h->flow)(&efftab[e], &efftab[e-1].obuf[efftab[e-1].odone], efftab[e].obuf, &idone, &odone); efftab[e-1].odone += idone; efftab[e].odone = 0; efftab[e].olen = odone; done = idone + odone; } else { /* Put stereo data in two seperate buffers and run effect * on each of them. */ idone = efftab[e-1].olen - efftab[e-1].odone; odone = BUFSIZ; ibuf = &efftab[e-1].obuf[efftab[e-1].odone]; for(i = 0; i < idone; i += 2) { ibufl[i/2] = *ibuf++; ibufr[i/2] = *ibuf++; } /* left */ idonel = (idone + 1)/2; /* odd-length logic */ odonel = odone/2; effstatus = (* efftab[e].h->flow)(&efftab[e], ibufl, obufl, &idonel, &odonel); /* right */ idoner = idone/2; /* odd-length logic */ odoner = odone/2; effstatus = (* efftabR[e].h->flow)(&efftabR[e], ibufr, obufr, &idoner, &odoner); obuf = efftab[e].obuf; /* This loop implies left and right effect will always output * the same amount of data. */ for(i = 0; i < odoner; i++) { *obuf++ = obufl[i]; *obuf++ = obufr[i]; } efftab[e-1].odone += idonel + idoner; efftab[e].odone = 0; efftab[e].olen = odonel + odoner; done = idonel + idoner + odonel + odoner; } if (done == 0) st_fail("Effect took & gave no samples!"); if (effstatus == ST_EOF) return -1; return 1; } static int drain_effect(e) int e; { LONG i, olen, olenl, olenr; LONG *obuf; if (! efftabR[e].name) { efftab[e].olen = BUFSIZ; (* efftab[e].h->drain)(&efftab[e],efftab[e].obuf, &efftab[e].olen); } else { olen = BUFSIZ; /* left */ olenl = olen/2; (* efftab[e].h->drain)(&efftab[e], obufl, &olenl); /* right */ olenr = olen/2; (* efftab[e].h->drain)(&efftabR[e], obufr, &olenr); obuf = efftab[e].obuf; /* This loop implies left and right effect will always output * the same amount of data. */ for(i = 0; i < olenr; i++) { *obuf++ = obufl[i]; *obuf++ = obufr[i]; } efftab[e].olen = olenl + olenr; } return(efftab[e].olen); } /* * If no effect given, decide what it should be. * Smart ruleset for multiple effects in sequence. * Puts user-specified effect in right place. */ static void checkeffect() { int i; int needchan = 0, needrate = 0, haschan = 0, hasrate = 0; int effects_mask = 0; needrate = (informat->info.rate != outformat->info.rate); needchan = (informat->info.channels != outformat->info.channels); for (i = 0; i < nuser_effects; i++) { if (user_efftab[i].h->flags & ST_EFF_CHAN) { haschan++; } if (user_efftab[i].h->flags & ST_EFF_RATE) { hasrate++; } } if (haschan > 1) st_fail("Can not specify multiple effects that modify channel #"); if (hasrate > 1) st_fail("Can not specify multiple effects that change sampel rate"); if (haschan && !needchan) st_fail("Can not specify channel effects when input and output channel # are equal"); if (hasrate && !needrate) st_fail("Can not specify sample rate effects when input and output rate are equal"); /* If not writing output then do not worry about adding * channel and rate effects. This is just to speed things * up. */ if (!writing) { needchan = 0; needrate = 0; } /* --------- add the effects ------------------------ */ /* efftab[0] is always the input stream and always exists */ neffects = 1; /* If reducing channels then its faster to run all effects * after the avg effect. */ if (needchan && !(haschan) && (informat->info.channels > outformat->info.channels)) { /* Find effect and update initial pointers */ st_geteffect(&efftab[neffects], "avg"); /* give default opts for added effects */ (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0, (char **)0); /* Copy format info to effect table */ effects_mask = st_updateeffect(&efftab[neffects], informat, outformat, effects_mask); neffects++; } /* If reducing the number of samples, its faster to run all effects * after the resample effect. */ if (needrate && !(hasrate) && (informat->info.rate > outformat->info.rate)) { if (soxpreview) st_geteffect(&efftab[neffects], "rate"); else st_geteffect(&efftab[neffects], "resample"); /* set up & give default opts for added effects */ (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0, (char **)0); /* Copy format info to effect table */ effects_mask = st_updateeffect(&efftab[neffects], informat, outformat, effects_mask); /* Rate can't handle multiple channels so be sure and * account for that. */ if (efftab[neffects].ininfo.channels > 1) { memcpy(&efftabR[neffects], &efftab[neffects], sizeof(struct st_effect)); } neffects++; } /* Copy over user specified effects into real efftab */ for(i = 0; i < nuser_effects; i++) { memcpy(&efftab[neffects], &user_efftab[i], sizeof(struct st_effect)); /* Copy format info to effect table */ effects_mask = st_updateeffect(&efftab[neffects], informat, outformat, effects_mask); /* If this effect can't handle multiple channels then * account for this. */ if ((efftab[neffects].ininfo.channels > 1) && !(efftab[neffects].h->flags & ST_EFF_MCHAN)) { memcpy(&efftabR[neffects], &efftab[neffects], sizeof(struct st_effect)); } neffects++; } /* If rate effect hasn't been added by now then add it here. * Check adding rate before avg because its faster to run * rate on less channels then more. */ if (needrate && !(effects_mask & ST_EFF_RATE)) { if (soxpreview) st_geteffect(&efftab[neffects], "rate"); else st_geteffect(&efftab[neffects], "resample"); /* set up & give default opts for added effects */ (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0, (char **)0); /* Copy format info to effect table */ effects_mask = st_updateeffect(&efftab[neffects], informat, outformat, effects_mask); /* Rate can't handle multiple channels so be sure and * account for that. */ if (efftab[neffects].ininfo.channels > 1) { memcpy(&efftabR[neffects], &efftab[neffects], sizeof(struct st_effect)); } neffects++; } /* If code up until know still hasn't added avg effect then * do it now. */ if (needchan && !(effects_mask & ST_EFF_CHAN)) { st_geteffect(&efftab[neffects], "avg"); /* set up & give default opts for added effects */ (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0, (char **)0); /* Copy format info to effect table */ effects_mask = st_updateeffect(&efftab[neffects], informat, outformat, effects_mask); neffects++; } } static void statistics(void) { if (dovolume && clipped > 0) st_report("Volume change clipped %d samples", clipped); } static LONG volumechange(buf, ct, vol) LONG *buf; LONG ct; double vol; { double y; LONG *p,*top; LONG clips=0; p = buf; top = buf+ct; while (p < top) { y = vol * *p; if (y < -2147483647.0) { y = -2147483647.0; clips++; } else if (y > 2147483647.0) { y = 2147483647.0; clips++; } *p++ = y + 0.5; } return clips; } static int filetype(fd) int fd; { struct stat st; fstat(fd, &st); return st.st_mode & S_IFMT; } static char *usagestr = "[ gopts ] [ fopts ] ifile [ fopts ] ofile [ effect [ effopts ] ]"; static void usage(opt) char *opt; { int i; fprintf(stderr, "%s: ", myname); if (verbose || !opt) fprintf(stderr, "%s\n\n", st_version()); fprintf(stderr, "Usage: %s\n\n", usagestr); if (opt) fprintf(stderr, "Failed at: %s\n", opt); else { fprintf(stderr,"gopts: -e -h -p -v volume -V\n\n"); fprintf(stderr,"fopts: -r rate -c channels -s/-u/-U/-A/-a/-i/-g -b/-w/-l/-f/-d/-D -x\n\n"); fprintf(stderr, "effect: "); for (i = 1; st_effects[i].name != NULL; i++) { fprintf(stderr, "%s ", st_effects[i].name); } fprintf(stderr, "\n\neffopts: depends on effect\n\n"); fprintf(stderr, "Supported file formats: "); for (i = 0; st_formats[i].names != NULL; i++) { /* only print the first name */ fprintf(stderr, "%s ", st_formats[i].names[0]); } fputc('\n', stderr); } exit(1); } /* called from util.c:fail */ void cleanup(void) { /* Close the input file and outputfile before exiting*/ if (informat && informat->fp) fclose(informat->fp); if (outformat && outformat->fp) { fclose(outformat->fp); /* remove the output file because we failed, if it's ours. */ /* Don't if its not a regular file. */ if (filetype(fileno(outformat->fp)) == S_IFREG) REMOVE(outformat->filename); } }