fixes to remove warnings from gcc-4.0.0
[platform/upstream/flac.git] / src / metaflac / options.c
1 /* metaflac - Command-line FLAC metadata editor
2  * Copyright (C) 2001,2002,2003,2004,2005  Josh Coalson
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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17  */
18
19 #include "options.h"
20 #include "usage.h"
21 #include "utils.h"
22 #include "FLAC/assert.h"
23 #include <ctype.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 /*
29    share__getopt format struct; note we don't use short options so we just
30    set the 'val' field to 0 everywhere to indicate a valid option.
31 */
32 struct share__option long_options_[] = {
33         /* global options */
34         { "preserve-modtime", 0, 0, 0 },
35         { "with-filename", 0, 0, 0 },
36         { "no-filename", 0, 0, 0 },
37         { "no-utf8-convert", 0, 0, 0 },
38         { "dont-use-padding", 0, 0, 0 },
39         { "no-cued-seekpoints", 0, 0, 0 },
40         /* shorthand operations */
41         { "show-md5sum", 0, 0, 0 },
42         { "show-min-blocksize", 0, 0, 0 },
43         { "show-max-blocksize", 0, 0, 0 },
44         { "show-min-framesize", 0, 0, 0 },
45         { "show-max-framesize", 0, 0, 0 },
46         { "show-sample-rate", 0, 0, 0 },
47         { "show-channels", 0, 0, 0 },
48         { "show-bps", 0, 0, 0 },
49         { "show-total-samples", 0, 0, 0 },
50         { "set-md5sum", 1, 0, 0 }, /* undocumented */
51         { "set-min-blocksize", 1, 0, 0 }, /* undocumented */
52         { "set-max-blocksize", 1, 0, 0 }, /* undocumented */
53         { "set-min-framesize", 1, 0, 0 }, /* undocumented */
54         { "set-max-framesize", 1, 0, 0 }, /* undocumented */
55         { "set-sample-rate", 1, 0, 0 }, /* undocumented */
56         { "set-channels", 1, 0, 0 }, /* undocumented */
57         { "set-bps", 1, 0, 0 }, /* undocumented */
58         { "set-total-samples", 1, 0, 0 }, /* undocumented */ /* WATCHOUT: used by test/test_flac.sh on windows */
59         { "show-vendor-tag", 0, 0, 0 }, 
60         { "show-tag", 1, 0, 0 }, 
61         { "remove-all-tags", 0, 0, 0 }, 
62         { "remove-tag", 1, 0, 0 }, 
63         { "remove-first-tag", 1, 0, 0 }, 
64         { "set-tag", 1, 0, 0 }, 
65         { "import-tags-from", 1, 0, 0 }, 
66         { "export-tags-to", 1, 0, 0 }, 
67         { "show-vc-vendor", 0, 0, 0 }, /* deprecated */
68         { "show-vc-field", 1, 0, 0 }, /* deprecated */
69         { "remove-vc-all", 0, 0, 0 }, /* deprecated */
70         { "remove-vc-field", 1, 0, 0 }, /* deprecated */
71         { "remove-vc-firstfield", 1, 0, 0 }, /* deprecated */
72         { "set-vc-field", 1, 0, 0 }, /* deprecated */
73         { "import-vc-from", 1, 0, 0 }, /* deprecated */
74         { "export-vc-to", 1, 0, 0 }, /* deprecated */
75         { "import-cuesheet-from", 1, 0, 0 },
76         { "export-cuesheet-to", 1, 0, 0 },
77         { "add-seekpoint", 1, 0, 0 },
78         { "add-replay-gain", 0, 0, 0 },
79         { "add-padding", 1, 0, 0 },
80         /* major operations */
81         { "help", 0, 0, 0 },
82         { "version", 0, 0, 0 },
83         { "list", 0, 0, 0 },
84         { "append", 0, 0, 0 },
85         { "remove", 0, 0, 0 },
86         { "remove-all", 0, 0, 0 },
87         { "merge-padding", 0, 0, 0 },
88         { "sort-padding", 0, 0, 0 },
89         /* major operation arguments */
90         { "block-number", 1, 0, 0 },
91         { "block-type", 1, 0, 0 },
92         { "except-block-type", 1, 0, 0 },
93         { "data-format", 1, 0, 0 },
94         { "application-data-format", 1, 0, 0 },
95         { "from-file", 1, 0, 0 },
96         {0, 0, 0, 0}
97 };
98
99 static FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options);
100 static void append_new_operation(CommandLineOptions *options, Operation operation);
101 static void append_new_argument(CommandLineOptions *options, Argument argument);
102 static Operation *append_major_operation(CommandLineOptions *options, OperationType type);
103 static Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type);
104 static Operation *find_shorthand_operation(CommandLineOptions *options, OperationType type);
105 static Argument *append_argument(CommandLineOptions *options, ArgumentType type);
106 static FLAC__bool parse_md5(const char *src, FLAC__byte dest[16]);
107 static FLAC__bool parse_uint32(const char *src, FLAC__uint32 *dest);
108 static FLAC__bool parse_uint64(const char *src, FLAC__uint64 *dest);
109 static FLAC__bool parse_filename(const char *src, char **dest);
110 static FLAC__bool parse_vorbis_comment_field_name(const char *field_ref, char **name, const char **violation);
111 static FLAC__bool parse_add_seekpoint(const char *in, char **out, const char **violation);
112 static FLAC__bool parse_add_padding(const char *in, unsigned *out);
113 static FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out);
114 static FLAC__bool parse_block_type(const char *in, Argument_BlockType *out);
115 static FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out);
116 static FLAC__bool parse_application_data_format(const char *in, FLAC__bool *out);
117 static void undocumented_warning(const char *opt);
118
119
120 void init_options(CommandLineOptions *options)
121 {
122         options->preserve_modtime = false;
123
124         /* '2' is a hack to mean "use default if not forced on command line" */
125         FLAC__ASSERT(true != 2);
126         options->prefix_with_filename = 2;
127
128         options->utf8_convert = true;
129         options->use_padding = true;
130         options->cued_seekpoints = true;
131         options->show_long_help = false;
132         options->show_version = false;
133         options->application_data_format_is_hexdump = false;
134
135         options->ops.operations = 0;
136         options->ops.num_operations = 0;
137         options->ops.capacity = 0;
138
139         options->args.arguments = 0;
140         options->args.num_arguments = 0;
141         options->args.capacity = 0;
142
143         options->args.checks.num_shorthand_ops = 0;
144         options->args.checks.num_major_ops = 0;
145         options->args.checks.has_block_type = false;
146         options->args.checks.has_except_block_type = false;
147
148         options->num_files = 0;
149         options->filenames = 0;
150 }
151
152 FLAC__bool parse_options(int argc, char *argv[], CommandLineOptions *options)
153 {
154         int ret;
155         int option_index = 1;
156         FLAC__bool had_error = false;
157
158         while ((ret = share__getopt_long(argc, argv, "", long_options_, &option_index)) != -1) {
159                 switch (ret) {
160                         case 0:
161                                 had_error |= !parse_option(option_index, share__optarg, options);
162                                 break;
163                         case '?':
164                         case ':':
165                                 had_error = true;
166                                 break;
167                         default:
168                                 FLAC__ASSERT(0);
169                                 break;
170                 }
171         }
172
173         if(options->prefix_with_filename == 2)
174                 options->prefix_with_filename = (argc - share__optind > 1);
175
176         if(share__optind >= argc && !options->show_long_help && !options->show_version) {
177                 fprintf(stderr,"ERROR: you must specify at least one FLAC file;\n");
178                 fprintf(stderr,"       metaflac cannot be used as a pipe\n");
179                 had_error = true;
180         }
181
182         options->num_files = argc - share__optind;
183
184         if(options->num_files > 0) {
185                 unsigned i = 0;
186                 if(0 == (options->filenames = (char**)malloc(sizeof(char*) * options->num_files)))
187                         die("out of memory allocating space for file names list");
188                 while(share__optind < argc)
189                         options->filenames[i++] = local_strdup(argv[share__optind++]);
190         }
191
192         if(options->args.checks.num_major_ops > 0) {
193                 if(options->args.checks.num_major_ops > 1) {
194                         fprintf(stderr, "ERROR: you may only specify one major operation at a time\n");
195                         had_error = true;
196                 }
197                 else if(options->args.checks.num_shorthand_ops > 0) {
198                         fprintf(stderr, "ERROR: you may not mix shorthand and major operations\n");
199                         had_error = true;
200                 }
201         }
202
203         /* check for only one FLAC file used with --import-cuesheet-from/--export-cuesheet-to */
204         if((0 != find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM) || 0 != find_shorthand_operation(options, OP__EXPORT_CUESHEET_TO)) && options->num_files > 1) {
205                 fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--import-cuesheet-from' or '--export-cuesheet-to'\n");
206                 had_error = true;
207         }
208
209         if(options->args.checks.has_block_type && options->args.checks.has_except_block_type) {
210                 fprintf(stderr, "ERROR: you may not specify both '--block-type' and '--except-block-type'\n");
211                 had_error = true;
212         }
213
214         if(had_error)
215                 short_usage(0);
216
217         /*
218          * We need to create an OP__ADD_SEEKPOINT operation if there is
219          * not one already,  and --import-cuesheet-from was specified but
220          * --no-cued-seekpoints was not:
221          */
222         if(options->cued_seekpoints) {
223                 Operation *op = find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM);
224                 if(0 != op) {
225                         Operation *op2 = find_shorthand_operation(options, OP__ADD_SEEKPOINT);
226                         if(0 == op2)
227                                 op2 = append_shorthand_operation(options, OP__ADD_SEEKPOINT);
228                         op->argument.import_cuesheet_from.add_seekpoint_link = &(op2->argument.add_seekpoint);
229                 }
230         }
231
232         return !had_error;
233 }
234
235 void free_options(CommandLineOptions *options)
236 {
237         unsigned i;
238         Operation *op;
239         Argument *arg;
240
241         FLAC__ASSERT(0 == options->ops.operations || options->ops.num_operations > 0);
242         FLAC__ASSERT(0 == options->args.arguments || options->args.num_arguments > 0);
243
244         for(i = 0, op = options->ops.operations; i < options->ops.num_operations; i++, op++) {
245                 switch(op->type) {
246                         case OP__SHOW_VC_FIELD:
247                         case OP__REMOVE_VC_FIELD:
248                         case OP__REMOVE_VC_FIRSTFIELD:
249                                 if(0 != op->argument.vc_field_name.value)
250                                         free(op->argument.vc_field_name.value);
251                                 break;
252                         case OP__SET_VC_FIELD:
253                                 if(0 != op->argument.vc_field.field)
254                                         free(op->argument.vc_field.field);
255                                 if(0 != op->argument.vc_field.field_name)
256                                         free(op->argument.vc_field.field_name);
257                                 if(0 != op->argument.vc_field.field_value)
258                                         free(op->argument.vc_field.field_value);
259                                 break;
260                         case OP__IMPORT_VC_FROM:
261                         case OP__EXPORT_VC_TO:
262                         case OP__EXPORT_CUESHEET_TO:
263                                 if(0 != op->argument.filename.value)
264                                         free(op->argument.filename.value);
265                                 break;
266                         case OP__IMPORT_CUESHEET_FROM:
267                                 if(0 != op->argument.import_cuesheet_from.filename)
268                                         free(op->argument.import_cuesheet_from.filename);
269                                 break;
270                         case OP__ADD_SEEKPOINT:
271                                 if(0 != op->argument.add_seekpoint.specification)
272                                         free(op->argument.add_seekpoint.specification);
273                                 break;
274                         default:
275                                 break;
276                 }
277         }
278
279         for(i = 0, arg = options->args.arguments; i < options->args.num_arguments; i++, arg++) {
280                 switch(arg->type) {
281                         case ARG__BLOCK_NUMBER:
282                                 if(0 != arg->value.block_number.entries)
283                                         free(arg->value.block_number.entries);
284                                 break;
285                         case ARG__BLOCK_TYPE:
286                         case ARG__EXCEPT_BLOCK_TYPE:
287                                 if(0 != arg->value.block_type.entries)
288                                         free(arg->value.block_type.entries);
289                                 break;
290                         case ARG__FROM_FILE:
291                                 if(0 != arg->value.from_file.file_name)
292                                         free(arg->value.from_file.file_name);
293                                 break;
294                         default:
295                                 break;
296                 }
297         }
298
299         if(0 != options->ops.operations)
300                 free(options->ops.operations);
301
302         if(0 != options->args.arguments)
303                 free(options->args.arguments);
304
305         if(0 != options->filenames) {
306                 for(i = 0; i < options->num_files; i++) {
307                         if(0 != options->filenames[i])
308                                 free(options->filenames[i]);
309                 }
310                 free(options->filenames);
311         }
312 }
313
314 /*
315  * local routines
316  */
317
318 FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options)
319 {
320         const char *opt = long_options_[option_index].name;
321         Operation *op;
322         Argument *arg;
323         FLAC__bool ok = true;
324
325         if(0 == strcmp(opt, "preserve-modtime")) {
326                 options->preserve_modtime = true;
327         }
328         else if(0 == strcmp(opt, "with-filename")) {
329                 options->prefix_with_filename = true;
330         }
331         else if(0 == strcmp(opt, "no-filename")) {
332                 options->prefix_with_filename = false;
333         }
334         else if(0 == strcmp(opt, "no-utf8-convert")) {
335                 options->utf8_convert = false;
336         }
337         else if(0 == strcmp(opt, "dont-use-padding")) {
338                 options->use_padding = false;
339         }
340         else if(0 == strcmp(opt, "no-cued-seekpoints")) {
341                 options->cued_seekpoints = false;
342         }
343         else if(0 == strcmp(opt, "show-md5sum")) {
344                 (void) append_shorthand_operation(options, OP__SHOW_MD5SUM);
345         }
346         else if(0 == strcmp(opt, "show-min-blocksize")) {
347                 (void) append_shorthand_operation(options, OP__SHOW_MIN_BLOCKSIZE);
348         }
349         else if(0 == strcmp(opt, "show-max-blocksize")) {
350                 (void) append_shorthand_operation(options, OP__SHOW_MAX_BLOCKSIZE);
351         }
352         else if(0 == strcmp(opt, "show-min-framesize")) {
353                 (void) append_shorthand_operation(options, OP__SHOW_MIN_FRAMESIZE);
354         }
355         else if(0 == strcmp(opt, "show-max-framesize")) {
356                 (void) append_shorthand_operation(options, OP__SHOW_MAX_FRAMESIZE);
357         }
358         else if(0 == strcmp(opt, "show-sample-rate")) {
359                 (void) append_shorthand_operation(options, OP__SHOW_SAMPLE_RATE);
360         }
361         else if(0 == strcmp(opt, "show-channels")) {
362                 (void) append_shorthand_operation(options, OP__SHOW_CHANNELS);
363         }
364         else if(0 == strcmp(opt, "show-bps")) {
365                 (void) append_shorthand_operation(options, OP__SHOW_BPS);
366         }
367         else if(0 == strcmp(opt, "show-total-samples")) {
368                 (void) append_shorthand_operation(options, OP__SHOW_TOTAL_SAMPLES);
369         }
370         else if(0 == strcmp(opt, "set-md5sum")) {
371                 op = append_shorthand_operation(options, OP__SET_MD5SUM);
372                 FLAC__ASSERT(0 != option_argument);
373                 if(!parse_md5(option_argument, op->argument.streaminfo_md5.value)) {
374                         fprintf(stderr, "ERROR (--%s): bad MD5 sum\n", opt);
375                         ok = false;
376                 }
377                 else
378                         undocumented_warning(opt);
379         }
380         else if(0 == strcmp(opt, "set-min-blocksize")) {
381                 op = append_shorthand_operation(options, OP__SET_MIN_BLOCKSIZE);
382                 if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value < FLAC__MIN_BLOCK_SIZE || op->argument.streaminfo_uint32.value > FLAC__MAX_BLOCK_SIZE) {
383                         fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BLOCK_SIZE, FLAC__MAX_BLOCK_SIZE);
384                         ok = false;
385                 }
386                 else
387                         undocumented_warning(opt);
388         }
389         else if(0 == strcmp(opt, "set-max-blocksize")) {
390                 op = append_shorthand_operation(options, OP__SET_MAX_BLOCKSIZE);
391                 if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value < FLAC__MIN_BLOCK_SIZE || op->argument.streaminfo_uint32.value > FLAC__MAX_BLOCK_SIZE) {
392                         fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BLOCK_SIZE, FLAC__MAX_BLOCK_SIZE);
393                         ok = false;
394                 }
395                 else
396                         undocumented_warning(opt);
397         }
398         else if(0 == strcmp(opt, "set-min-framesize")) {
399                 op = append_shorthand_operation(options, OP__SET_MIN_FRAMESIZE);
400                 if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value >= (1u<<FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN)) {
401                         fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN);
402                         ok = false;
403                 }
404                 else
405                         undocumented_warning(opt);
406         }
407         else if(0 == strcmp(opt, "set-max-framesize")) {
408                 op = append_shorthand_operation(options, OP__SET_MAX_FRAMESIZE);
409                 if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value >= (1u<<FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN)) {
410                         fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN);
411                         ok = false;
412                 }
413                 else
414                         undocumented_warning(opt);
415         }
416         else if(0 == strcmp(opt, "set-sample-rate")) {
417                 op = append_shorthand_operation(options, OP__SET_SAMPLE_RATE);
418                 if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || !FLAC__format_sample_rate_is_valid(op->argument.streaminfo_uint32.value)) {
419                         fprintf(stderr, "ERROR (--%s): invalid sample rate\n", opt);
420                         ok = false;
421                 }
422                 else
423                         undocumented_warning(opt);
424         }
425         else if(0 == strcmp(opt, "set-channels")) {
426                 op = append_shorthand_operation(options, OP__SET_CHANNELS);
427                 if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value > FLAC__MAX_CHANNELS) {
428                         fprintf(stderr, "ERROR (--%s): value must be > 0 and <= %u\n", opt, FLAC__MAX_CHANNELS);
429                         ok = false;
430                 }
431                 else
432                         undocumented_warning(opt);
433         }
434         else if(0 == strcmp(opt, "set-bps")) {
435                 op = append_shorthand_operation(options, OP__SET_BPS);
436                 if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value < FLAC__MIN_BITS_PER_SAMPLE || op->argument.streaminfo_uint32.value > FLAC__MAX_BITS_PER_SAMPLE) {
437                         fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BITS_PER_SAMPLE, FLAC__MAX_BITS_PER_SAMPLE);
438                         ok = false;
439                 }
440                 else
441                         undocumented_warning(opt);
442         }
443         else if(0 == strcmp(opt, "set-total-samples")) {
444                 op = append_shorthand_operation(options, OP__SET_TOTAL_SAMPLES);
445                 if(!parse_uint64(option_argument, &(op->argument.streaminfo_uint64.value)) || op->argument.streaminfo_uint64.value >= (1u<<FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN)) {
446                         fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN);
447                         ok = false;
448                 }
449                 else
450                         undocumented_warning(opt);
451         }
452         else if(0 == strcmp(opt, "show-vendor-tag") || 0 == strcmp(opt, "show-vc-vendor")) {
453                 if(0 == strcmp(opt, "show-vc-vendor"))
454                         fprintf(stderr, "WARNING: --%s is deprecated, the new name is --show-vendor-tag\n", opt);
455                 (void) append_shorthand_operation(options, OP__SHOW_VC_VENDOR);
456         }
457         else if(0 == strcmp(opt, "show-tag") || 0 == strcmp(opt, "show-vc-field")) {
458                 const char *violation;
459                 if(0 == strcmp(opt, "show-vc-field"))
460                         fprintf(stderr, "WARNING: --%s is deprecated, the new name is --show-tag\n", opt);
461                 op = append_shorthand_operation(options, OP__SHOW_VC_FIELD);
462                 FLAC__ASSERT(0 != option_argument);
463                 if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.vc_field_name.value), &violation)) {
464                         FLAC__ASSERT(0 != violation);
465                         fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
466                         ok = false;
467                 }
468         }
469         else if(0 == strcmp(opt, "remove-all-tags") || 0 == strcmp(opt, "remove-vc-all")) {
470                 if(0 == strcmp(opt, "remove-vc-all"))
471                         fprintf(stderr, "WARNING: --%s is deprecated, the new name is --remove-all-tags\n", opt);
472                 (void) append_shorthand_operation(options, OP__REMOVE_VC_ALL);
473         }
474         else if(0 == strcmp(opt, "remove-tag") || 0 == strcmp(opt, "remove-vc-field")) {
475                 const char *violation;
476                 if(0 == strcmp(opt, "remove-vc-field"))
477                         fprintf(stderr, "WARNING: --%s is deprecated, the new name is --remove-tag\n", opt);
478                 op = append_shorthand_operation(options, OP__REMOVE_VC_FIELD);
479                 FLAC__ASSERT(0 != option_argument);
480                 if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.vc_field_name.value), &violation)) {
481                         FLAC__ASSERT(0 != violation);
482                         fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
483                         ok = false;
484                 }
485         }
486         else if(0 == strcmp(opt, "remove-first-tag") || 0 == strcmp(opt, "remove-vc-firstfield")) {
487                 const char *violation;
488                 if(0 == strcmp(opt, "remove-vc-firstfield"))
489                         fprintf(stderr, "WARNING: --%s is deprecated, the new name is --remove-first-tag\n", opt);
490                 op = append_shorthand_operation(options, OP__REMOVE_VC_FIRSTFIELD);
491                 FLAC__ASSERT(0 != option_argument);
492                 if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.vc_field_name.value), &violation)) {
493                         FLAC__ASSERT(0 != violation);
494                         fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
495                         ok = false;
496                 }
497         }
498         else if(0 == strcmp(opt, "set-tag") || 0 == strcmp(opt, "set-vc-field")) {
499                 const char *violation;
500                 if(0 == strcmp(opt, "set-vc-field"))
501                         fprintf(stderr, "WARNING: --%s is deprecated, the new name is --set-tag\n", opt);
502                 op = append_shorthand_operation(options, OP__SET_VC_FIELD);
503                 FLAC__ASSERT(0 != option_argument);
504                 if(!parse_vorbis_comment_field(option_argument, &(op->argument.vc_field.field), &(op->argument.vc_field.field_name), &(op->argument.vc_field.field_value), &(op->argument.vc_field.field_value_length), &violation)) {
505                         FLAC__ASSERT(0 != violation);
506                         fprintf(stderr, "ERROR (--%s): malformed vorbis comment field \"%s\",\n       %s\n", opt, option_argument, violation);
507                         ok = false;
508                 }
509         }
510         else if(0 == strcmp(opt, "import-tags-from") || 0 == strcmp(opt, "import-vc-from")) {
511                 if(0 == strcmp(opt, "import-vc-from"))
512                         fprintf(stderr, "WARNING: --%s is deprecated, the new name is --import-tags-from\n", opt);
513                 op = append_shorthand_operation(options, OP__IMPORT_VC_FROM);
514                 FLAC__ASSERT(0 != option_argument);
515                 if(!parse_filename(option_argument, &(op->argument.filename.value))) {
516                         fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
517                         ok = false;
518                 }
519         }
520         else if(0 == strcmp(opt, "export-tags-to") || 0 == strcmp(opt, "export-vc-to")) {
521                 if(0 == strcmp(opt, "export-vc-to"))
522                         fprintf(stderr, "WARNING: --%s is deprecated, the new name is --export-tags-to\n", opt);
523                 op = append_shorthand_operation(options, OP__EXPORT_VC_TO);
524                 FLAC__ASSERT(0 != option_argument);
525                 if(!parse_filename(option_argument, &(op->argument.filename.value))) {
526                         fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
527                         ok = false;
528                 }
529         }
530         else if(0 == strcmp(opt, "import-cuesheet-from")) {
531                 if(0 != find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM)) {
532                         fprintf(stderr, "ERROR (--%s): may be specified only once\n", opt);
533                         ok = false;
534                 }
535                 op = append_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM);
536                 FLAC__ASSERT(0 != option_argument);
537                 if(!parse_filename(option_argument, &(op->argument.import_cuesheet_from.filename))) {
538                         fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
539                         ok = false;
540                 }
541         }
542         else if(0 == strcmp(opt, "export-cuesheet-to")) {
543                 op = append_shorthand_operation(options, OP__EXPORT_CUESHEET_TO);
544                 FLAC__ASSERT(0 != option_argument);
545                 if(!parse_filename(option_argument, &(op->argument.filename.value))) {
546                         fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
547                         ok = false;
548                 }
549         }
550         else if(0 == strcmp(opt, "add-seekpoint")) {
551                 const char *violation;
552                 char *spec;
553                 FLAC__ASSERT(0 != option_argument);
554                 if(!parse_add_seekpoint(option_argument, &spec, &violation)) {
555                         FLAC__ASSERT(0 != violation);
556                         fprintf(stderr, "ERROR (--%s): malformed seekpoint specification \"%s\",\n       %s\n", opt, option_argument, violation);
557                         ok = false;
558                 }
559                 op = find_shorthand_operation(options, OP__ADD_SEEKPOINT);
560                 if(0 == op)
561                         op = append_shorthand_operation(options, OP__ADD_SEEKPOINT);
562                 local_strcat(&(op->argument.add_seekpoint.specification), spec);
563                 local_strcat(&(op->argument.add_seekpoint.specification), ";");
564                 free(spec);
565         }
566         else if(0 == strcmp(opt, "add-replay-gain")) {
567                 (void) append_shorthand_operation(options, OP__ADD_REPLAY_GAIN);
568         }
569         else if(0 == strcmp(opt, "add-padding")) {
570                 op = append_shorthand_operation(options, OP__ADD_PADDING);
571                 FLAC__ASSERT(0 != option_argument);
572                 if(!parse_add_padding(option_argument, &(op->argument.add_padding.length))) {
573                         fprintf(stderr, "ERROR (--%s): illegal length \"%s\", length must be >= 0 and < 2^%u\n", opt, option_argument, FLAC__STREAM_METADATA_LENGTH_LEN);
574                         ok = false;
575                 }
576         }
577         else if(0 == strcmp(opt, "help")) {
578                 options->show_long_help = true;
579         }
580         else if(0 == strcmp(opt, "version")) {
581                 options->show_version = true;
582         }
583         else if(0 == strcmp(opt, "list")) {
584                 (void) append_major_operation(options, OP__LIST);
585         }
586         else if(0 == strcmp(opt, "append")) {
587                 (void) append_major_operation(options, OP__APPEND);
588         }
589         else if(0 == strcmp(opt, "remove")) {
590                 (void) append_major_operation(options, OP__REMOVE);
591         }
592         else if(0 == strcmp(opt, "remove-all")) {
593                 (void) append_major_operation(options, OP__REMOVE_ALL);
594         }
595         else if(0 == strcmp(opt, "merge-padding")) {
596                 (void) append_major_operation(options, OP__MERGE_PADDING);
597         }
598         else if(0 == strcmp(opt, "sort-padding")) {
599                 (void) append_major_operation(options, OP__SORT_PADDING);
600         }
601         else if(0 == strcmp(opt, "block-number")) {
602                 arg = append_argument(options, ARG__BLOCK_NUMBER);
603                 FLAC__ASSERT(0 != option_argument);
604                 if(!parse_block_number(option_argument, &(arg->value.block_number))) {
605                         fprintf(stderr, "ERROR: malformed block number specification \"%s\"\n", option_argument);
606                         ok = false;
607                 }
608         }
609         else if(0 == strcmp(opt, "block-type")) {
610                 arg = append_argument(options, ARG__BLOCK_TYPE);
611                 FLAC__ASSERT(0 != option_argument);
612                 if(!parse_block_type(option_argument, &(arg->value.block_type))) {
613                         fprintf(stderr, "ERROR (--%s): malformed block type specification \"%s\"\n", opt, option_argument);
614                         ok = false;
615                 }
616                 options->args.checks.has_block_type = true;
617         }
618         else if(0 == strcmp(opt, "except-block-type")) {
619                 arg = append_argument(options, ARG__EXCEPT_BLOCK_TYPE);
620                 FLAC__ASSERT(0 != option_argument);
621                 if(!parse_block_type(option_argument, &(arg->value.block_type))) {
622                         fprintf(stderr, "ERROR (--%s): malformed block type specification \"%s\"\n", opt, option_argument);
623                         ok = false;
624                 }
625                 options->args.checks.has_except_block_type = true;
626         }
627         else if(0 == strcmp(opt, "data-format")) {
628                 arg = append_argument(options, ARG__DATA_FORMAT);
629                 FLAC__ASSERT(0 != option_argument);
630                 if(!parse_data_format(option_argument, &(arg->value.data_format))) {
631                         fprintf(stderr, "ERROR (--%s): illegal data format \"%s\"\n", opt, option_argument);
632                         ok = false;
633                 }
634         }
635         else if(0 == strcmp(opt, "application-data-format")) {
636                 FLAC__ASSERT(0 != option_argument);
637                 if(!parse_application_data_format(option_argument, &(options->application_data_format_is_hexdump))) {
638                         fprintf(stderr, "ERROR (--%s): illegal application data format \"%s\"\n", opt, option_argument);
639                         ok = false;
640                 }
641         }
642         else if(0 == strcmp(opt, "from-file")) {
643                 arg = append_argument(options, ARG__FROM_FILE);
644                 FLAC__ASSERT(0 != option_argument);
645                 arg->value.from_file.file_name = local_strdup(option_argument);
646         }
647         else {
648                 FLAC__ASSERT(0);
649         }
650
651         return ok;
652 }
653
654 void append_new_operation(CommandLineOptions *options, Operation operation)
655 {
656         if(options->ops.capacity == 0) {
657                 options->ops.capacity = 50;
658                 if(0 == (options->ops.operations = (Operation*)malloc(sizeof(Operation) * options->ops.capacity)))
659                         die("out of memory allocating space for option list");
660                 memset(options->ops.operations, 0, sizeof(Operation) * options->ops.capacity);
661         }
662         if(options->ops.capacity <= options->ops.num_operations) {
663                 unsigned original_capacity = options->ops.capacity;
664                 options->ops.capacity *= 4;
665                 if(0 == (options->ops.operations = (Operation*)realloc(options->ops.operations, sizeof(Operation) * options->ops.capacity)))
666                         die("out of memory allocating space for option list");
667                 memset(options->ops.operations + original_capacity, 0, sizeof(Operation) * (options->ops.capacity - original_capacity));
668         }
669
670         options->ops.operations[options->ops.num_operations++] = operation;
671 }
672
673 void append_new_argument(CommandLineOptions *options, Argument argument)
674 {
675         if(options->args.capacity == 0) {
676                 options->args.capacity = 50;
677                 if(0 == (options->args.arguments = (Argument*)malloc(sizeof(Argument) * options->args.capacity)))
678                         die("out of memory allocating space for option list");
679                 memset(options->args.arguments, 0, sizeof(Argument) * options->args.capacity);
680         }
681         if(options->args.capacity <= options->args.num_arguments) {
682                 unsigned original_capacity = options->args.capacity;
683                 options->args.capacity *= 4;
684                 if(0 == (options->args.arguments = (Argument*)realloc(options->args.arguments, sizeof(Argument) * options->args.capacity)))
685                         die("out of memory allocating space for option list");
686                 memset(options->args.arguments + original_capacity, 0, sizeof(Argument) * (options->args.capacity - original_capacity));
687         }
688
689         options->args.arguments[options->args.num_arguments++] = argument;
690 }
691
692 Operation *append_major_operation(CommandLineOptions *options, OperationType type)
693 {
694         Operation op;
695         memset(&op, 0, sizeof(op));
696         op.type = type;
697         append_new_operation(options, op);
698         options->args.checks.num_major_ops++;
699         return options->ops.operations + (options->ops.num_operations - 1);
700 }
701
702 Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type)
703 {
704         Operation op;
705         memset(&op, 0, sizeof(op));
706         op.type = type;
707         append_new_operation(options, op);
708         options->args.checks.num_shorthand_ops++;
709         return options->ops.operations + (options->ops.num_operations - 1);
710 }
711
712 Operation *find_shorthand_operation(CommandLineOptions *options, OperationType type)
713 {
714         unsigned i;
715         for(i = 0; i < options->ops.num_operations; i++)
716                 if(options->ops.operations[i].type == type)
717                         return &options->ops.operations[i];
718         return 0;
719 }
720
721 Argument *append_argument(CommandLineOptions *options, ArgumentType type)
722 {
723         Argument arg;
724         memset(&arg, 0, sizeof(arg));
725         arg.type = type;
726         append_new_argument(options, arg);
727         return options->args.arguments + (options->args.num_arguments - 1);
728 }
729
730 FLAC__bool parse_md5(const char *src, FLAC__byte dest[16])
731 {
732         unsigned i, d;
733         int c;
734         FLAC__ASSERT(0 != src);
735         if(strlen(src) != 32)
736                 return false;
737         /* strtoul() accepts negative numbers which we do not want, so we do it the hard way */
738         for(i = 0; i < 16; i++) {
739                 c = (int)(*src++);
740                 if(isdigit(c))
741                         d = (unsigned)(c - '0');
742                 else if(c >= 'a' && c <= 'f')
743                         d = (unsigned)(c - 'a') + 10u;
744                 else if(c >= 'A' && c <= 'F')
745                         d = (unsigned)(c - 'A') + 10u;
746                 else
747                         return false;
748                 d <<= 4;
749                 c = (int)(*src++);
750                 if(isdigit(c))
751                         d |= (unsigned)(c - '0');
752                 else if(c >= 'a' && c <= 'f')
753                         d |= (unsigned)(c - 'a') + 10u;
754                 else if(c >= 'A' && c <= 'F')
755                         d |= (unsigned)(c - 'A') + 10u;
756                 else
757                         return false;
758                 dest[i] = (FLAC__byte)d;
759         }
760         return true;
761 }
762
763 FLAC__bool parse_uint32(const char *src, FLAC__uint32 *dest)
764 {
765         FLAC__ASSERT(0 != src);
766         if(strlen(src) == 0 || strspn(src, "0123456789") != strlen(src))
767                 return false;
768         *dest = strtoul(src, 0, 10);
769         return true;
770 }
771
772 /* There's no stroull() in MSVC6 so we just write a specialized one */
773 static FLAC__uint64 local__strtoull(const char *src)
774 {
775         FLAC__uint64 ret = 0;
776         int c;
777         FLAC__ASSERT(0 != src);
778         while(0 != (c = *src++)) {
779                 c -= '0';
780                 if(c >= 0 && c <= 9)
781                         ret = (ret * 10) + c;
782                 else
783                         break;
784         }
785         return ret;
786 }
787
788 FLAC__bool parse_uint64(const char *src, FLAC__uint64 *dest)
789 {
790         FLAC__ASSERT(0 != src);
791         if(strlen(src) == 0 || strspn(src, "0123456789") != strlen(src))
792                 return false;
793         *dest = local__strtoull(src);
794         return true;
795 }
796
797 FLAC__bool parse_filename(const char *src, char **dest)
798 {
799         if(0 == src || strlen(src) == 0)
800                 return false;
801         *dest = strdup(src);
802         return true;
803 }
804
805 FLAC__bool parse_vorbis_comment_field_name(const char *field_ref, char **name, const char **violation)
806 {
807         static const char * const violations[] = {
808                 "field name contains invalid character"
809         };
810
811         char *q, *s;
812
813         s = local_strdup(field_ref);
814
815         for(q = s; *q; q++) {
816                 if(*q < 0x20 || *q > 0x7d || *q == 0x3d) {
817                         free(s);
818                         *violation = violations[0];
819                         return false;
820                 }
821         }
822
823         *name = s;
824
825         return true;
826 }
827
828 FLAC__bool parse_add_seekpoint(const char *in, char **out, const char **violation)
829 {
830         static const char *garbled_ = "garbled specification";
831         const unsigned n = strlen(in);
832
833         FLAC__ASSERT(0 != in);
834         FLAC__ASSERT(0 != out);
835
836         if(n == 0) {
837                 *violation = "specification is empty";
838                 return false;
839         }
840
841         if(n > strspn(in, "0123456789.Xsx")) {
842                 *violation = "specification contains invalid character";
843                 return false;
844         }
845
846         if(in[n-1] == 'X') {
847                 if(n > 1) {
848                         *violation = garbled_;
849                         return false;
850                 }
851         }
852         else if(in[n-1] == 's') {
853                 if(n-1 > strspn(in, "0123456789.")) {
854                         *violation = garbled_;
855                         return false;
856                 }
857         }
858         else if(in[n-1] == 'x') {
859                 if(n-1 > strspn(in, "0123456789")) {
860                         *violation = garbled_;
861                         return false;
862                 }
863         }
864         else {
865                 if(n > strspn(in, "0123456789")) {
866                         *violation = garbled_;
867                         return false;
868                 }
869         }
870
871         *out = local_strdup(in);
872         return true;
873 }
874
875 FLAC__bool parse_add_padding(const char *in, unsigned *out)
876 {
877         FLAC__ASSERT(0 != in);
878         FLAC__ASSERT(0 != out);
879         *out = (unsigned)strtoul(in, 0, 10);
880         return *out < (1u << FLAC__STREAM_METADATA_LENGTH_LEN);
881 }
882
883 FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out)
884 {
885         char *p, *q, *s, *end;
886         long i;
887         unsigned entry;
888
889         if(*in == '\0')
890                 return false;
891
892         s = local_strdup(in);
893
894         /* first count the entries */
895         for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(++p, ','))
896                 ;
897
898         /* make space */
899         FLAC__ASSERT(out->num_entries > 0);
900         if(0 == (out->entries = (unsigned*)malloc(sizeof(unsigned) * out->num_entries)))
901                 die("out of memory allocating space for option list");
902
903         /* load 'em up */
904         entry = 0;
905         q = s;
906         while(q) {
907                 FLAC__ASSERT(entry < out->num_entries);
908                 if(0 != (p = strchr(q, ',')))
909                         *p++ = '\0';
910                 if(!isdigit((int)(*q)) || (i = strtol(q, &end, 10)) < 0 || *end) {
911                         free(s);
912                         return false;
913                 }
914                 out->entries[entry++] = (unsigned)i;
915                 q = p;
916         }
917         FLAC__ASSERT(entry == out->num_entries);
918
919         free(s);
920         return true;
921 }
922
923 FLAC__bool parse_block_type(const char *in, Argument_BlockType *out)
924 {
925         char *p, *q, *r, *s;
926         unsigned entry;
927
928         if(*in == '\0')
929                 return false;
930
931         s = local_strdup(in);
932
933         /* first count the entries */
934         for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(++p, ','))
935                 ;
936
937         /* make space */
938         FLAC__ASSERT(out->num_entries > 0);
939         if(0 == (out->entries = (Argument_BlockTypeEntry*)malloc(sizeof(Argument_BlockTypeEntry) * out->num_entries)))
940                 die("out of memory allocating space for option list");
941
942         /* load 'em up */
943         entry = 0;
944         q = s;
945         while(q) {
946                 FLAC__ASSERT(entry < out->num_entries);
947                 if(0 != (p = strchr(q, ',')))
948                         *p++ = 0;
949                 r = strchr(q, ':');
950                 if(r)
951                         *r++ = '\0';
952                 if(0 != r && 0 != strcmp(q, "APPLICATION")) {
953                         free(s);
954                         return false;
955                 }
956                 if(0 == strcmp(q, "STREAMINFO")) {
957                         out->entries[entry++].type = FLAC__METADATA_TYPE_STREAMINFO;
958                 }
959                 else if(0 == strcmp(q, "PADDING")) {
960                         out->entries[entry++].type = FLAC__METADATA_TYPE_PADDING;
961                 }
962                 else if(0 == strcmp(q, "APPLICATION")) {
963                         out->entries[entry].type = FLAC__METADATA_TYPE_APPLICATION;
964                         out->entries[entry].filter_application_by_id = (0 != r);
965                         if(0 != r) {
966                                 if(strlen(r) == 4) {
967                                         strcpy(out->entries[entry].application_id, r);
968                                 }
969                                 else if(strlen(r) == 10 && strncmp(r, "0x", 2) == 0 && strspn(r+2, "0123456789ABCDEFabcdef") == 8) {
970                                         FLAC__uint32 x = strtoul(r+2, 0, 16);
971                                         out->entries[entry].application_id[3] = (FLAC__byte)(x & 0xff);
972                                         out->entries[entry].application_id[2] = (FLAC__byte)((x>>=8) & 0xff);
973                                         out->entries[entry].application_id[1] = (FLAC__byte)((x>>=8) & 0xff);
974                                         out->entries[entry].application_id[0] = (FLAC__byte)((x>>=8) & 0xff);
975                                 }
976                                 else {
977                                         free(s);
978                                         return false;
979                                 }
980                         }
981                         entry++;
982                 }
983                 else if(0 == strcmp(q, "SEEKTABLE")) {
984                         out->entries[entry++].type = FLAC__METADATA_TYPE_SEEKTABLE;
985                 }
986                 else if(0 == strcmp(q, "VORBIS_COMMENT")) {
987                         out->entries[entry++].type = FLAC__METADATA_TYPE_VORBIS_COMMENT;
988                 }
989                 else if(0 == strcmp(q, "CUESHEET")) {
990                         out->entries[entry++].type = FLAC__METADATA_TYPE_CUESHEET;
991                 }
992                 else {
993                         free(s);
994                         return false;
995                 }
996                 q = p;
997         }
998         FLAC__ASSERT(entry == out->num_entries);
999
1000         free(s);
1001         return true;
1002 }
1003
1004 FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out)
1005 {
1006         if(0 == strcmp(in, "binary"))
1007                 out->is_binary = true;
1008         else if(0 == strcmp(in, "text"))
1009                 out->is_binary = false;
1010         else
1011                 return false;
1012         return true;
1013 }
1014
1015 FLAC__bool parse_application_data_format(const char *in, FLAC__bool *out)
1016 {
1017         if(0 == strcmp(in, "hexdump"))
1018                 *out = true;
1019         else if(0 == strcmp(in, "text"))
1020                 *out = false;
1021         else
1022                 return false;
1023         return true;
1024 }
1025
1026 void undocumented_warning(const char *opt)
1027 {
1028         fprintf(stderr, "WARNING: undocmented option --%s should be used with caution,\n         only for repairing a damaged STREAMINFO block\n", opt);
1029 }