1 /* This program is licensed under the GNU General Public License,
2  * version 2, a copy of which is included with this program.
3  *
4  * (c) 2000-2002 Michael Smith <msmith@xiph.org>
5  * (c) 2001      Ralph Giles <giles@xiph.org>
6  * (c) 2017-2020 Philipp Schafft <phschafft@de.loewenfelsen.net>
7  *
8  * Front end to show how to use vcedit;
9  * Of limited usability on its own, but could be useful.
10  */
11 
12 #ifdef HAVE_CONFIG_H
13 #include <config.h>
14 #endif
15 
16 #include <stdio.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <locale.h>
20 
21 #if HAVE_STAT && HAVE_CHMOD
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <unistd.h>
25 #endif
26 
27 #include "getopt.h"
28 #include "utf8.h"
29 #include "i18n.h"
30 
31 #include "vcedit.h"
32 
33 
34 /* getopt format struct */
35 static const struct option long_options[] = {
36     {"list",        no_argument,        0, 'l'},
37     {"append",      no_argument,        0, 'a'},
38     {"tag",         required_argument,  0, 't'},
39     {"rm",          required_argument,  0, 'd'},
40     {"write",       no_argument,        0, 'w'},
41     {"help",        no_argument,        0, 'h'},
42     {"quiet",       no_argument,        0, 'q'},  /* unused */
43     {"version",     no_argument,        0, 'V'},
44     {"commentfile", required_argument,  0, 'c'},
45     {"raw",         no_argument,        0, 'R'},
46     {"escapes",     no_argument,        0, 'e'},
47     {NULL, 0, 0, 0}
48 };
49 
50 /* local parameter storage from parsed options */
51 typedef struct {
52     /* mode and flags */
53     int mode;
54     int raw;
55     int escapes;
56 
57     /* file names and handles */
58     char    *infilename, *outfilename;
59     char    *commentfilename;
60     FILE    *in, *out, *com;
61     int tempoutfile;
62 
63     /* comments */
64     int commentcount;
65     char    **comments;
66     int     *changetypes;
67 } param_t;
68 
69 #define MODE_NONE  0
70 #define MODE_LIST  1
71 #define MODE_WRITE 2
72 #define MODE_APPEND 3
73 
74 #define CHANGETYPE_ADD 0
75 #define CHANGETYPE_REMOVE 1
76 
77 /* prototypes */
78 void usage(void);
79 void print_comments(FILE *out, vorbis_comment *vc, int raw, int escapes);
80 int  alter_comment(char *line, vorbis_comment *vc, int raw, int escapes, int changetype);
81 
82 char *escape(const char *from, int fromsize);
83 char *unescape(const char *from, int *tosize);
84 
85 param_t *new_param(void);
86 void free_param(param_t *param);
87 void parse_options(int argc, char *argv[], param_t *param);
88 void open_files(param_t *p);
89 void close_files(param_t *p, int output_written);
90 
91 static void _vorbis_comment_rm_tag(vorbis_comment *vc, const char *tag, const char *contents);
92 
read_line(FILE * input)93 char * read_line (FILE *input)
94 {
95         /* Construct a list of buffers. Each buffer will hold 1024 bytes. If
96          * more is required, it is easier to extend the list than to extend
97          * a massive buffer. When all the bytes up to a newline have been
98          * retrieved, join the buffers together
99         **/
100         int buffer_count = 0, max_buffer_count = 10, buffer_size = 1024;
101         int ii;
102         char **buffers = 0, *buffer;
103 
104         /* Start with room for 10 buffers */
105         buffers = malloc (sizeof (char *) * max_buffer_count);
106 
107         while (1)
108         {
109                 char *retval;
110 
111                 /* Increase the max buffer count in increments of 10 */
112                 if (buffer_count == max_buffer_count)
113                 {
114                         max_buffer_count = buffer_count + 10;
115                         buffers = realloc (buffers, sizeof (char *) * max_buffer_count);
116                 }
117 
118                 buffer = malloc (sizeof (char) * (buffer_size + 1));
119                 retval = fgets (buffer, (buffer_size + 1), input);
120 
121                 if (retval)
122                 {
123                         buffers[buffer_count] = buffer;
124                         buffer_count++;
125 
126                         if (retval[strlen (retval) - 1] == '\n')
127                         {
128                                 /* End of the line */
129                                 break;
130                         }
131                 }
132 
133                 else
134                 {
135                         /* End of the file */
136                         free (buffer);
137                         break;
138                 }
139         }
140 
141         if (buffer_count == 0)
142         {
143                 /* No more data to read */
144                 free (buffers);
145                 return 0;
146         }
147 
148         /* Create one giant buffer to contain all the retrieved text */
149         buffer = malloc (sizeof (char) * (buffer_count * (buffer_size + 1)));
150 
151         /* Copy buffer data and free memory */
152         for (ii = 0; ii < buffer_count; ii++)
153         {
154                 strncpy (buffer + (ii * buffer_size), buffers[ii], buffer_size);
155                 free (buffers[ii]);
156         }
157 
158         free (buffers);
159         buffer[buffer_count * (buffer_size + 1) - 1] = 0;
160         return buffer;
161 }
162 
163 /**********
164 
165    Delete a comment from the comment list.
166 
167    This deletes a comment from the list.
168    We implement this here as libvorbis does not provide it.
169    Also this is very inefficent as we need to copy the compelet structure.
170    But it is only called once for each file and each deleted key so that is
171    not much of a problem.
172 
173 ***********/
174 
_vorbis_comment_rm_tag(vorbis_comment * vc,const char * tag,const char * contents)175 static void _vorbis_comment_rm_tag(vorbis_comment *vc, const char *tag, const char *contents)
176 {
177     vorbis_comment vc_tmp;
178     size_t taglen;
179     int i;
180     const char *p;
181     int match;
182 
183     taglen = strlen(tag);
184 
185     vorbis_comment_init(&vc_tmp);
186 
187     for (i = 0; i < vc->comments; i++)
188     {
189         p = vc->user_comments[i];
190         match = 0;
191 
192         do {
193             if (strncasecmp(p, tag, taglen) != 0) {
194                 break;
195             }
196             p += taglen;
197             if (*p != '=') {
198                 break;
199             }
200             p++;
201             if (contents) {
202                 if (strcmp(p, contents) != 0) {
203                     break;
204                 }
205             }
206 
207             match = 1;
208         } while (0);
209 
210         if (!match) {
211             vorbis_comment_add(&vc_tmp, vc->user_comments[i]);
212         }
213     }
214 
215     vorbis_comment_clear(vc);
216 
217     *vc = vc_tmp;
218 }
219 
220 /**********
221    main.c
222 
223    This is the main function where options are read and written
224    you should be able to just read this function and see how
225    to call the vcedit routines. Details of how to pack/unpack the
226    vorbis_comment structure itself are in the following two routines.
227    The rest of the file is ui dressing so make the program minimally
228    useful as a command line utility and can generally be ignored.
229 
230 ***********/
231 
main(int argc,char ** argv)232 int main(int argc, char **argv)
233 {
234     vcedit_state *state;
235     vorbis_comment *vc;
236     param_t *param;
237     int i;
238 
239     setlocale(LC_ALL, "");
240     bindtextdomain(PACKAGE, LOCALEDIR);
241     textdomain(PACKAGE);
242 
243     /* initialize the cmdline interface */
244     param = new_param();
245     parse_options(argc, argv, param);
246 
247     /* take care of opening the requested files */
248     /* relevent file pointers are returned in the param struct */
249     open_files(param);
250 
251     /* which mode are we in? */
252 
253     if (param->mode == MODE_LIST) {
254 
255         state = vcedit_new_state();
256 
257         if (vcedit_open(state, param->in) < 0)
258         {
259             fprintf(stderr, _("Failed to open file as Vorbis: %s\n"),
260                     vcedit_error(state));
261             close_files(param, 0);
262             free_param(param);
263             vcedit_clear(state);
264             return 1;
265         }
266 
267         /* extract and display the comments */
268         vc = vcedit_comments(state);
269         print_comments(param->com, vc, param->raw, param->escapes);
270 
271         /* done */
272         vcedit_clear(state);
273 
274         close_files(param, 0);
275         free_param(param);
276         return 0;
277     }
278 
279     if (param->mode == MODE_WRITE || param->mode == MODE_APPEND) {
280 
281         state = vcedit_new_state();
282 
283         if (vcedit_open(state, param->in) < 0)
284         {
285             fprintf(stderr, _("Failed to open file as Vorbis: %s\n"),
286                     vcedit_error(state));
287             close_files(param, 0);
288             free_param(param);
289             vcedit_clear(state);
290             return 1;
291         }
292 
293         /* grab and clear the exisiting comments */
294         vc = vcedit_comments(state);
295         if (param->mode != MODE_APPEND)
296         {
297             vorbis_comment_clear(vc);
298             vorbis_comment_init(vc);
299         }
300 
301         for (i=0; i < param->commentcount; i++)
302         {
303             if (alter_comment(param->comments[i], vc,
304                     param->raw, param->escapes, param->changetypes[i]) < 0)
305                 fprintf(stderr, _("Bad comment: \"%s\"\n"), param->comments[i]);
306         }
307 
308         /* build the replacement structure */
309         if (param->commentcount==0) {
310             char *comment;
311 
312             while ((comment = read_line (param->com))) {
313                 if (alter_comment(comment, vc, param->raw, param->escapes, CHANGETYPE_ADD) < 0) {
314                     fprintf (stderr, _("bad comment: \"%s\"\n"),
315                             comment);
316                 }
317                 free (comment);
318             }
319         }
320 
321         /* write out the modified stream */
322         if (vcedit_write(state, param->out) < 0) {
323             fprintf(stderr, _("Failed to write comments to output file: %s\n"),
324                     vcedit_error(state));
325             close_files(param, 0);
326             free_param(param);
327             vcedit_clear(state);
328             return 1;
329         }
330 
331         /* done */
332         vcedit_clear(state);
333 
334         close_files(param, 1);
335         free_param(param);
336         return 0;
337     }
338 
339     /* should never reach this point */
340     fprintf(stderr, _("no action specified\n"));
341     free_param(param);
342     return 1;
343 }
344 
345 /**********
346 
347    Print out the comments from the vorbis structure
348 
349    this version just dumps the raw strings
350    a more elegant version would use vorbis_comment_query()
351 
352 ***********/
353 
print_comments(FILE * out,vorbis_comment * vc,int raw,int escapes)354 void print_comments(FILE *out, vorbis_comment *vc, int raw, int escapes)
355 {
356     int i;
357     char *escaped_value, *decoded_value;
358 
359     for (i = 0; i < vc->comments; i++) {
360         if (escapes) {
361             escaped_value = escape(vc->user_comments[i], vc->comment_lengths[i]);
362         } else {
363             escaped_value = vc->user_comments[i];
364         }
365 
366         if (!raw && utf8_decode(escaped_value, &decoded_value) >= 0) {
367             fprintf(out, "%s\n", decoded_value);
368             free(decoded_value);
369         } else {
370             fprintf(out, "%s\n", escaped_value);
371         }
372 
373         if (escapes) {
374             free(escaped_value);
375         }
376     }
377 }
378 
379 /**********
380 
381    Take a line of the form "TAG=value string", parse it, convert the
382    value to UTF-8, and add or remove it from vc comment block.
383    Error checking is performed (return 0 if OK, negative on error).
384 
385    Note that this assumes a null-terminated string, which may cause
386    problems with > 8-bit character sets!
387 
388 ***********/
389 
alter_comment(char * line,vorbis_comment * vc,int raw,int escapes,int changetype)390 int  alter_comment(char *line, vorbis_comment *vc, int raw, int escapes, int changetype)
391 {
392     char *mark, *value, *utf8_value, *unescaped_value;
393     int unescaped_len;
394     int allow_empty = 0;
395     int is_empty = 0;
396 
397     if (changetype != CHANGETYPE_ADD &&
398         changetype != CHANGETYPE_REMOVE) {
399         return -1;
400     }
401 
402     if (changetype == CHANGETYPE_REMOVE) {
403         allow_empty = 1;
404     }
405 
406     /* strip any terminal newline */
407     {
408         int len = strlen(line);
409         if (line[len-1] == '\n') line[len-1] = '\0';
410     }
411 
412     /* validation: basically, we assume it's a tag
413      * if if has an '=' after valid tag characters,
414      * or if it just contains valid tag characters
415      * and we're allowing empty values. */
416 
417     mark = strchr(line, '=');
418     if (mark == NULL) {
419         if (allow_empty) {
420             mark = line + strlen(line);
421             is_empty = 1;
422         } else {
423             return -1;
424         }
425     }
426 
427     value = line;
428     while (value < mark) {
429         if (*value < 0x20 || *value > 0x7d || *value == 0x3d) return -1;
430         value++;
431     }
432 
433     if (is_empty) {
434         unescaped_value = NULL;
435     } else {
436         /* split the line by turning the '=' in to a null */
437         *mark = '\0';
438         value++;
439 
440         if (raw) {
441             if (!utf8_validate(value)) {
442                 fprintf(stderr, _("'%s' is not valid UTF-8, cannot add\n"), line);
443                 return -1;
444             }
445             utf8_value = value;
446         } else {
447             /* convert the value from the native charset to UTF-8 */
448             if (utf8_encode(value, &utf8_value) < 0) {
449                 fprintf(stderr,
450                         _("Couldn't convert comment to UTF-8, cannot add\n"));
451                 return -1;
452             }
453         }
454 
455         if (escapes) {
456             unescaped_value = unescape(utf8_value, &unescaped_len);
457             /*
458               NOTE: unescaped_len remains unused; to write comments with embeded
459               \0's one would need to access the vc struct directly -- see
460               vorbis_comment_add() in vorbis/lib/info.c for details, but use mem*
461               instead of str*...
462             */
463             if (unescaped_value == NULL) {
464                 fprintf(stderr,
465                         _("Couldn't un-escape comment, cannot add\n"));
466                 if (!raw)
467                     free(utf8_value);
468                 return -1;
469             }
470         } else {
471             unescaped_value = utf8_value;
472         }
473     }
474 
475     /* append or delete the comment and return */
476     switch (changetype) {
477     case CHANGETYPE_ADD:
478         vorbis_comment_add_tag(vc, line, unescaped_value);
479         break;
480     case CHANGETYPE_REMOVE:
481         _vorbis_comment_rm_tag(vc, line, unescaped_value);
482         break;
483     }
484 
485     if (escapes)
486         free(unescaped_value);
487     if (!raw && !is_empty)
488         free(utf8_value);
489     return 0;
490 }
491 
492 
493 /*** Escaping routines. ***/
494 
495 /**********
496 
497    Convert raw comment content to a safely escaped single-line 0-terminated
498    string.  The raw comment can contain null bytes and thus requires an
499    explicit size argument.  The size argument doesn't include a trailing '\0'
500    (the vorbis bitstream doesn't use one).
501 
502    Returns the address of a newly allocated string - caller is responsible to
503    free it.
504 
505 ***********/
506 
escape(const char * from,int fromsize)507 char *escape(const char *from, int fromsize)
508 {
509     /* worst-case allocation, will be trimmed when done */
510     char *to = malloc(fromsize * 2 + 1);
511 
512     char *s;
513     for (s = to; fromsize > 0; fromsize--, from++) {
514         switch (*from) {
515         case '\n':
516             *s++ = '\\';
517             *s++ = 'n';
518             break;
519         case '\r':
520             *s++ = '\\';
521             *s++ = 'r';
522             break;
523         case '\0':
524             *s++ = '\\';
525             *s++ = '0';
526             break;
527         case '\\':
528             *s++ = '\\';
529             *s++ = '\\';
530             break;
531         default:
532             /* normal character */
533             *s++ = *from;
534             break;
535         }
536     }
537 
538     *s++ = '\0';
539     to = realloc(to, s - to);   /* free unused space */
540     return to;
541 }
542 
543 /**********
544 
545    Convert a safely escaped 0-terminated string to raw comment content.  The
546    result can contain null bytes, so the the result's length is written into
547    *tosize.  This size doesn't include a trailing '\0' (the vorbis bitstream
548    doesn't use one) but we do append it for convenience since
549    vorbis_comment_add[_tag]() has a null-terminated interface.
550 
551    Returns the address of a newly allocated string - caller is responsible to
552    free it.  Returns NULL in case of error (if the input is mal-formed).
553 
554 ***********/
555 
unescape(const char * from,int * tosize)556 char *unescape(const char *from, int *tosize)
557 {
558     /* worst-case allocation, will be trimmed when done */
559     char *to = malloc(strlen(from) + 1);
560 
561     char *s;
562     for (s = to; *from != '\0'; ) {
563         if (*from == '\\') {
564             from++;
565             switch (*from++) {
566             case 'n':
567                 *s++ = '\n';
568                 break;
569             case 'r':
570                 *s++ = '\r';
571                 break;
572             case '0':
573                 *s++ = '\0';
574                 break;
575             case '\\':
576                 *s++ = '\\';
577                 break;
578             case '\0':
579                 /* A backslash as the last character of the string is an error. */
580                 /* FALL-THROUGH */
581             default:
582                 /* We consider any unrecognized escape as an error.  This is
583                    good in general and reserves them for future expansion. */
584                 free(to);
585                 return NULL;
586             }
587         } else {
588             /* normal character */
589             *s++ = *from++;
590         }
591     }
592 
593     *tosize = s - to;           /* excluding '\0' */
594 
595     *s++ = '\0';
596     to = realloc(to, s - to);   /* free unused space */
597     return to;
598 }
599 
600 
601 /*** ui-specific routines ***/
602 
603 /**********
604 
605    Print out to usage summary for the cmdline interface (ui)
606 
607 ***********/
608 
609 /* XXX: -q is unused
610   printf (_("  -q, --quiet             Don't display comments while editing\n"));
611 */
612 
usage(void)613 void usage(void)
614 {
615 
616   printf (_("vorbiscomment from %s %s\n"
617             " by the Xiph.Org Foundation (http://www.xiph.org/)\n\n"), PACKAGE, VERSION);
618 
619   printf (_("List or edit comments in Ogg Vorbis files.\n"));
620   printf ("\n");
621 
622   printf (_("Usage: \n"
623         "  vorbiscomment [-Vh]\n"
624         "  vorbiscomment [-lRe] inputfile\n"
625         "  vorbiscomment <-a|-w> [-Re] [-c file] [-t tag] inputfile [outputfile]\n"));
626   printf ("\n");
627 
628   printf (_("Listing options\n"));
629   printf (_("  -l, --list              List the comments (default if no options are given)\n"));
630   printf ("\n");
631 
632   printf (_("Editing options\n"));
633   printf (_("  -a, --append            Update comments\n"));
634   printf (_("  -t \"name=value\", --tag \"name=value\"\n"
635             "                          Specify a comment tag on the commandline\n"));
636   printf (_("  -d \"name[=value]\", --rm \"name[=value]\"\n"
637             "                          Specify a comment tag on the commandline to remove\n"
638             "                          If no value is given all tags with that name are removed\n"
639             "                          This implies -a,\n"));
640   printf (_("  -w, --write             Write comments, replacing the existing ones\n"));
641   printf ("\n");
642 
643   printf (_("Miscellaneous options\n"));
644   printf (_("  -c file, --commentfile file\n"
645             "                          When listing, write comments to the specified file.\n"
646             "                          When editing, read comments from the specified file.\n"));
647   printf (_("  -R, --raw               Read and write comments in UTF-8\n"));
648   printf (_("  -e, --escapes           Use \\n-style escapes to allow multiline comments.\n"));
649   printf ("\n");
650 
651   printf (_("  -h, --help              Display this help\n"));
652   printf (_("  -V, --version           Output version information and exit\n"));
653   printf ("\n");
654 
655   printf (_("If no output file is specified, vorbiscomment will modify the input file. This\n"
656             "is handled via temporary file, such that the input file is not modified if any\n"
657             "errors are encountered during processing.\n"));
658   printf ("\n");
659 
660   printf (_("vorbiscomment handles comments in the format \"name=value\", one per line. By\n"
661             "default, comments are written to stdout when listing, and read from stdin when\n"
662             "editing. Alternatively, a file can be specified with the -c option, or tags\n"
663             "can be given on the commandline with -t \"name=value\". Use of either -c or -t\n"
664             "disables reading from stdin.\n"));
665   printf ("\n");
666 
667   printf (_("Examples:\n"
668             "  vorbiscomment -a in.ogg -c comments.txt\n"
669             "  vorbiscomment -a in.ogg -t \"ARTIST=Some Guy\" -t \"TITLE=A Title\"\n"));
670   printf ("\n");
671 
672   printf (_("NOTE: Raw mode (--raw, -R) will read and write comments in UTF-8 rather than\n"
673         "converting to the user's character set, which is useful in scripts. However,\n"
674         "this is not sufficient for general round-tripping of comments in all cases,\n"
675         "since comments can contain newlines. To handle that, use escaping (-e,\n"
676         "--escape).\n"));
677 }
678 
free_param(param_t * param)679 void free_param(param_t *param)
680 {
681     free(param->infilename);
682     free(param->outfilename);
683     free(param);
684 }
685 
686 /**********
687 
688    allocate and initialize a the parameter struct
689 
690 ***********/
691 
new_param(void)692 param_t *new_param(void)
693 {
694     param_t *param = (param_t *)malloc(sizeof(param_t));
695 
696     /* mode and flags */
697     param->mode = MODE_LIST;
698     param->raw = 0;
699     param->escapes = 0;
700 
701     /* filenames */
702     param->infilename  = NULL;
703     param->outfilename = NULL;
704     param->commentfilename = "-";   /* default */
705 
706     /* file pointers */
707     param->in = param->out = NULL;
708     param->com = NULL;
709     param->tempoutfile=0;
710 
711     /* comments */
712     param->commentcount=0;
713     param->comments=NULL;
714     param->changetypes=NULL;
715 
716     return param;
717 }
718 
719 /**********
720    parse_options()
721 
722    This function takes care of parsing the command line options
723    with getopt() and fills out the param struct with the mode,
724    flags, and filenames.
725 
726 ***********/
727 
parse_options(int argc,char * argv[],param_t * param)728 void parse_options(int argc, char *argv[], param_t *param)
729 {
730     int ret;
731     int option_index = 1;
732 
733     setlocale(LC_ALL, "");
734 
735     while ((ret = getopt_long(argc, argv, "alwhqVc:t:d:Re",
736             long_options, &option_index)) != -1) {
737         switch (ret) {
738             case 0:
739                 fprintf(stderr, _("Internal error parsing command options\n"));
740                 exit(1);
741                 break;
742             case 'l':
743                 param->mode = MODE_LIST;
744                 break;
745             case 'R':
746                 param->raw = 1;
747                 break;
748             case 'e':
749                 param->escapes = 1;
750                 break;
751             case 'w':
752                 param->mode = MODE_WRITE;
753                 break;
754             case 'a':
755                 param->mode = MODE_APPEND;
756                 break;
757             case 'V':
758                 fprintf(stderr, _("vorbiscomment from vorbis-tools " VERSION "\n"));
759                 exit(0);
760                 break;
761             case 'h':
762                 usage();
763                 exit(0);
764                 break;
765             case 'q':
766                 /* set quiet flag: unused */
767                 break;
768             case 'c':
769                 param->commentfilename = strdup(optarg);
770                 break;
771             case 't':
772                 param->comments = realloc(param->comments,
773                         (param->commentcount+1)*sizeof(char *));
774                 param->changetypes = realloc(param->changetypes,
775                         (param->commentcount+1)*sizeof(int));
776                 param->comments[param->commentcount] = strdup(optarg);
777                 param->changetypes[param->commentcount] = CHANGETYPE_ADD;
778                 param->commentcount++;
779                 break;
780             case 'd':
781                 param->comments = realloc(param->comments,
782                         (param->commentcount+1)*sizeof(char *));
783                 param->changetypes = realloc(param->changetypes,
784                         (param->commentcount+1)*sizeof(int));
785                 param->comments[param->commentcount] = strdup(optarg);
786                 param->changetypes[param->commentcount] = CHANGETYPE_REMOVE;
787                 param->commentcount++;
788                 param->mode = MODE_APPEND;
789                 break;
790             default:
791                 usage();
792                 exit(1);
793         }
794     }
795 
796     /* remaining bits must be the filenames */
797     if ((param->mode == MODE_LIST && (argc-optind) != 1) ||
798        ((param->mode == MODE_WRITE || param->mode == MODE_APPEND) &&
799        ((argc-optind) < 1 || (argc-optind) > 2))) {
800             usage();
801             exit(1);
802     }
803 
804     param->infilename = strdup(argv[optind]);
805     if (param->mode == MODE_WRITE || param->mode == MODE_APPEND) {
806         if (argc-optind == 1)
807         {
808             param->tempoutfile = 1;
809             param->outfilename = malloc(strlen(param->infilename)+8);
810             strcpy(param->outfilename, param->infilename);
811             strcat(param->outfilename, ".vctemp");
812         } else {
813             param->outfilename = strdup(argv[optind+1]);
814         }
815     }
816 }
817 
818 /**********
819    open_files()
820 
821    This function takes care of opening the appropriate files
822    based on the mode and filenames in the param structure.
823    A filename of '-' is interpreted as stdin/out.
824 
825    The idea is just to hide the tedious checking so main()
826    is easier to follow as an example.
827 
828 ***********/
829 
open_files(param_t * p)830 void open_files(param_t *p)
831 {
832     /* for all modes, open the input file */
833 
834     if (strncmp(p->infilename,"-",2) == 0) {
835         p->in = stdin;
836     } else {
837         p->in = fopen(p->infilename, "rb");
838     }
839     if (p->in == NULL) {
840         fprintf(stderr,
841             _("Error opening input file '%s'.\n"),
842             p->infilename);
843         exit(1);
844     }
845 
846     if (p->mode == MODE_WRITE || p->mode == MODE_APPEND) {
847 
848         /* open output for write mode */
849         if (!strcmp(p->infilename, p->outfilename)) {
850             fprintf(stderr, _("Input filename may not be the same as output filename\n"));
851             exit(1);
852         }
853 
854         if (strncmp(p->outfilename,"-",2) == 0) {
855             p->out = stdout;
856         } else {
857             p->out = fopen(p->outfilename, "wb");
858         }
859         if (p->out == NULL) {
860             fprintf(stderr,
861                 _("Error opening output file '%s'.\n"),
862                 p->outfilename);
863             exit(1);
864         }
865 
866         /* commentfile is input */
867 
868         if ((p->commentfilename == NULL) ||
869                 (strncmp(p->commentfilename,"-",2) == 0)) {
870             p->com = stdin;
871         } else {
872             p->com = fopen(p->commentfilename, "r");
873         }
874         if (p->com == NULL) {
875             fprintf(stderr,
876                 _("Error opening comment file '%s'.\n"),
877                 p->commentfilename);
878             exit(1);
879         }
880 
881     } else {
882 
883         /* in list mode, commentfile is output */
884 
885         if ((p->commentfilename == NULL) ||
886                 (strncmp(p->commentfilename,"-",2) == 0)) {
887             p->com = stdout;
888         } else {
889             p->com = fopen(p->commentfilename, "w");
890         }
891         if (p->com == NULL) {
892             fprintf(stderr,
893                 _("Error opening comment file '%s'\n"),
894                 p->commentfilename);
895             exit(1);
896         }
897     }
898 
899     /* all done */
900 }
901 
902 /**********
903    close_files()
904 
905    Do some quick clean-up.
906 
907 ***********/
908 
close_files(param_t * p,int output_written)909 void close_files(param_t *p, int output_written)
910 {
911   if (p->in != NULL && p->in != stdin) fclose(p->in);
912   if (p->out != NULL && p->out != stdout) fclose(p->out);
913   if (p->com != NULL && p->com != stdout && p->com != stdin) fclose(p->com);
914 
915   if (p->tempoutfile) {
916 #if HAVE_STAT && HAVE_CHMOD
917     struct stat st;
918     stat (p->infilename, &st);
919 #endif
920 
921     if (output_written) {
922       /* Some platforms fail to rename a file if the new name already
923        * exists, so we need to remove, then rename. How stupid.
924        */
925       if (rename(p->outfilename, p->infilename)) {
926         if (remove(p->infilename))
927           fprintf(stderr, _("Error removing old file %s\n"), p->infilename);
928         else if (rename(p->outfilename, p->infilename))
929           fprintf(stderr, _("Error renaming %s to %s\n"), p->outfilename,
930                   p->infilename);
931       } else {
932 #if HAVE_STAT && HAVE_CHMOD
933         chmod (p->infilename, st.st_mode);
934 #endif
935       }
936     } else {
937       if (remove(p->outfilename)) {
938         fprintf(stderr, _("Error removing erroneous temporary file %s\n"),
939                     p->outfilename);
940       }
941     }
942   }
943 }
944