From e959a5de455aeaf0ea532b27a04f1f9df4d567eb Mon Sep 17 00:00:00 2001 From: Josh Coalson Date: Thu, 23 May 2002 05:21:19 +0000 Subject: [PATCH] flesh out the option processing --- src/metaflac/main.c | 521 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 443 insertions(+), 78 deletions(-) diff --git a/src/metaflac/main.c b/src/metaflac/main.c index 13396ff..81596d0 100644 --- a/src/metaflac/main.c +++ b/src/metaflac/main.c @@ -25,11 +25,13 @@ more powerful operations yet to add: # include #endif +#include "FLAC/assert.h" #include "FLAC/metadata.h" #include #include #include #include +#include #if HAVE_GETOPT_LONG # include @@ -37,43 +39,46 @@ more powerful operations yet to add: # include "share/getopt.h" #endif -/* getopt format struct; note we don't use short options so we just - set the 'val' field to 1 everywhere to indicate a valid option. */ +/* + getopt format struct; note we don't use short options so we just + set the 'val' field to 0 everywhere to indicate a valid option. +*/ static struct option long_options_[] = { /* global options */ - { "preserve-modtime", 0, 0, 1 }, - { "with-filename", 0, 0, 1 }, - { "no-filename", 0, 0, 1 }, - { "dont-use-padding", 0, 0, 1 }, + { "preserve-modtime", 0, 0, 0 }, + { "with-filename", 0, 0, 0 }, + { "no-filename", 0, 0, 0 }, + { "dont-use-padding", 0, 0, 0 }, /* shorthand operations */ - { "show-md5sum", 0, 0, 1 }, - { "show-min-blocksize", 0, 0, 1 }, - { "show-max-blocksize", 0, 0, 1 }, - { "show-min-framesize", 0, 0, 1 }, - { "show-max-framesize", 0, 0, 1 }, - { "show-sample-rate", 0, 0, 1 }, - { "show-channels", 0, 0, 1 }, - { "show-bps", 0, 0, 1 }, - { "show-total-samples", 0, 0, 1 }, - { "show-vc-vendor", 0, 0, 1 }, - { "show-vc-field", 1, 0, 1 }, - { "remove-vc-all", 0, 0, 1 }, - { "remove-vc-field", 1, 0, 1 }, - { "remove-vc-firstfield", 1, 0, 1 }, - { "set-vc-field", 1, 0, 1 }, + { "show-md5sum", 0, 0, 0 }, + { "show-min-blocksize", 0, 0, 0 }, + { "show-max-blocksize", 0, 0, 0 }, + { "show-min-framesize", 0, 0, 0 }, + { "show-max-framesize", 0, 0, 0 }, + { "show-sample-rate", 0, 0, 0 }, + { "show-channels", 0, 0, 0 }, + { "show-bps", 0, 0, 0 }, + { "show-total-samples", 0, 0, 0 }, + { "show-vc-vendor", 0, 0, 0 }, + { "show-vc-field", 1, 0, 0 }, + { "remove-vc-all", 0, 0, 0 }, + { "remove-vc-field", 1, 0, 0 }, + { "remove-vc-firstfield", 1, 0, 0 }, + { "set-vc-field", 1, 0, 0 }, /* major operations */ - { "list", 0, 0, 1 }, - { "append", 0, 0, 1 }, - { "remove", 0, 0, 1 }, - { "remove-all", 0, 0, 1 }, - { "merge-padding", 0, 0, 1 }, - { "sort-padding", 0, 0, 1 }, - { "block-number", 1, 0, 1 }, - { "block-type", 1, 0, 1 }, - { "except-block-type", 1, 0, 1 }, - { "data-format", 1, 0, 1 }, - { "application-data-format", 1, 0, 1 }, - { "from-file", 1, 0, 1 }, + { "help", 0, 0, 0 }, + { "list", 0, 0, 0 }, + { "append", 0, 0, 0 }, + { "remove", 0, 0, 0 }, + { "remove-all", 0, 0, 0 }, + { "merge-padding", 0, 0, 0 }, + { "sort-padding", 0, 0, 0 }, + { "block-number", 1, 0, 0 }, + { "block-type", 1, 0, 0 }, + { "except-block-type", 1, 0, 0 }, + { "data-format", 1, 0, 0 }, + { "application-data-format", 1, 0, 0 }, + { "from-file", 1, 0, 0 }, {0, 0, 0, 0} }; @@ -89,9 +94,11 @@ typedef enum { OP__SHOW_TOTAL_SAMPLES, OP__SHOW_VC_VENDOR, OP__SHOW_VC_FIELD, + OP__REMOVE_VC_ALL, OP__REMOVE_VC_FIELD, OP__REMOVE_VC_FIRSTFIELD, OP__SET_VC_FIELD, + OP__HELP, OP__LIST, OP__APPEND, OP__REMOVE, @@ -107,15 +114,19 @@ typedef enum { } OperationType; typedef struct { - int dummy; + char *field_name; } Argument_VcFieldName; typedef struct { - int dummy; + char *field_name; + /* according to the vorbis spec, field values can contain \0 so simple C strings are not enough here */ + unsigned field_value_length; + char *field_value; } Argument_VcField; typedef struct { - int dummy; + unsigned num_entries; + unsigned *entries; } Argument_BlockNumber; typedef struct { @@ -123,19 +134,19 @@ typedef struct { } Argument_BlockType; typedef struct { - int dummy; + FLAC__bool is_binary; } Argument_DataFormat; typedef struct { - int dummy; + FLAC__bool is_hexdump; } Argument_ApplicationDataFormat; typedef struct { - int dummy; + char *file_name; } Argument_FromFile; typedef struct { - OperationType operation; + OperationType type; union { Argument_VcFieldName show_vc_field; Argument_VcFieldName remove_vc_field; @@ -154,73 +165,330 @@ typedef struct { FLAC__bool preserve_modtime; FLAC__bool prefix_with_filename; FLAC__bool use_padding; - Operation *operations; struct { - FLAC__bool has_shorthand_op; - FLAC__bool has_major_op; + unsigned num_shorthand_ops; + unsigned num_major_ops; FLAC__bool has_block_type; FLAC__bool has_except_block_type; } checks; + struct { + Operation *operations; + unsigned num_operations; + unsigned capacity; + } ops; } CommandLineOptions; -static void parse_options(int argc, char *argv[], CommandLineOptions *options); +static void init_options(CommandLineOptions *options); +static FLAC__bool parse_options(int argc, char *argv[], CommandLineOptions *options); +static FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options); +static void free_options(CommandLineOptions *options); +static void append_operation(CommandLineOptions *options, Operation operation); +static Operation *append_major_operation(CommandLineOptions *options, OperationType type); +static Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type); static int short_usage(const char *message, ...); static int long_usage(const char *message, ...); static void hexdump(const FLAC__byte *buf, unsigned bytes, const char *indent); +static char *local_strdup(const char *source); +static FLAC__bool parse_vorbis_comment_field(const char *field, char **name, char **value, unsigned *length); +static FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out); +static FLAC__bool parse_block_type(const char *in, Argument_BlockType *out); +static FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out); +static FLAC__bool parse_application_data_format(const char *in, Argument_ApplicationDataFormat *out); int main(int argc, char *argv[]) { - CommandLineOptions o; - parse_options(argc, argv, &o); - return 3; + CommandLineOptions options; + int ret; + + init_options(&options); + + ret = !parse_options(argc, argv, &options); + + free_options(&options); + + return ret; } -void parse_options(int argc, char *argv[], CommandLineOptions *options) +void init_options(CommandLineOptions *options) +{ + options->preserve_modtime = false; + + /* hack to mean "use default if not forced on command line" */ + FLAC__ASSERT(true != 2); + options->prefix_with_filename = 2; + + options->use_padding = true; + options->checks.num_shorthand_ops = 0; + options->checks.num_major_ops = 0; + options->checks.has_block_type = false; + options->checks.has_except_block_type = false; + + options->ops.operations = 0; + options->ops.num_operations = 0; + options->ops.capacity = 0; +} + +FLAC__bool parse_options(int argc, char *argv[], CommandLineOptions *options) { int ret; int option_index = 1; + FLAC__bool had_error = false; while ((ret = getopt_long(argc, argv, "", long_options_, &option_index)) != -1) { switch (ret) { case 0: - fprintf(stderr, "Internal error parsing command options\n"); - exit(1); - break; - case 1: + had_error |= !parse_option(option_index, optarg, options); break; case '?': - break; case ':': - break; + short_usage(0); + return false; + had_error = true; default: - /*@@@usage();*/ - exit(1); + FLAC__ASSERT(0); } } -#if 0 - /* remaining bits must be the filenames */ - if((param->mode == MODE_LIST && (argc-optind) != 1) || - ((param->mode == MODE_WRITE || param->mode == MODE_APPEND) && - ((argc-optind) < 1 || (argc-optind) > 2))) { - usage(); - exit(1); - } + if(options->prefix_with_filename == 2) + options->prefix_with_filename = (argc - optind > 1); - param->infilename = strdup(argv[optind]); - if (param->mode == MODE_WRITE || param->mode == MODE_APPEND) - { - if(argc-optind == 1) - { - param->tempoutfile = 1; - param->outfilename = malloc(strlen(param->infilename)+8); - strcpy(param->outfilename, param->infilename); - strcat(param->outfilename, ".vctemp"); - } - else - param->outfilename = strdup(argv[optind+1]); - } -#endif + if(optind < argc) { + while(optind < argc) + printf("%s ", argv[optind++]); + printf("\n"); + } + + return had_error; +} + +FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options) +{ + const char *opt = long_options_[option_index].name; + Operation *op; + FLAC__bool ret = true; + + if(0 == strcmp(opt, "preserve-modtime")) { + options->preserve_modtime = true; + } + else if(0 == strcmp(opt, "with-filename")) { + options->prefix_with_filename = true; + } + else if(0 == strcmp(opt, "no-filename")) { + options->prefix_with_filename = false; + } + else if(0 == strcmp(opt, "dont-use-padding")) { + options->use_padding = false; + } + else if(0 == strcmp(opt, "show-md5sum")) { + (void) append_shorthand_operation(options, OP__SHOW_MD5SUM); + } + else if(0 == strcmp(opt, "show-min-blocksize")) { + (void) append_shorthand_operation(options, OP__SHOW_MIN_BLOCKSIZE); + } + else if(0 == strcmp(opt, "show-max-blocksize")) { + (void) append_shorthand_operation(options, OP__SHOW_MAX_BLOCKSIZE); + } + else if(0 == strcmp(opt, "show-min-framesize")) { + (void) append_shorthand_operation(options, OP__SHOW_MIN_FRAMESIZE); + } + else if(0 == strcmp(opt, "show-max-framesize")) { + (void) append_shorthand_operation(options, OP__SHOW_MAX_FRAMESIZE); + } + else if(0 == strcmp(opt, "show-sample-rate")) { + (void) append_shorthand_operation(options, OP__SHOW_SAMPLE_RATE); + } + else if(0 == strcmp(opt, "show-channels")) { + (void) append_shorthand_operation(options, OP__SHOW_CHANNELS); + } + else if(0 == strcmp(opt, "show-bps")) { + (void) append_shorthand_operation(options, OP__SHOW_BPS); + } + else if(0 == strcmp(opt, "show-total-samples")) { + (void) append_shorthand_operation(options, OP__SHOW_TOTAL_SAMPLES); + } + else if(0 == strcmp(opt, "show-vc-vendor")) { + (void) append_shorthand_operation(options, OP__SHOW_VC_VENDOR); + } + else if(0 == strcmp(opt, "show-vc-field")) { + op = append_shorthand_operation(options, OP__SHOW_VC_FIELD); + FLAC__ASSERT(0 != option_argument); + op->argument.show_vc_field.field_name = local_strdup(option_argument); + } + else if(0 == strcmp(opt, "remove-vc-all")) { + (void) append_shorthand_operation(options, OP__REMOVE_VC_ALL); + } + else if(0 == strcmp(opt, "remove-vc-field")) { + op = append_shorthand_operation(options, OP__REMOVE_VC_FIELD); + FLAC__ASSERT(0 != option_argument); + op->argument.remove_vc_field.field_name = local_strdup(option_argument); + } + else if(0 == strcmp(opt, "remove-vc-firstfield")) { + op = append_shorthand_operation(options, OP__REMOVE_VC_FIRSTFIELD); + FLAC__ASSERT(0 != option_argument); + op->argument.remove_vc_firstfield.field_name = local_strdup(option_argument); + } + else if(0 == strcmp(opt, "set-vc-field")) { + op = append_shorthand_operation(options, OP__SET_VC_FIELD); + FLAC__ASSERT(0 != option_argument); + if(!parse_vorbis_comment_field(option_argument, &(op->argument.set_vc_field.field_name), &(op->argument.set_vc_field.field_value), &(op->argument.set_vc_field.field_value_length))) { + fprintf(stderr, "ERROR: malformed vorbis comment field \"%s\"\n", option_argument); + ret = false; + } + } + else if(0 == strcmp(opt, "help")) { + (void) append_major_operation(options, OP__HELP); + } + else if(0 == strcmp(opt, "list")) { + (void) append_major_operation(options, OP__LIST); + } + else if(0 == strcmp(opt, "append")) { + (void) append_major_operation(options, OP__APPEND); + } + else if(0 == strcmp(opt, "remove")) { + (void) append_major_operation(options, OP__REMOVE); + } + else if(0 == strcmp(opt, "remove-all")) { + (void) append_major_operation(options, OP__REMOVE_ALL); + } + else if(0 == strcmp(opt, "merge-padding")) { + (void) append_major_operation(options, OP__MERGE_PADDING); + } + else if(0 == strcmp(opt, "sort-padding")) { + (void) append_major_operation(options, OP__SORT_PADDING); + } + else if(0 == strcmp(opt, "block-number")) { + op = append_major_operation(options, OP__BLOCK_NUMBER); + FLAC__ASSERT(0 != option_argument); + if(!parse_block_number(option_argument, &(op->argument.block_number))) { + fprintf(stderr, "ERROR: malformed block number specification \"%s\"\n", option_argument); + ret = false; + } + } + else if(0 == strcmp(opt, "block-type")) { + op = append_major_operation(options, OP__BLOCK_TYPE); + FLAC__ASSERT(0 != option_argument); + if(!parse_block_type(option_argument, &(op->argument.block_type))) { + fprintf(stderr, "ERROR: malformed block type specification \"%s\"\n", option_argument); + ret = false; + } + } + else if(0 == strcmp(opt, "except-block-type")) { + op = append_major_operation(options, OP__EXCEPT_BLOCK_TYPE); + FLAC__ASSERT(0 != option_argument); + if(!parse_block_type(option_argument, &(op->argument.except_block_type))) { + fprintf(stderr, "ERROR: malformed block type specification \"%s\"\n", option_argument); + ret = false; + } + } + else if(0 == strcmp(opt, "data-format")) { + op = append_major_operation(options, OP__DATA_FORMAT); + FLAC__ASSERT(0 != option_argument); + if(!parse_data_format(option_argument, &(op->argument.data_format))) { + fprintf(stderr, "ERROR: illegal data format \"%s\"\n", option_argument); + ret = false; + } + } + else if(0 == strcmp(opt, "application-data-format")) { + op = append_major_operation(options, OP__APPLICATION_DATA_FORMAT); + FLAC__ASSERT(0 != option_argument); + if(!parse_application_data_format(option_argument, &(op->argument.application_data_format))) { + fprintf(stderr, "ERROR: illegal application data format \"%s\"\n", option_argument); + ret = false; + } + } + else if(0 == strcmp(opt, "from-file")) { + op = append_major_operation(options, OP__FROM_FILE); + FLAC__ASSERT(0 != option_argument); + op->argument.from_file.file_name = local_strdup(option_argument); + } + else { + FLAC__ASSERT(0); + } + + return ret; +} + +void free_options(CommandLineOptions *options) +{ + unsigned i; + Operation *op; + + FLAC__ASSERT(0 == options->ops.operations || options->ops.num_operations > 0); + + for(i = 0; i < options->ops.num_operations; i++) { + op = options->ops.operations + i; + switch(op->type) { + case OP__SHOW_VC_FIELD: + case OP__REMOVE_VC_FIELD: + case OP__REMOVE_VC_FIRSTFIELD: + FLAC__ASSERT(0 != op->argument.show_vc_field.field_name); + free(op->argument.show_vc_field.field_name); + break; + case OP__SET_VC_FIELD: + FLAC__ASSERT(0 != op->argument.set_vc_field.field_name); + free(op->argument.set_vc_field.field_name); + if(0 != op->argument.set_vc_field.field_value) + free(op->argument.set_vc_field.field_value); + break; + case OP__BLOCK_NUMBER: + /*@@@*/ + break; + case OP__BLOCK_TYPE: + case OP__EXCEPT_BLOCK_TYPE: + /*@@@*/ + break; + case OP__FROM_FILE: + FLAC__ASSERT(0 != op->argument.from_file.file_name); + free(op->argument.from_file.file_name); + break; + default: + break; + } + } + + if(0 != options->ops.operations) + free(options->ops.operations); +} + +void append_operation(CommandLineOptions *options, Operation operation) +{ + if(options->ops.capacity == 0) { + options->ops.capacity = 50; + if(0 == (options->ops.operations = malloc(sizeof(Operation) * options->ops.capacity))) { + fprintf(stderr, "ERROR: out of memory allocating space for option list\n"); + exit(1); + } + memset(options->ops.operations, 0, sizeof(Operation) * options->ops.capacity); + } + if(options->ops.capacity <= options->ops.num_operations) { + unsigned original_capacity = options->ops.capacity; + options->ops.capacity *= 4; + if(0 == (options->ops.operations = realloc(options->ops.operations, sizeof(Operation) * options->ops.capacity))) { + fprintf(stderr, "ERROR: out of memory allocating space for option list\n"); + exit(1); + } + memset(options->ops.operations + original_capacity, 0, sizeof(Operation) * (options->ops.capacity - original_capacity)); + } + + options->ops.operations[options->ops.num_operations++] = operation; +} + +Operation *append_major_operation(CommandLineOptions *options, OperationType type) +{ + Operation op; + op.type = type; + append_operation(options, op); + options->checks.num_major_ops++; + return options->ops.operations + (options->ops.num_operations - 1); +} + +Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type) +{ + Operation op; + op.type = type; + append_operation(options, op); + options->checks.num_shorthand_ops++; + return options->ops.operations + (options->ops.num_operations - 1); } static void usage_header(FILE *out) @@ -461,3 +729,100 @@ void hexdump(const FLAC__byte *buf, unsigned bytes, const char *indent) b += 16; } } + +char *local_strdup(const char *source) +{ + char *ret; + FLAC__ASSERT(0 != source); + if(0 == (ret = strdup(source))) { + fprintf(stderr, "ERROR: out of memory during strdup()\n"); + exit(1); + } + return ret; +} + +FLAC__bool parse_vorbis_comment_field(const char *field, char **name, char **value, unsigned *length) +{ + char *p, *s = local_strdup(field); + + if(0 == (p = strchr(s, '='))) { + free(s); + return false; + } + *p++ = '\0'; + *name = local_strdup(s); + *value = local_strdup(p); + *length = strlen(p); + + free(s); + return true; +} + +FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out) +{ + char *p, *q, *s; + int i; + unsigned entry; + + if(*in == '\0') + return false; + + s = local_strdup(in); + + /* first count the entries */ + for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(s, ',')) + ; + + /* make space */ + FLAC__ASSERT(out->num_entries > 0); + if(0 == (out->entries = malloc(sizeof(unsigned) * out->num_entries))) { + fprintf(stderr, "ERROR: out of memory allocating space for option list\n"); + exit(1); + } + + /* load 'em up */ + entry = 0; + q = s; + while(q) { + if(0 != (p = strchr(q, ','))) + *p++ = 0; + if(!isdigit((int)(*q)) || (i = atoi(q)) < 0) { + free(s); + return false; + } + FLAC__ASSERT(entry < out->num_entries); + out->entries[entry++] = (unsigned)i; + q = p; + } + FLAC__ASSERT(entry == out->num_entries); + + free(s); + return true; +} + +FLAC__bool parse_block_type(const char *in, Argument_BlockType *out) +{ + return true; +} + +FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out) +{ + if(0 == strcmp(in, "binary")) + out->is_binary = true; + else if(0 == strcmp(in, "text")) + out->is_binary = false; + else + return false; + return true; +} + +FLAC__bool parse_application_data_format(const char *in, Argument_ApplicationDataFormat *out) +{ + if(0 == strcmp(in, "hexdump")) + out->is_hexdump = true; + else if(0 == strcmp(in, "text")) + out->is_hexdump = false; + else + return false; + return true; +} -- 2.7.4