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