1 /*  core_mode.c - public functions for mode modules
2  *  Copyright (C) 2000-2009  Jason Jordan <shnutils@freeshell.org>
3  *
4  *  This program is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU General Public License
6  *  as published by the Free Software Foundation; either version 2
7  *  of the License, or (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18 
19 #include <string.h>
20 #include <unistd.h>
21 #include <ctype.h>
22 #include <stdarg.h>
23 #include <sys/types.h>
24 #include <signal.h>
25 #ifndef WIN32
26 #include <sys/wait.h>
27 #endif
28 #include <sys/stat.h>
29 #include "shntool.h"
30 
31 CVSID("$Id: core_mode.c,v 1.88 2009/03/30 05:55:33 jason Exp $")
32 
33 global_opts st_ops;
34 
35 /* private functions */
36 
37 #define parse_input_args_cmd(a,b)   parse_args(a,b,FALSE,ARGSRC_CMDLINE)
38 #define parse_output_args_cmd(a,b)  parse_args(a,b,TRUE,ARGSRC_CMDLINE)
39 #define parse_input_args_env(a,b)   parse_args(a,b,FALSE,ARGSRC_ENV)
40 #define parse_output_args_env(a,b)  parse_args(a,b,TRUE,ARGSRC_ENV)
41 #define parse_input_args_fmt(a,b)   parse_args(a,b,FALSE,ARGSRC_FORMAT)
42 #define parse_output_args_fmt(a,b)  parse_args(a,b,TRUE,ARGSRC_FORMAT)
43 
parse_args(format_module * fm,char * argstr,bool is_output,int argsrc)44 static void parse_args(format_module *fm,char *argstr,bool is_output,int argsrc)
45 {
46   char *argdup,*token,*seps = " \t",*extstr = "ext=",*argsrcstr;
47   child_args *ca;
48 
49   if (NULL == argstr)
50     return;
51 
52   argdup = strdup(argstr);
53   if (NULL == argdup)
54     st_error("argument string duplication failed");
55 
56   switch (argsrc) {
57     case ARGSRC_ENV:
58       argsrcstr = "environment";
59       break;
60     case ARGSRC_CMDLINE:
61       argsrcstr = "command line";
62       break;
63     case ARGSRC_FORMAT:
64     default:
65       argsrcstr = PACKAGE;
66       break;
67   }
68 
69   st_debug1("parsing [%s] %s argument string originating from %s: [%s]",fm->name,(is_output)?"encoder":"decoder",argsrcstr,argstr);
70 
71   ca = (is_output) ? &fm->output_args_template : &fm->input_args_template;
72 
73   /* read first token */
74   token = strtok(argdup,seps);
75 
76   /* skip certain arguments based on the source of the string we're parsing.
77      format default = "arg1 arg2 ... argN"
78      environment    = "prog arg1 arg2 ... argN"
79      command line   = "format prog arg1 arg2 ... argN"
80    */
81   switch (argsrc) {
82     case ARGSRC_CMDLINE:
83       /* discard format name */
84       if (NULL == token)
85         return;
86 
87       token = strtok(NULL,seps);
88 
89       /* fall through */
90 
91     case ARGSRC_ENV:
92       /* check for program name ("prog" or "ext=abc prog") */
93       if (NULL == token)
94         return;
95 
96       if (is_output) {
97         /* output format: check for "ext=abc" */
98         if (strstr(token,extstr) == token) {
99           fm->extension = token + strlen(extstr);
100           st_debug1("changing %s output extension to: [%s]",fm->name,fm->extension);
101           token = strtok(NULL,seps);
102         }
103 
104         if (NULL == token)
105           return;
106 
107         /* output format: this is the program name */
108         fm->encoder = token;
109       }
110       else {
111         /* input format: this is the program name */
112         fm->decoder = token;
113       }
114 
115       arg_replace(ca,0,token);
116 
117       token = strtok(NULL,seps);
118 
119       /* fall through */
120 
121     case ARGSRC_FORMAT:
122     default:
123       break;
124   }
125 
126   /* check if there are any program arguments specified */
127   if (NULL == token)
128     return;
129 
130   /* if more arguments exist, they must be encoder/decoder args, so start modifying the existing arg list */
131   arg_reset(ca);
132   arg_add(ca,(is_output)?fm->encoder:fm->decoder);
133 
134   while (token) {
135     arg_add(ca,token);
136     token = strtok(NULL,seps);
137   }
138 }
139 
rename_by_extension(child_args * outargs,char * filename,char * outfilename,char * input_extension,char * output_extension)140 static void rename_by_extension(child_args *outargs,char *filename,char *outfilename,char *input_extension,char *output_extension)
141 /* function to properly name an output file based on the input filename, prefix/postfixes, and extensions */
142 {
143   char dirname[FILENAME_SIZE],
144        newbasename[FILENAME_SIZE],
145        outdirname[FILENAME_SIZE],
146        custprefix[FILENAME_SIZE],
147        custpostfix[FILENAME_SIZE],
148        tmp[FILENAME_SIZE],
149        *base,*ext,*outdir,*p,*extension;
150   int i;
151 
152   /* get user-specified prefix/postfix in %f filename, if any */
153 
154   strcpy(custprefix,"");
155   strcpy(custpostfix,"");
156 
157   if (outargs) {
158     p = NULL;
159     for (i=0;i<outargs->num_args;i++) {
160       if (NULL == outargs->args[i])
161         continue;
162       if ((p = strstr(outargs->args[i],FILENAME_PLACEHOLDER)))
163         break;
164     }
165 
166     if (p) {
167       /* get postfix (everything after %f) */
168       strcpy(custpostfix,p+strlen(FILENAME_PLACEHOLDER));
169 
170       /* get prefix (everything before %f) */
171       strcat(custprefix,outargs->args[i]);
172       *(strstr(custprefix,FILENAME_PLACEHOLDER)) = 0;
173     }
174   }
175 
176   /* next, copy directory name, if any */
177   strcpy(dirname,filename);
178   base = basename(dirname);
179   if (base)
180     *base = 0;
181 
182   /* now get base filename as well as its extension */
183   base = basename(filename);
184   strcpy(newbasename,base);
185   ext = extname(newbasename);
186 
187   /* if input filename's extension matches the input format's default extension, then swap it with the
188      output format's extension.  otherwise, append the output format's extension to whatever's there */
189   if (ext && input_extension && !strcmp(ext,input_extension))
190     *(ext-1) = 0;
191 
192   /* set output directory */
193   outdir = dirname;
194   if (strcmp(st_ops.output_directory,""))
195     outdir = st_ops.output_directory;
196 
197   strcpy(outdirname,outdir);
198 
199 #if 0
200   /* leave this out for now... we may be getting too fancy.  let the underlying OS handle it.
201    * the repeated '/' case below is more straightforward, and harder to screw up, so that stays in for now.
202    */
203 
204   /* remove all occurrences of '/./' - it's redundant */
205   p = outdirname;
206   while (*p) {
207     if (0 == *(p+1))
208       break;
209     while ((*(p+2)) && (PATHSEPCHAR == *p) && ('.' == *(p+1)) && (PATHSEPCHAR == *(p+2))) {
210       strcpy(p,p+2);
211     }
212     p++;
213   }
214 #endif
215 
216   /* remove duplicate '/' (e.g. "/path///to/output///dir//" becomes "/path/to/output/dir/") */
217   p = outdirname;
218   while (*p) {
219     while ((*(p+1)) && (PATHSEPCHAR == *p) && (PATHSEPCHAR == *(p+1))) {
220       strcpy(p+1,p+2);
221     }
222     p++;
223   }
224 
225   st_snprintf(tmp,FILENAME_SIZE,".%c",PATHSEPCHAR);
226 
227   if (!strcmp(outdirname,".") || !strcmp(outdirname,tmp))
228     strcpy(outdirname,"");
229 
230   st_snprintf(tmp,FILENAME_SIZE,"%c",PATHSEPCHAR);
231 
232   if (strcmp(outdirname,"") && PATHSEPCHAR != outdirname[strlen(outdirname)-1])
233     strcat(outdirname,tmp);
234 
235   extension = (output_extension) ? output_extension : "";
236 
237   /* now build full output filename */
238   st_snprintf(outfilename,FILENAME_SIZE,"%s%s%s%s%s%s.%s",outdirname,custprefix,st_ops.output_prefix,newbasename,st_ops.output_postfix,custpostfix,extension);
239 }
240 
default_output_format()241 static format_module *default_output_format()
242 {
243   int i;
244 
245   for (i=0;st_formats[i];i++) {
246     if (st_formats[i]->supports_output) {
247       return st_formats[i];
248     }
249   }
250 
251   st_error("no output formats found");
252 
253   return NULL;
254 }
255 
is_numeric(unsigned char * buf)256 static int is_numeric(unsigned char *buf)
257 {
258   unsigned char *p = buf;
259   wlong bytes;
260 
261   if (0 == strlen((const char *)buf))
262     return -1;
263 
264   while (*p) {
265     if (!isdigit(*p))
266       return -1;
267     p++;
268   }
269 
270   bytes =
271 #ifdef HAVE_ATOL
272       (wlong)atol((const char *)buf);
273 #else
274       (wlong)atoi((const char *)buf);
275 #endif
276 
277   return bytes;
278 }
279 
is_m_ss(unsigned char * buf,wave_info * info)280 static wlong is_m_ss(unsigned char *buf,wave_info *info)
281 {
282   unsigned char *colon,*p;
283   int len,min,sec;
284   wlong bytes;
285 
286   len = strlen((const char *)buf);
287 
288   if (len < 4)
289     return -1;
290 
291   colon = buf + len - 3;
292 
293   if (':' != *colon)
294     return -1;
295 
296   p = buf;
297   while (*p) {
298     if (p != colon) {
299       if (!isdigit(*p))
300         return -1;
301     }
302     p++;
303   }
304 
305   *colon = 0;
306 
307   min = atoi((const char *)buf);
308   sec = atoi((const char *)(colon+1));
309 
310   if (sec >= 60)
311     st_error("invalid value for seconds: [%d]",sec);
312 
313   bytes = (wlong)(min * info->rate * 60) +
314           (wlong)(sec * info->rate);
315 
316   return bytes;
317 }
318 
is_m_ss_ff(unsigned char * buf,wave_info * info)319 static wlong is_m_ss_ff(unsigned char *buf,wave_info *info)
320 {
321   unsigned char *colon,*dot,*p;
322   int len,min,sec,frames;
323   wlong bytes;
324 
325   len = strlen((const char *)buf);
326 
327   if (len < 7)
328     return -1;
329 
330   colon = buf + len - 6;
331   dot = buf + len - 3;
332 
333   if (':' != *colon || '.' != *dot)
334     return -1;
335 
336   p = buf;
337   while (*p) {
338     if (p != colon && p != dot) {
339       if (!isdigit(*p))
340         return -1;
341     }
342     p++;
343   }
344 
345   *colon = 0;
346   *dot = 0;
347 
348   if (PROB_NOT_CD(info))
349     st_error("m:ss.ff format can only be used with CD-quality files");
350 
351   min = atoi((const char *)buf);
352   sec = atoi((const char *)(colon+1));
353   frames = atoi((const char *)(dot+1));
354 
355   if (sec >= 60)
356     st_error("invalid value for seconds: [%d]",sec);
357 
358   if (frames >= 75)
359     st_error("invalid value for frames: [%d]",frames);
360 
361   bytes = (wlong)(min * CD_RATE * 60) +
362           (wlong)(sec * CD_RATE) +
363           (wlong)(frames * CD_BLOCK_SIZE);
364 
365   return bytes;
366 }
367 
is_m_ss_nnn(unsigned char * buf,wave_info * info)368 static wlong is_m_ss_nnn(unsigned char *buf,wave_info *info)
369 {
370   unsigned char *colon,*dot,*p;
371   int len,min,sec,ms,nearest_byte,nearest_frame;
372   wlong bytes;
373 
374   len = strlen((const char *)buf);
375 
376   if (len < 8)
377     return -1;
378 
379   colon = buf + len - 7;
380   dot = buf + len - 4;
381 
382   if (':' != *colon || '.' != *dot)
383     return -1;
384 
385   p = buf;
386   while (*p) {
387     if (p != colon && p != dot) {
388       if (!isdigit(*p))
389         return -1;
390     }
391     p++;
392   }
393 
394   *colon = 0;
395   *dot = 0;
396 
397   min = atoi((const char *)buf);
398   sec = atoi((const char *)(colon+1));
399   ms = atoi((const char *)(dot+1));
400 
401   if (sec >= 60)
402     st_error("invalid value for seconds: [%d]",sec);
403 
404   nearest_byte = (int)((((double)ms * (double)info->rate) / 1000.0) + 0.5);
405 
406   bytes = (wlong)(min * info->rate * 60) +
407           (wlong)(sec * info->rate);
408 
409   if (PROB_NOT_CD(info)) {
410     bytes += nearest_byte;
411   }
412   else {
413     nearest_frame = (int)((double)ms * .075 + 0.5) * CD_BLOCK_SIZE;
414     if (0 == nearest_frame && 0 == bytes) {
415       st_warning("closest sector boundary to %d:%02d.%03d is the beginning of the file -- rounding up to first sector boundary",min,sec,ms);
416       nearest_frame = CD_BLOCK_SIZE;
417     }
418     if (nearest_frame != nearest_byte) {
419       st_warning("rounding %d:%02d.%03d (offset: %lu) to nearest sector boundary (offset: %lu)",
420               min,sec,ms,bytes+(wlong)nearest_byte,bytes+(wlong)nearest_frame);
421       bytes += (wlong)nearest_frame;
422     }
423   }
424 
425   return bytes;
426 }
427 
428 /* Compare strings while treating digits characters numerically.
429    Copyright (C) 1997, 2000 Free Software Foundation, Inc.
430    This file is part of the GNU C Library.
431    Contributed by Jean-Fran�ois Bignolles <bignolle@ecoledoc.ibp.fr>, 1997.
432 
433    The GNU C Library is free software; you can redistribute it and/or
434    modify it under the terms of the GNU Library General Public License as
435    published by the Free Software Foundation; either version 2 of the
436    License, or (at your option) any later version.
437 
438    The GNU C Library is distributed in the hope that it will be useful,
439    but WITHOUT ANY WARRANTY; without even the implied warranty of
440    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
441    Library General Public License for more details.
442 
443    You should have received a copy of the GNU Library General Public
444    License along with the GNU C Library; see the file COPYING.LIB.  If not,
445    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
446    Boston, MA 02111-1307, USA.  */
447 
448 /* states: S_N: normal, S_I: comparing integral part, S_F: comparing
449            fractional parts, S_Z: idem but with leading Zeroes only */
450 #define S_N    0x0
451 #define S_I    0x4
452 #define S_F    0x8
453 #define S_Z    0xC
454 
455 /* result_type: CMP: return diff; LEN: compare using len_diff/diff */
456 #define CMP    2
457 #define LEN    3
458 
459 
460 /* ISDIGIT differs from isdigit, as follows:
461    - Its arg may be any int or unsigned int; it need not be an unsigned char.
462    - It's guaranteed to evaluate its argument exactly once.
463    - It's typically faster.
464    Posix 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
465    only '0' through '9' are digits.  Prefer ISDIGIT to isdigit unless
466    it's important to use the locale's definition of `digit' even when the
467    host does not conform to Posix.  */
468 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
469 
470 #undef __strverscmp
471 #undef strverscmp
472 
473 #ifndef weak_alias
474 # define __strverscmp strverscmp
475 #endif
476 
477 /* Compare S1 and S2 as strings holding indices/version numbers,
478    returning less than, equal to or greater than zero if S1 is less than,
479    equal to or greater than S2 (for more info, see the texinfo doc).
480 */
481 
482 static int
__strverscmp(const char * s1,const char * s2)483 __strverscmp (const char *s1, const char *s2)
484 {
485   const unsigned char *p1 = (const unsigned char *) s1;
486   const unsigned char *p2 = (const unsigned char *) s2;
487   unsigned char c1, c2;
488   int state;
489   int diff;
490 
491   /* Symbol(s)    0       [1-9]   others  (padding)
492      Transition   (10) 0  (01) d  (00) x  (11) -   */
493   static const unsigned int next_state[] =
494   {
495       /* state    x    d    0    - */
496       /* S_N */  S_N, S_I, S_Z, S_N,
497       /* S_I */  S_N, S_I, S_I, S_I,
498       /* S_F */  S_N, S_F, S_F, S_F,
499       /* S_Z */  S_N, S_F, S_Z, S_Z
500   };
501 
502   static const int result_type[] =
503   {
504       /* state   x/x  x/d  x/0  x/-  d/x  d/d  d/0  d/-
505                  0/x  0/d  0/0  0/-  -/x  -/d  -/0  -/- */
506 
507       /* S_N */  CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP,
508                  CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP,
509       /* S_I */  CMP, -1,  -1,  CMP,  1,  LEN, LEN, CMP,
510                   1,  LEN, LEN, CMP, CMP, CMP, CMP, CMP,
511       /* S_F */  CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP,
512                  CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP,
513       /* S_Z */  CMP,  1,   1,  CMP, -1,  CMP, CMP, CMP,
514                  -1,  CMP, CMP, CMP
515   };
516 
517   if (p1 == p2)
518     return 0;
519 
520   c1 = *p1++;
521   c2 = *p2++;
522   /* Hint: '0' is a digit too.  */
523   state = S_N | ((c1 == '0') + (ISDIGIT (c1) != 0));
524 
525   while ((diff = c1 - c2) == 0 && c1 != '\0')
526     {
527       state = next_state[state];
528       c1 = *p1++;
529       c2 = *p2++;
530       state |= (c1 == '0') + (ISDIGIT (c1) != 0);
531     }
532 
533   state = result_type[state << 2 | ((c2 == '0') + (ISDIGIT (c2) != 0))];
534 
535   switch (state)
536     {
537     case CMP:
538       return diff;
539 
540     case LEN:
541       while (ISDIGIT (*p1++))
542 	if (!ISDIGIT (*p2++))
543 	  return 1;
544 
545       return ISDIGIT (*p2) ? -1 : diff;
546 
547     default:
548       return state;
549     }
550 }
551 #ifdef weak_alias
weak_alias(__strverscmp,strverscmp)552 static weak_alias (__strverscmp, strverscmp)
553 #endif
554 
555 static int compare_version(const wave_info **w1,const wave_info **w2)
556 {
557   return strverscmp(w1[0]->filename,w2[0]->filename);
558 }
559 
compare_ascii(const wave_info ** w1,const wave_info ** w2)560 static int compare_ascii(const wave_info **w1,const wave_info **w2)
561 {
562   return strcmp(w1[0]->filename,w2[0]->filename);
563 }
564 
ascii_sort_files(wave_info ** filenames,int numfiles)565 static void ascii_sort_files(wave_info **filenames, int numfiles)
566 {
567   int (*cmpfunc) ();
568 
569   cmpfunc = compare_ascii;
570   qsort(filenames,numfiles,sizeof(wave_info *),cmpfunc);
571 }
572 
version_sort_files(wave_info ** filenames,int numfiles)573 static void version_sort_files(wave_info **filenames,int numfiles)
574 {
575   int (*cmpfunc) ();
576 
577   cmpfunc = compare_version;
578   qsort(filenames,numfiles,sizeof(wave_info *),cmpfunc);
579 }
580 
581 /* public functions */
582 
open_input_stream_fmt(format_module * fmt,char * infile,proc_info * pinfo)583 FILE *open_input_stream_fmt(format_module *fmt,char *infile,proc_info *pinfo)
584 {
585   if (fmt && fmt->supports_input) {
586     /* if this format defines its own input function, run it */
587     if (fmt->input_func)
588       return fmt->input_func(infile,pinfo);
589 
590     return launch_input(fmt,infile,pinfo);
591   }
592 
593   return NULL;
594 }
595 
open_input_stream(wave_info * info)596 bool open_input_stream(wave_info *info)
597 /* opens an input stream, and if it contains an ID3v2 tag, skips past it */
598 {
599   unsigned long bytes_to_read,tag_size;
600   unsigned char tmp[BUF_SIZE];
601 
602   if (info->file_has_id3v2_tag) {
603     if (info->input_format->decoder)
604       st_debug1("decoder [%s] might fail to process ID3v2 tag detected in file: [%s]",info->input_format->decoder,info->filename);
605     else
606       st_debug1("discarding ID3v2 tag detected in file: [%s]",info->filename);
607   }
608 
609   if (NULL == (info->input = open_input_stream_fmt(info->input_format,info->filename,&info->input_proc))) {
610     st_warning("could not open file for streaming input: [%s]",info->filename);
611     return FALSE;
612   }
613 
614   /* check for ID3v2 tag on input stream */
615   if ((tag_size = check_for_id3v2_tag(info->input))) {
616     if (!info->stream_has_id3v2_tag) {
617       if (info->input_format->decoder)
618         st_debug1("discarding ID3v2 tag detected in input stream generated by decoder [%s] from file: [%s]",info->input_format->decoder,info->filename);
619       else
620         st_debug1("discarding ID3v2 tag detected in input stream from file: [%s]",info->filename);
621       info->stream_has_id3v2_tag = TRUE;
622     }
623 
624     if (0 == info->id3v2_tag_size)
625       info->id3v2_tag_size = (wlong)(tag_size + sizeof(id3v2_header));
626 
627     st_debug1("discarding %lu-byte ID3v2 tag in input stream generated by decoder [%s] from file: [%s]",
628       info->id3v2_tag_size,info->filename,info->input_format->decoder);
629 
630     while (tag_size > 0) {
631       bytes_to_read = min(tag_size,BUF_SIZE);
632 
633       if (bytes_to_read != read_n_bytes(info->input,tmp,bytes_to_read,NULL)) {
634         if (info->input_format->decoder)
635           st_warning("error while discarding ID3v2 tag in input stream generated by decoder [%s] from file: [%s]",info->input_format->decoder,info->filename);
636         else
637           st_warning("error while discarding ID3v2 tag in input stream from file: [%s]",info->filename);
638         close_input_stream(info);
639         return FALSE;
640       }
641 
642       tag_size -= bytes_to_read;
643     }
644   }
645   else {
646     close_input_stream(info);
647 
648     if (NULL == (info->input = open_input_stream_fmt(info->input_format,info->filename,&info->input_proc))) {
649       st_warning("could not reopen file for streaming input: [%s]",info->filename);
650       return FALSE;
651     }
652   }
653 
654   return TRUE;
655 }
656 
remove_file(char * filename)657 void remove_file(char *filename)
658 {
659   struct stat sz;
660   int retval;
661 
662   if (st_ops.output_format && !st_ops.output_format->remove_output_file) {
663     return;
664   }
665 
666   if (stat(filename,&sz)) {
667     st_debug1("tried to remove nonexistent file: [%s]",filename);
668     return;
669   }
670 
671   if ((retval = unlink(filename))) {
672     st_warning("error while trying to remove partially-written output file: [%s]",filename);
673     return;
674   }
675 
676   st_debug1("successfully removed file: [%s]",filename);
677 }
678 
files_are_identical(char * file1,char * file2)679 bool files_are_identical(char *file1,char *file2)
680 {
681   struct stat sz1,sz2;
682 
683   if (stat(file1,&sz1) || stat(file2,&sz2))
684     return FALSE;
685 
686   if (!S_ISREG(sz1.st_mode) || !S_ISREG(sz2.st_mode))
687     return FALSE;
688 
689   if (sz1.st_dev != sz2.st_dev)
690     return FALSE;
691 
692   if (sz1.st_ino != sz2.st_ino)
693     return FALSE;
694 
695   /* more checks, because windows inodes are always zero, which creates false positives */
696   if (sz1.st_size != sz2.st_size)
697     return FALSE;
698 
699   if (sz1.st_mtime != sz2.st_mtime)
700     return FALSE;
701 
702   return TRUE;
703 }
704 
odd_sized_data_chunk_is_null_padded(wave_info * info)705 bool odd_sized_data_chunk_is_null_padded(wave_info *info)
706 /* function to determine whether odd-sized data chunks are NULL-padded to an even length */
707 {
708   FILE *devnull;
709   unsigned char nullpad[BUF_SIZE];
710 
711   if (!PROB_ODD_SIZED_DATA(info))
712     return TRUE;
713 
714   /* with no extra RIFF chunks, we can tell from the extra RIFF size whether it's padded */
715   if (-1 == info->extra_riff_size)
716     return FALSE;
717 
718   if (0 == info->extra_riff_size)
719     return TRUE;
720 
721   /* it's odd-sized, and has extra RIFF chunks, so we'll have to make a pass through it to know for sure.
722    * most modes don't need to do this, but ones that update chunk sizes on existing files need to know whether
723    * it's padded in order to calculate the correct chunk size (currently this includes pad and strip modes).
724    */
725 
726   if (NULL == (devnull = open_output(NULLDEVICE))) {
727     return FALSE;
728   }
729 
730   if (!open_input_stream(info)) {
731     fclose(devnull);
732     return FALSE;
733   }
734 
735   st_debug1("scanning WAVE contents to determine whether odd-sized data chunk is padded with a NULL byte per RIFF specs");
736 
737   if (info->header_size + info->data_size != transfer_n_bytes(info->input,devnull,info->header_size + info->data_size,NULL)) {
738     fclose(devnull);
739     close_input_stream(info);
740     return FALSE;
741   }
742 
743   nullpad[0] = 1;
744 
745   if (0 == read_n_bytes(info->input,nullpad,1,NULL)) {
746     fclose(devnull);
747     close_input_stream(info);
748     return FALSE;
749   }
750 
751   fclose(devnull);
752   close_input_stream(info);
753 
754   st_debug1("odd-sized data chunk is%s padded with a NULL byte in file: [%s]",(0==nullpad[0])?"":" not",info->filename);
755 
756   return (0 == nullpad[0]) ? TRUE : FALSE;
757 }
758 
st_snprintf(char * dest,int maxlen,char * formatstr,...)759 void st_snprintf(char *dest,int maxlen,char *formatstr, ...)
760 /* acts like snprintf, but makes 100% sure the string is NULL-terminated */
761 {
762   va_list args;
763 
764   va_start(args,formatstr);
765 
766   st_vsnprintf(dest,maxlen,formatstr,args);
767 
768   dest[maxlen-1] = 0;
769 
770   va_end(args);
771 }
772 
length_to_str(wave_info * info)773 void length_to_str(wave_info *info)
774 /* converts length of file to a string in m:ss or m:ss.ff format */
775 {
776   wlong newlength,rem1,rem2,frames,ms,h,m,s;
777   double tmp;
778   char ffnnn[8];
779 
780   /* get total seconds plus trailing portion of seconds */
781   if (PROB_NOT_CD(info)) {
782     newlength = (wlong)info->exact_length;
783 
784     tmp = info->exact_length - (double)((wlong)info->exact_length);
785     ms = (wlong)((tmp * 1000.0) + 0.5);
786 
787     if (1000 == ms) {
788       ms = 0;
789       newlength++;
790     }
791 
792     st_snprintf(ffnnn,8,"%03lu",ms);
793   }
794   else {
795     newlength = info->length;
796 
797     rem1 = info->data_size % CD_RATE;
798     rem2 = rem1 % CD_BLOCK_SIZE;
799 
800     frames = rem1 / CD_BLOCK_SIZE;
801     if (rem2 >= (CD_BLOCK_SIZE / 2))
802       frames++;
803 
804     if (frames == CD_BLOCKS_PER_SEC) {
805       frames = 0;
806       newlength++;
807     }
808 
809     st_snprintf(ffnnn,8,"%02lu",frames);
810   }
811 
812   /* calculate h:m:s */
813   h = newlength / 3600;
814   newlength -= h * 3600;
815   m = newlength / 60;
816   newlength -= m * 60;
817   s = newlength;
818 
819   if (!st_priv.show_hmmss)
820     m += h * 60;
821 
822   /* now build m_ss string, with h if necessary */
823   if (st_priv.show_hmmss && h > 0)
824     st_snprintf(info->m_ss,16,"%lu:%02lu:%02lu.%s",h,m,s,ffnnn);
825   else
826     st_snprintf(info->m_ss,16,"%lu:%02lu.%s",m,s,ffnnn);
827 }
828 
close_and_wait(FILE * fd,proc_info * pinfo,int child_type,format_module * fm)829 int close_and_wait(FILE *fd,proc_info *pinfo,int child_type,format_module *fm)
830 {
831   int retval;
832 #ifdef WIN32
833   DWORD exitcode;
834 #else
835   int gotpid,status;
836   char tmp[BUF_SIZE],debuginfo[BUF_SIZE];
837 #endif
838 
839   retval = CLOSE_SUCCESS;
840 
841   /* never close stdin/stdout/stderr */
842   if ((fd == stdin) || (fd == stdout) || (fd == stderr))
843     return retval;
844 
845   if (fd) {
846     fclose(fd);
847     fd = NULL;
848   }
849 
850   if (NO_CHILD_PID == pinfo->pid)
851     return retval;
852 
853   /* the following seems to work fine under linux, for any decoder.  but we really only need it for certain ones. */
854   if (CHILD_INPUT == child_type) {
855     /*
856      * kill input processes outright, since we're done with them.  this prevents programs from stalling us too long.
857      * for example, 'la' doesn't exit when we close its stdout, but instead keeps decoding the whole file.  so each
858      * time shntool opens an 'la' file, it will be decoded entirely at least four times before all is said and done,
859      * even for simple things such as checking sizes in len mode.  a preemptive 'kill' here prevents this from
860      * happening.  note that we can't do this for output child types, since we may unwittingly corrupt the output
861      * file in the process -- for example, we would most certainly cause problems with .shn files by killing
862      * 'shorten' while it is busy creating a seek table.
863      *
864      * this is now done on a per-format basis, with reasons given below:
865      *
866      * la   :  'la' decodes entire file no matter what, every time the input file is opened (see above).
867      * aiff :  'sox' takes progressively longer as the input file becomes larger.
868      * bonk :  'bonk' decodes entire file no matter what, every time the input file is opened (see above).
869      * tta  :  'ttaenc' decodes entire file no matter what, every time the input file is opened (see above).
870      */
871 
872     if (fm) {
873       if (fm->kill_when_input_done) {
874         st_debug2("preemptively killing [%s] input process %d to prevent possible delays",fm->decoder,pinfo->pid);
875 #ifdef WIN32
876         TerminateProcess(pinfo->hProcess,0);
877 #else
878         kill(pinfo->pid,SIGHUP);
879 #endif
880       }
881       else {
882         st_debug2("waiting for [%s] input process %d to exit",fm->decoder,pinfo->pid);
883       }
884     }
885     else {
886       st_debug2("waiting for input process %d to exit",pinfo->pid);
887     }
888   }
889   else {
890     if (st_ops.output_format) {
891       st_debug2("waiting for [%s] output process %d to exit",st_ops.output_format->encoder,pinfo->pid);
892     }
893     else {
894       st_debug2("waiting for output process %d to exit",pinfo->pid);
895     }
896   }
897 
898 #ifdef WIN32
899   if ((exitcode = WaitForSingleObject(pinfo->hProcess,INFINITE)))
900     st_debug2("WaitForSingleObject() return value: %d",exitcode);
901 
902   if (GetExitCodeProcess(pinfo->hProcess,&exitcode)) {
903     st_debug2("process %d exit status: [%d]",pinfo->pid,exitcode);
904   }
905   else {
906     st_debug2("process %d exit status could not be determined",pinfo->pid);
907   }
908 #else
909   gotpid = (int)waitpid((pid_t)pinfo->pid,&status,0);
910 
911   st_snprintf(debuginfo,BUF_SIZE,"process %d exit status: [%d",gotpid,WIFEXITED(status));
912   if (WIFEXITED(status)) {
913     st_snprintf(tmp,BUF_SIZE,"/%d",WEXITSTATUS(status));
914     strcat(debuginfo,tmp);
915   }
916   st_snprintf(tmp,BUF_SIZE,"] [%d",WIFSIGNALED(status));
917   strcat(debuginfo,tmp);
918   if (WIFSIGNALED(status)) {
919     st_snprintf(tmp,BUF_SIZE,"/%d",WTERMSIG(status));
920     strcat(debuginfo,tmp);
921   }
922   st_snprintf(tmp,BUF_SIZE,"] [%d",WIFSTOPPED(status));
923   strcat(debuginfo,tmp);
924   if (WIFSTOPPED(status)) {
925     st_snprintf(tmp,BUF_SIZE,"/%d",WSTOPSIG(status));
926     strcat(debuginfo,tmp);
927   }
928   strcat(debuginfo,"]");
929 
930   st_debug2(debuginfo);
931 #endif
932 
933 #ifdef WIN32
934   if (0 != exitcode) {
935 #else
936   if (WIFEXITED(status) && WEXITSTATUS(status)) {
937 #endif
938     if (CHILD_OUTPUT == child_type) {
939       st_warning("child encoder process %d had non-zero exit status %d",pinfo->pid,
940 #ifdef WIN32
941                  exitcode);
942 #else
943                  WEXITSTATUS(status));
944 #endif
945       retval = CLOSE_CHILD_ERROR_OUTPUT;
946     }
947     else if (CHILD_INPUT == child_type) {
948       retval = CLOSE_CHILD_ERROR_INPUT;
949     }
950   }
951 
952   return retval;
953 }
954 
955 char *st_progname()
956 {
957   return st_priv.fullprogname;
958 }
959 
960 void alter_file_order(wave_info **filenames,int numfiles)
961 /* a simple menu system to change the order of the files as given on the command line */
962 {
963   int i,current,a,b;
964   char response[BUF_SIZE],
965        *p,
966        *args[3];
967   wave_info *tmp;
968   bool finished = FALSE;
969 
970   /* some modes read files/split points/etc. from stdin, so we can't ask.  force user to use -O option */
971   if (!isatty(fileno(stdin)))
972     st_error("standard input is not a terminal -- cannot invoke interactive file order editor");
973 
974   st_info("\nEntering file order editor.\n");
975 
976   while (!finished) {
977     st_info("\n");
978     st_info("File list:\n");
979     st_info("\n");
980     for (i=0;i<numfiles;i++)
981       st_info("%2d: %s\n",i+1,filenames[i]->filename);
982     st_info("\n");
983     st_info("Commands:\n");
984     st_info("\n");
985     st_info("  [s]wap a b   (swaps positions a and b)\n");
986     st_info("  [m]ove a b   (moves position a to position b, shifting everything in between)\n");
987     st_info("  [b]egin a    (moves position a to the beginning of the list)\n");
988     st_info("  [e]nd a      (moves position a to the end of the list)\n");
989     st_info("  [n]atural    (sorts files using a natural sorting algorithm -- t1, t2, ... t10)\n");
990     st_info("  [a]scii      (sorts files based on their ascii characters -- t1, t10, t2, ...)\n");
991     st_info("  [d]one       (quits this editor, and continues with processing)\n");
992     st_info("  [q]uit       (quits %s [you can also use Ctrl-C])\n",st_priv.progname);
993     st_info("\n? ");
994     fgets(response,BUF_SIZE-1,stdin);
995     if (feof(stdin))
996       strcpy(response,"done");
997     trim(response);
998     p = response;
999     current = 0;
1000     args[0] = NULL;
1001     args[1] = NULL;
1002     args[2] = NULL;
1003     while (*p) {
1004       while (' ' == *p || '\t' == *p || '\n' == *p) {
1005         *p = 0;
1006         p++;
1007       }
1008       if (0 == *p)
1009         break;
1010       if (3 > current)
1011         args[current++] = p;
1012       while (*p && ' ' != *p && '\t' != *p && '\n' != *p)
1013         p++;
1014     }
1015     if (NULL == args[0])
1016       args[0] = "";
1017 
1018     st_info("\n");
1019 
1020     switch (args[0][0]) {
1021       case 's':
1022       case 'S':
1023         if (NULL == args[2])
1024           st_info("Not enough positions supplied to the 'swap' command\n");
1025         else {
1026           a = atoi(args[1]);
1027           b = atoi(args[2]);
1028           if ((a<1 || a>numfiles) || (b<1 || b>numfiles))
1029             st_info("One or more of your entries are out of range.  Try again.\n");
1030           else {
1031             st_info("Swapping positions %d and %d...\n",a,b);
1032             tmp = filenames[a-1];
1033             filenames[a-1] = filenames[b-1];
1034             filenames[b-1] = tmp;
1035           }
1036         }
1037         break;
1038       case 'm':
1039       case 'M':
1040         if (NULL == args[2])
1041           st_info("Not enough positions supplied to the 'move' command\n");
1042         else {
1043           a = atoi(args[1]);
1044           b = atoi(args[2]);
1045           if ((a<1 || a>numfiles) || (b<1 || b>numfiles))
1046             st_info("One or more of your entries are out of range.  Try again.\n");
1047           else {
1048             st_info("Moving position %d to position %d...\n",a,b);
1049             tmp = filenames[a-1];
1050             if (a < b) {
1051               for (i=a-1;i<b-1;i++)
1052                 filenames[i] = filenames[i+1];
1053             }
1054             else if (a > b) {
1055               for (i=a-1;i>b-1;i--)
1056                 filenames[i] = filenames[i-1];
1057             }
1058             filenames[b-1] = tmp;
1059           }
1060         }
1061         break;
1062       case 'b':
1063       case 'B':
1064         if (NULL == args[1])
1065           st_info("Missing a position for the 'begin' command.\n");
1066         else {
1067           a = atoi(args[1]);
1068           if ((a<1 || a>numfiles))
1069             st_info("Your entry is out of range.  Try again.\n");
1070           else {
1071             st_info("Moving position %d to the beginning of the list\n",a);
1072             tmp = filenames[a-1];
1073             for (i=a-1;i>0;i--)
1074               filenames[i] = filenames[i-1];
1075             filenames[0] = tmp;
1076           }
1077         }
1078         break;
1079       case 'e':
1080       case 'E':
1081         if (NULL == args[1])
1082           st_info("Missing a position for the 'end' command.\n");
1083         else {
1084           a = atoi(args[1]);
1085           if ((a<1 || a>numfiles))
1086             st_info("Your entry is out of range.  Try again.\n");
1087           else {
1088             st_info("Moving position %d to the end of the list...\n",a);
1089             tmp = filenames[a-1];
1090             for (i=a-1;i<numfiles-1;i++)
1091               filenames[i] = filenames[i+1];
1092             filenames[numfiles-1] = tmp;
1093           }
1094         }
1095         break;
1096       case 'n':
1097       case 'N':
1098         version_sort_files(filenames,numfiles);
1099         break;
1100       case 'a':
1101       case 'A':
1102         ascii_sort_files(filenames,numfiles);
1103         break;
1104       case 'd':
1105       case 'D':
1106         st_info("Exiting file order editor.\n");
1107         finished = TRUE;
1108         break;
1109       case 'q':
1110       case 'Q':
1111         st_info("Quitting %s.\n",st_priv.progname);
1112         exit(ST_EXIT_QUIT);
1113         break;
1114       default:
1115         st_info("Unrecognized command: %s\n",args[0]);
1116         break;
1117     }
1118   }
1119 
1120   st_info("\n");
1121 }
1122 
1123 wlong smrt_parse(unsigned char *data,wave_info *info)
1124 /* currently not so smart, so I gave it the Homer Simpson spelling :) */
1125 {
1126   wlong bytes;
1127   unsigned char tmp[BUF_SIZE];
1128 
1129   strcpy((char *)tmp,(const char *)data);
1130 
1131   /* check for all digits */
1132   if (-1 != (bytes = is_numeric(tmp)))
1133     return bytes;
1134 
1135   /* check for m:ss */
1136   if (-1 != (bytes = is_m_ss(tmp,info)))
1137     return bytes;
1138 
1139   /* check for m:ss.ff */
1140   if (-1 != (bytes = is_m_ss_ff(tmp,info)))
1141     return bytes;
1142 
1143   /* check for m:ss.nnn */
1144   if (-1 != (bytes = is_m_ss_nnn(tmp,info)))
1145     return bytes;
1146 
1147   /* it was not in any of these formats */
1148 
1149   st_error("value not in bytes, m:ss, m:ss.ff, or m:ss.nnn format: [%s]",data);
1150 
1151   return -1;
1152 }
1153 
1154 int st_getopt(int argc,char **argv,char *mode_opts)
1155 {
1156   char ops[BUF_SIZE],*p,c[2],*global_opts;
1157   int opt;
1158   format_module *input_format = NULL;
1159 
1160   global_opts = (st_priv.mode->creates_files) ? GLOBAL_OPTS GLOBAL_OPTS_OUTPUT : GLOBAL_OPTS;
1161 
1162   /* make sure the calling mode isn't using an option reserved for shntool global use */
1163   c[1] = 0;
1164   for (p=global_opts;*p;p++) {
1165     if (':' == *p)
1166       continue;
1167     c[0] = *p;
1168     if (strstr(mode_opts,c))
1169       st_error("mode attempted to use global option: [-%s]",c);
1170   }
1171 
1172   if (st_priv.mode->creates_files && (NULL == st_ops.output_format))
1173     st_ops.output_format = default_output_format();
1174 
1175   st_snprintf(ops,BUF_SIZE,"%s%s",global_opts,mode_opts);
1176 
1177   opt = getopt(argc,argv,ops);
1178 
1179   if (-1 == opt)
1180     return opt;
1181 
1182   /* handle global options before mode options */
1183   switch (opt) {
1184     case 'D':
1185       st_priv.debug_level++;
1186       break;
1187     case 'F':
1188       if (NULL == optarg)
1189         st_help("missing input file filename");
1190       st_input.type = INPUT_FILE;
1191       st_input.filename_source = optarg;
1192       break;
1193     case 'H':
1194       st_priv.show_hmmss = TRUE;
1195       break;
1196     case 'P':
1197       if (NULL == optarg)
1198         st_help("missing progress indicator type");
1199       if (!strcmp(optarg,"pct"))
1200         st_priv.progress_type = PROGRESS_PERCENT;
1201       else if (!strcmp(optarg,"dot"))
1202         st_priv.progress_type = PROGRESS_DOT;
1203       else if (!strcmp(optarg,"spin"))
1204         st_priv.progress_type = PROGRESS_SPIN;
1205       else if (!strcmp(optarg,"face"))
1206         st_priv.progress_type = PROGRESS_FACE;
1207       else if (!strcmp(optarg,"none"))
1208         st_priv.progress_type = PROGRESS_NONE;
1209       else
1210         st_help("invalid progress indicator type: [%s]",optarg);
1211       break;
1212     case '?':
1213     case 'h':
1214       if ('?' == opt)
1215         st_info("\n");
1216       if (st_priv.mode->run_help)
1217         st_priv.mode->run_help();
1218       st_global_usage();
1219       exit(('?' == opt) ? ST_EXIT_ERROR : ST_EXIT_SUCCESS);
1220       break;
1221     case 'i':
1222       if (NULL == optarg)
1223         st_help("missing input format");
1224       if (NULL == (input_format = find_format(optarg)))
1225         st_help("missing input format name");
1226       if (!input_format->supports_input)
1227         st_help("format does not support input: [%s]",input_format->name);
1228       parse_input_args_cmd(input_format,optarg);
1229       break;
1230     case 'q':
1231       st_priv.suppress_stderr = TRUE;
1232       break;
1233     case 'r':
1234       if (NULL == optarg)
1235         st_help("missing reorder type");
1236       if (!strcmp(optarg,"ask"))
1237         st_priv.reorder_type = ORDER_ASK;
1238       else if (!strcmp(optarg,"ascii"))
1239         st_priv.reorder_type = ORDER_ASCII;
1240       else if (!strcmp(optarg,"natural"))
1241         st_priv.reorder_type = ORDER_NATURAL;
1242       else if (!strcmp(optarg,"none"))
1243         st_priv.reorder_type = ORDER_AS_IS;
1244       else
1245         st_help("invalid reorder type: [%s]",optarg);
1246       break;
1247     case 'v':
1248       st_version();
1249       exit(ST_EXIT_SUCCESS);
1250       break;
1251     case 'w':
1252       st_priv.suppress_warnings = TRUE;
1253       break;
1254   }
1255 
1256   if (st_priv.mode->creates_files) {
1257     switch (opt) {
1258       case 'O':
1259         if (NULL == optarg)
1260           st_help("missing overwrite action");
1261         if (!strcmp(optarg,"ask"))
1262           st_priv.clobber_action = CLOBBER_ACTION_ASK;
1263         else if (!strcmp(optarg,"always"))
1264           st_priv.clobber_action = CLOBBER_ACTION_ALWAYS;
1265         else if (!strcmp(optarg,"never"))
1266           st_priv.clobber_action = CLOBBER_ACTION_NEVER;
1267         else
1268           st_help("invalid overwrite action: [%s]",optarg);
1269         break;
1270       case 'a':
1271         if (NULL == optarg)
1272           st_help("missing filename prefix");
1273         st_ops.output_prefix = optarg;
1274         break;
1275       case 'd':
1276         if (NULL == optarg)
1277           st_help("missing output directory");
1278         st_ops.output_directory = optarg;
1279         break;
1280       case 'o':
1281         if (NULL == optarg)
1282           st_help("missing output format");
1283         if (NULL == (st_ops.output_format = find_format(optarg)))
1284           st_help("missing output format name");
1285         if (!st_ops.output_format->supports_output)
1286           st_help("format does not support output: [%s]",st_ops.output_format->name);
1287         parse_output_args_cmd(st_ops.output_format,optarg);
1288         break;
1289       case 'z':
1290         if (NULL == optarg)
1291           st_help("missing filename postfix");
1292         st_ops.output_postfix = optarg;
1293         break;
1294     }
1295   }
1296 
1297   return opt;
1298 }
1299 
1300 void discard_header(wave_info *info)
1301 {
1302   unsigned char *header;
1303 
1304   if (NULL == (header = malloc(info->header_size * sizeof(unsigned char))))
1305     st_error("could not allocate %d bytes for WAVE header",info->header_size);
1306 
1307   if (read_n_bytes(info->input,header,info->header_size,NULL) != info->header_size)
1308     st_error("error while discarding %d-byte header from file: [%s]",info->header_size,info->filename);
1309 
1310   st_free(header);
1311 }
1312 
1313 void create_output_filename(char *infile,char *inext,char *outfile)
1314 {
1315   strcpy(outfile,"");
1316 
1317   if (st_ops.output_format) {
1318     if (st_ops.output_format->create_output_filename) {
1319       st_ops.output_format->create_output_filename(outfile);
1320       return;
1321     }
1322 
1323     rename_by_extension(
1324       (st_ops.output_format->encoder) ? &st_ops.output_format->output_args_template : NULL,
1325       infile,outfile,inext,st_ops.output_format->extension);
1326   }
1327 }
1328 
1329 FILE *open_output_stream(char *outfile,proc_info *pinfo)
1330 {
1331   pinfo->pid = NO_CHILD_PID;
1332 
1333   if (st_ops.output_format && st_ops.output_format->supports_output) {
1334     /* if this format defines its own output function, run it */
1335     if (st_ops.output_format->output_func)
1336       return st_ops.output_format->output_func(outfile,pinfo);
1337 
1338     return launch_output(st_ops.output_format,outfile,pinfo);
1339   }
1340 
1341   return NULL;
1342 }
1343 
1344 void st_global_usage()
1345 {
1346   st_info("Global options:\n");
1347   st_info("\n");
1348   st_info("  -D      print debugging information (each one increases debugging level)\n");
1349   st_info("  -F file get input filenames from file, instead of command line or terminal\n");
1350   st_info("  -H      print times in h:mm:ss.{ff,nnn} format, instead of m:ss.{ff,nnn}\n");
1351   if (st_priv.mode->creates_files) {
1352     st_info("  -O val  overwrite existing files?  val is: {[ask], always, never}\n");
1353   }
1354   st_info("  -P type progress indicator type.  type is: {[pct], dot, spin, face, none}\n");
1355   if (st_priv.mode->creates_files) {
1356     st_info("  -a str  prefix 'str' to base part of output filenames\n");
1357     st_info("  -d dir  specify output directory\n");
1358   }
1359   st_info("  -i fmt  specify input file format decoder and/or arguments.\n");
1360   st_info("          format is:  \"fmt decoder [arg1 ... argN (%s = filename)]\"\n",FILENAME_PLACEHOLDER);
1361   if (st_priv.mode->creates_files) {
1362     st_info("  -o fmt  specify output file format, extension, encoder and/or arguments.\n");
1363     st_info("          format is:  \"fmt [ext=abc] [encoder [arg1 ... argN (%s = filename)]]\"\n",FILENAME_PLACEHOLDER);
1364   }
1365   st_info("  -q      suppress non-critical output (quiet mode)\n");
1366   st_info("  -r val  reorder input files?  val is: {ask, ascii, [natural], none}\n");
1367   st_info("  -v      show version information\n");
1368   st_info("  -w      suppress warnings\n");
1369   if (st_priv.mode->creates_files) {
1370     st_info("  -z str  postfix 'str' to base part of output filenames\n");
1371   }
1372   st_info("  --      indicates that everything following it is a filename\n");
1373   st_info("\n");
1374 }
1375 
1376 void arg_init(format_module *fm)
1377 {
1378   char *p,envname[BUF_SIZE],upper[BUF_SIZE];
1379 
1380   strcpy(upper,fm->name);
1381 
1382   /* convert format name to upper case */
1383   for (p=upper;*p;p++) {
1384     if (*p >= 'a' && *p <= 'z')
1385       *p = *p - 32;
1386   }
1387 
1388   if (fm->supports_input) {
1389     /* initialize with format defaults */
1390     parse_input_args_fmt(fm,fm->decoder_args);
1391 
1392     /* check environment for user-specified decoder */
1393     st_snprintf(envname,BUF_SIZE,"ST_%s_DEC",upper);
1394     parse_input_args_env(fm,scan_env(envname));
1395   }
1396 
1397   if (fm->supports_output) {
1398     /* initialize with format defaults */
1399     parse_output_args_fmt(fm,fm->encoder_args);
1400 
1401     /* check environment for user-specified extension and/or encoder */
1402     st_snprintf(envname,BUF_SIZE,"ST_%s_ENC",upper);
1403     parse_output_args_env(fm,scan_env(envname));
1404   }
1405 }
1406 
1407 void arg_add(child_args *child_args,char *arg)
1408 {
1409   if (child_args->num_args >= MAX_CHILD_ARGS)
1410     st_error("too many arguments specified -- limit is %d",MAX_CHILD_ARGS);
1411 
1412   child_args->args[child_args->num_args] = arg;
1413   child_args->num_args++;
1414 }
1415 
1416 void arg_reset(child_args *child_args)
1417 {
1418   int i;
1419 
1420   child_args->num_args = 0;
1421 
1422   for (i=0;i<MAX_CHILD_ARGS;i++)
1423     child_args->args[i] = NULL;
1424 }
1425 
1426 void arg_replace(child_args *child_args,int pos,char *arg)
1427 {
1428   child_args->args[pos] = arg;
1429 }
1430 
1431 void reorder_files(wave_info **files,int numfiles)
1432 {
1433   switch (st_priv.reorder_type) {
1434     case ORDER_ASK:
1435       alter_file_order(files,numfiles);
1436       break;
1437     case ORDER_NATURAL:
1438       version_sort_files(files,numfiles);
1439       break;
1440     case ORDER_ASCII:
1441       ascii_sort_files(files,numfiles);
1442       break;
1443     case ORDER_AS_IS:
1444     default:
1445       break;
1446   }
1447 }
1448 
1449 static void prog_print_data(progress_info *proginfo)
1450 {
1451   if (proginfo->prefix) {
1452     st_info("%s ",proginfo->prefix);
1453   }
1454 
1455   if (proginfo->filename1) {
1456     st_info("[%s] ",proginfo->filename1);
1457     if (proginfo->filedesc1) {
1458       st_info("(%s) ",proginfo->filedesc1);
1459     }
1460   }
1461 
1462   if (proginfo->clause) {
1463     st_info("%s ",proginfo->clause);
1464   }
1465 
1466   if (proginfo->filename2) {
1467     st_info("[%s] ",proginfo->filename2);
1468     if (proginfo->filedesc2) {
1469       st_info("(%s) ",proginfo->filedesc2);
1470     }
1471   }
1472 
1473   st_info(": ");
1474 }
1475 
1476 static void prog_init(progress_info *proginfo)
1477 {
1478   if (!proginfo->initialized) {
1479     proginfo->bytes_written = 0;
1480     proginfo->dot_step = -1;
1481     proginfo->last_percent = -1;
1482     proginfo->progress_shown = FALSE;
1483     prog_print_data(proginfo);
1484     proginfo->initialized = TRUE;
1485     st_priv.screen_dirty = FALSE;
1486   }
1487 
1488   if (st_priv.screen_dirty) {
1489     proginfo->last_percent = -1;
1490     proginfo->progress_shown = FALSE;
1491     prog_print_data(proginfo);
1492     st_priv.screen_dirty = FALSE;
1493   }
1494 }
1495 
1496 static void prog_uninit(progress_info *proginfo)
1497 {
1498   proginfo->initialized = FALSE;
1499   proginfo->progress_shown = FALSE;
1500   proginfo->bytes_written = 0;
1501   proginfo->bytes_total = 0;
1502   proginfo->dot_step = 0;
1503   proginfo->percent = 0;
1504   proginfo->last_percent = 0;
1505 }
1506 
1507 static void prog_erase_chars(progress_info *proginfo,int erasecnt)
1508 {
1509   int i;
1510 
1511   if (!proginfo->progress_shown) {
1512     proginfo->progress_shown = TRUE;
1513     return;
1514   }
1515 
1516   for (i=0;i<erasecnt;i++)
1517     st_info("\b");
1518 }
1519 
1520 static void prog_show_pct(progress_info *proginfo)
1521 {
1522   prog_erase_chars(proginfo,5);
1523 
1524   st_info("%3d%% ",proginfo->percent);
1525 }
1526 
1527 static void prog_show_dot(progress_info *proginfo)
1528 {
1529   if (-1 == proginfo->dot_step)
1530     proginfo->dot_step = 0;
1531 
1532   while ((proginfo->dot_step + 10) <= proginfo->percent) {
1533     st_info(".");
1534     proginfo->dot_step += 10;
1535   }
1536 }
1537 
1538 static void prog_show_spin(progress_info *proginfo)
1539 {
1540   char spin[4] = { '|', '/', '-', '\\' };
1541 
1542   while ((proginfo->dot_step + 1) <= proginfo->percent) {
1543     proginfo->dot_step++;
1544     prog_erase_chars(proginfo,2);
1545     st_info("%c ",spin[proginfo->dot_step % 4]);
1546   }
1547 }
1548 
1549 static void prog_show_face(progress_info *proginfo)
1550 {
1551   char *faces[6] = { ":-(", ":-/", ":-\\", ":-|", ":-)", ":-D" };
1552   bool show_face = FALSE;
1553 
1554   if (-1 == proginfo->dot_step)
1555     proginfo->dot_step = -20;
1556 
1557   while ((proginfo->dot_step + 20) <= proginfo->percent) {
1558     proginfo->dot_step += 20;
1559     show_face = TRUE;
1560   }
1561 
1562   if (!show_face)
1563     return;
1564 
1565   prog_erase_chars(proginfo,4);
1566 
1567   st_info("%s ",faces[proginfo->dot_step/20]);
1568 }
1569 
1570 static void prog_finish(char *status,progress_info *proginfo)
1571 {
1572   if (PROGRESS_DOT == st_priv.progress_type)
1573     st_info(" ");
1574 
1575   st_info("%s\n",status);
1576 
1577   prog_uninit(proginfo);
1578 }
1579 
1580 void prog_update(progress_info *proginfo)
1581 {
1582   prog_init(proginfo);
1583 
1584   if (proginfo->bytes_total <= 0)
1585     proginfo->percent = 0;
1586   else
1587     proginfo->percent = (int)(100.0 * ((double)proginfo->bytes_written)/((double)proginfo->bytes_total));
1588 
1589   proginfo->percent = min(max(proginfo->percent,0),100);
1590 
1591   if (proginfo->percent <= proginfo->last_percent)
1592     return;
1593 
1594   switch (st_priv.progress_type) {
1595     case PROGRESS_PERCENT:
1596       prog_show_pct(proginfo);
1597       break;
1598     case PROGRESS_DOT:
1599       prog_show_dot(proginfo);
1600       break;
1601     case PROGRESS_SPIN:
1602       prog_show_spin(proginfo);
1603       break;
1604     case PROGRESS_FACE:
1605       prog_show_face(proginfo);
1606       break;
1607     case PROGRESS_NONE:
1608     default:
1609       break;
1610   }
1611 
1612   proginfo->last_percent = proginfo->percent;
1613 }
1614 
1615 void prog_success(progress_info *proginfo)
1616 {
1617   proginfo->bytes_written = proginfo->bytes_total;
1618 
1619   prog_update(proginfo);
1620 
1621   prog_finish("OK",proginfo);
1622 }
1623 
1624 void prog_error(progress_info *proginfo)
1625 {
1626   prog_update(proginfo);
1627 
1628   prog_finish("ERROR",proginfo);
1629 }
1630 
1631 void input_init(int argn,int argc,char **argv)
1632 {
1633   if (INPUT_FILE != st_input.type && INPUT_INTERNAL != st_input.type) {
1634     if (argn >= argc) {
1635       st_input.type = INPUT_STDIN;
1636     }
1637     else {
1638       st_input.type = INPUT_CMDLINE;
1639     }
1640   }
1641 
1642   switch (st_input.type) {
1643     case INPUT_CMDLINE:
1644       st_debug1("reading input filenames from command line");
1645       st_input.argn = argn;
1646       st_input.argc = argc;
1647       st_input.argv = argv;
1648       break;
1649 
1650     case INPUT_STDIN:
1651       st_debug1("reading input filenames from stdin");
1652       if (isatty(fileno(stdin)))
1653         st_info("enter input filename(s):\n");
1654       st_input.fd = stdin;
1655       break;
1656 
1657     case INPUT_FILE:
1658       st_debug1("reading input filenames from file: [%s]",st_input.filename_source);
1659       if (NULL == (st_input.fd = fopen(st_input.filename_source,"rb"))) {
1660         st_error("could not open input filename file: [%s]",st_input.filename_source);
1661       }
1662       break;
1663 
1664     case INPUT_INTERNAL:
1665       st_input.filecur = 0;
1666       break;
1667 
1668     default:
1669       break;
1670   }
1671 }
1672 
1673 char *input_get_filename()
1674 {
1675   static char internal_filename[FILENAME_SIZE];
1676   char *filename = NULL;
1677 
1678   switch (st_input.type) {
1679     case INPUT_CMDLINE:
1680       if (st_input.argn < st_input.argc) {
1681         filename = st_input.argv[st_input.argn];
1682         st_input.argn++;
1683       }
1684       break;
1685 
1686     case INPUT_STDIN:
1687     case INPUT_FILE:
1688       fgets(internal_filename,FILENAME_SIZE-1,st_input.fd);
1689       if (!feof(st_input.fd)) {
1690         trim(internal_filename);
1691         filename = internal_filename;
1692       }
1693       else {
1694         if (INPUT_FILE == st_input.type) {
1695           fclose(st_input.fd);
1696         }
1697       }
1698       break;
1699 
1700     case INPUT_INTERNAL:
1701       if (st_input.filecur < st_input.filemax) {
1702         st_debug1("returning file %d: [%s]",st_input.filecur,st_input.filenames[st_input.filecur]);
1703         filename = st_input.filenames[st_input.filecur];
1704         st_input.filecur++;
1705       }
1706       break;
1707 
1708     default:
1709       break;
1710   }
1711 
1712   return filename;
1713 }
1714 
1715 void input_read_all_files()
1716 {
1717   char *filename;
1718 
1719   st_input.filemax = 0;
1720 
1721   while ((filename = input_get_filename())) {
1722     st_input.filenames[st_input.filemax] = strdup(filename);
1723     st_input.filemax++;
1724     if (st_input.filemax >= MAX_FILENAMES)
1725       st_error("exceeded maximum number of filenames: [%s]",MAX_FILENAMES);
1726   }
1727 
1728   st_input.type = INPUT_INTERNAL;
1729   st_input.filecur = 0;
1730 }
1731 
1732 int input_get_file_count()
1733 {
1734   return st_input.filemax;
1735 }
1736