From: Alberto Leiva Popper Date: Mon, 18 Feb 2019 18:09:49 +0000 (-0600) Subject: Configuration framework review X-Git-Tag: v0.0.2~86 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=293acd80609b187b2f2f97b09885a1a844a33611;p=thirdparty%2FFORT-validator.git Configuration framework review - Integrate the string data type to the configuration framework properly (They are no longer being handled as an exception) - Integrate the print and free operations to the the framework (The rpki_config object is no longer printed and freed manually) - Add short option support (eg. `-t` instead of `--tal`) - Add --help, --version and --usage handlers - Add a bunch of in-code documentation to most configuration fields --- diff --git a/src/config.c b/src/config.c index 92f31e2c..5b59a29c 100644 --- a/src/config.c +++ b/src/config.c @@ -1,23 +1,28 @@ #include "config.h" +#include #include +#include #include #include #include -#include "common.h" #include "log.h" #include "toml_handler.h" -#define OPT_FIELD_ARRAY_LEN(array) ARRAY_LEN(array) - 1 +#define FOREACH_OPTION(groups, grp, opt, type) \ + for (grp = groups; grp->name != NULL; grp++) \ + for (opt = grp->options; opt->id != 0; opt++) \ + if ((opt->availability == 0) || \ + (opt->availability & type)) struct rpki_config { /* tal file path*/ - char *tal; + char const *tal; /* Local repository path */ - char *local_repository; - /* Enable rsync downloads */ - bool enable_rsync; + char const *local_repository; + /* Disable rsync downloads */ + bool disable_rsync; /* Shuffle uris in tal */ bool shuffle_uris; /* @@ -27,549 +32,509 @@ struct rpki_config { unsigned int maximum_certificate_depth; }; -static int parse_bool(struct option_field *, char *, void *); -static int parse_u_int(struct option_field *, char *, void *); -static void _free_rpki_config(struct rpki_config *); +static void print_usage(FILE *stream); + +void print_bool(struct group_fields const *, struct option_field const *, + void *); +void print_u_int(struct group_fields const *, struct option_field const *, + void *); +void print_string(struct group_fields const *, struct option_field const *, + void *); +static int parse_bool(struct option_field const *, char const *, void *); +static int parse_u_int(struct option_field const *, char const *, void *); +static int parse_string(struct option_field const *, char const *, void *); +static int handle_help(struct option_field const *, char *); +static int handle_usage(struct option_field const *, char *); +static int handle_version(struct option_field const *, char *); +static int handle_toml(struct option_field const *, char *); +static void free_string(void *); + +static char const *program_name; static struct rpki_config rpki_config; -static struct global_type gt_bool = { - .id = GTI_BOOL, - .name = "Boolean", +static const struct global_type gt_bool = { + .has_arg = no_argument, .size = sizeof(bool), + .print = print_bool, .parse = parse_bool, - .candidates = "true|false", + .arg_doc = "true|false", }; -static struct global_type gt_string = { - .id = GTI_STRING, - .name = "String", -}; - -static struct global_type gt_u_int = { - .id = GTI_U_INT, - .name = "Unsigned Int", +static const struct global_type gt_u_int = { + .has_arg = required_argument, .size = sizeof(unsigned int), + .print = print_u_int, .parse = parse_u_int, - .candidates = "NUM", + .arg_doc = "", }; -static struct option manual_long_opts[] = { - { - .name = "configuration-file", - .has_arg = required_argument, - .flag = NULL, - .val = 'f', - }, +static const struct global_type gt_string = { + .has_arg = required_argument, + .size = sizeof(char *), + .print = print_string, + .parse = parse_string, + .free = free_string, + .arg_doc = "", }; +/** + * An option that takes no arguments, is not correlated to any rpki_config + * fields, and is entirely managed by its handler function. + */ +static const struct global_type gt_callback = { + .has_arg = no_argument, +}; -static struct option_field global_fields[] = { +static const struct option_field global_fields[] = { { + .id = 'h', + .name = "help", + .type = >_callback, + .handler = handle_help, + .doc = "Give this help list", + .availability = AVAILABILITY_GETOPT, + }, { + .id = 1000, + .name = "usage", + .type = >_callback, + .handler = handle_usage, + .doc = "Give a short usage message", + .availability = AVAILABILITY_GETOPT, + }, { + .id = 'V', + .name = "version", + .type = >_callback, + .handler = handle_version, + .doc = "Print program version", + .availability = AVAILABILITY_GETOPT, + }, { + .id = 'f', + .name = "configuration-file", + .type = >_string, + .handler = handle_toml, + .doc = "TOML file the configuration will be read from.", + .arg_doc = "", + .availability = AVAILABILITY_GETOPT, + }, { + .id = 'r', .name = "local-repository", .type = >_string, - .doc = "Local repository path.", .offset = offsetof(struct rpki_config, local_repository), - .has_arg = required_argument, - .short_opt = 0, - .required = true, - .candidates = "path", + .doc = "Local repository path.", + .arg_doc = "", }, { - .name = "enable-rsync", + .id = 1001, + .name = "disable-rsync", .type = >_bool, + .offset = offsetof(struct rpki_config, disable_rsync), .doc = "Enable or disable rsync downloads.", - .offset = offsetof(struct rpki_config, enable_rsync), - .has_arg = optional_argument, - .short_opt = 0, - .required = false, }, - { NULL }, + { 0 }, }; -static struct option_field tal_fields[] = { +static const struct option_field tal_fields[] = { { + .id = 't', .name = "tal", .type = >_string, - .doc = "TAL file path", .offset = offsetof(struct rpki_config, tal), - .has_arg = required_argument, - .short_opt = 0, - .required = true, - .candidates = "file", + .doc = "TAL file path", + .arg_doc = "", }, { + .id = 2000, .name = "shuffle-uris", .type = >_bool, - .doc = "Shuffle URIs in the TAL.", .offset = offsetof(struct rpki_config, shuffle_uris), - .has_arg = optional_argument, - .short_opt = 0, - .required = false, + .doc = "Shuffle URIs in the TAL.", }, { + .id = 2001, .name = "maximum-certificate-depth", .type = >_u_int, - .doc = "Prevents arbitrarily long paths and loops.", .offset = offsetof(struct rpki_config, maximum_certificate_depth), - .has_arg = required_argument, - .short_opt = 0, + .doc = "Prevents arbitrarily long paths and loops.", .min = 1, /** * It cannot be UINT_MAX, because then the actual number will * overflow and will never be bigger than this. */ - .max = UINT_MAX - 1, - .required = false, + .max = UINT_MAX - 1, }, - { NULL }, + { 0 }, }; - - -static struct group_fields fields[] = { +static const struct group_fields groups[] = { { - .group_name = "root", + .name = "root", .options = global_fields, - .options_len = OPT_FIELD_ARRAY_LEN(global_fields), - }, - { - .group_name = "tal", + }, { + .name = "tal", .options = tal_fields, - .options_len = OPT_FIELD_ARRAY_LEN(tal_fields), }, { NULL }, }; -static int -str_to_bool(const char *str, bool *bool_out) +/** + * Returns true if @field is the descriptor of one of the members of the + * struct rpki_config structure, false otherwise. + * (Alternatively: Returns true if @field->offset is valid, false otherwise.) + */ +static bool +is_rpki_config_field(struct option_field const *field) { - if (strcasecmp(str, "true") == 0 || strcasecmp(str, "1") == 0 || - strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0) { - *bool_out = true; - return 0; - } + return field->handler == NULL; +} - if (strcasecmp(str, "false") == 0 || strcasecmp(str, "0") == 0 || - strcasecmp(str, "no") == 0 || strcasecmp(str, "off") == 0) { - *bool_out = false; - return 0; - } +static void * +get_rpki_config_field(struct option_field const *field) +{ + return ((unsigned char *) &rpki_config) + field->offset; +} + +void +print_bool(struct group_fields const *group, struct option_field const *field, + void *_value) +{ + bool *value = _value; + pr_info("%s.%s: %s", group->name, field->name, + (*value) ? "true" : "false"); +} + +void +print_u_int(struct group_fields const *group, struct option_field const *field, + void *value) +{ + pr_info("%s.%s: %u", group->name, field->name, + *((unsigned int *) value)); +} - return pr_err("Cannot parse '%s' as a bool " - "(true|false|1|0|yes|no|on|off).", str); +void +print_string(struct group_fields const *group, struct option_field const *field, + void *value) +{ + pr_info("%s.%s: %s", group->name, field->name, *((char **) value)); } static int -parse_bool(struct option_field *field, char *str, void *result) +parse_bool(struct option_field const *field, char const *str, void *result) { bool *value = result; - switch (field->has_arg) { - case no_argument: + if (str == NULL) { *value = true; return 0; - break; - case optional_argument: - if (str == NULL) { - *value = true; - break; - } - /* FALLTHROUGH */ - case required_argument: - return str_to_bool(str, result); } - return 0; + if (strcmp(str, "true") == 0) { + *value = true; + return 0; + } + + if (strcmp(str, "false") == 0) { + *value = false; + return 0; + } + + return pr_err("Cannot parse '%s' as a bool (true|false).", str); } -static int str_to_ull(const char *str, char **endptr, - const unsigned long long int min, - const unsigned long long int max, - unsigned long long int *result) +static int +parse_u_int(struct option_field const *field, char const *str, void *_result) { - unsigned long long int parsed; + unsigned long parsed; + int *result; + + if (field->type->has_arg != required_argument || str == NULL) { + return pr_err("Integer options ('%s' in this case) require an argument.", + field->name); + } errno = 0; - parsed = strtoull(str, endptr, 10); + parsed = strtoul(str, NULL, 10); if (errno) return pr_errno(errno, "'%s' is not an unsigned integer", str); - if (parsed < min || max < parsed) - return pr_err("'%s' is out of bounds (%llu-%llu).", str, min, - max); + if (parsed < field->min || field->max < parsed) { + return pr_err("'%lu' is out of bounds (%u-%u).", parsed, + field->min, field->max); + } + result = _result; *result = parsed; return 0; } static int -str_to_unsigned_int(const char *str, unsigned int *out, unsigned int min, - unsigned int max) +parse_string(struct option_field const *field, char const *str, void *_result) { - unsigned long long int result = 0; - int error; + char **result = _result; - error = str_to_ull(str, NULL, min, max, &result); + if (field->type->has_arg != required_argument || str == NULL) { + return pr_err("String options ('%s' in this case) require an argument.", + field->name); + } - *out = result; - return error; + /* tomlc99 frees @str early, so work with a copy. */ + *result = strdup(str); + return ((*result) != NULL) ? 0 : pr_enomem(); } static int -parse_u_int(struct option_field *field, char *str, void *result) +handle_help(struct option_field const *field, char *arg) { - unsigned int *value = result; - - if (str == NULL) - return pr_err("String cannot be NULL"); - - return str_to_unsigned_int(str, value, field->min, field->max); + print_usage(stdout); + exit(0); } static int -construct_options(struct args_flag **flags, struct option **long_options, - int *flags_len) +handle_usage(struct option_field const *field, char *arg) { - struct option_field *tmp; - struct group_fields *tmp_all_fields; - struct args_flag *result_flags; - struct option *result_options; - unsigned int total_len, i, result_idx, - extra_long_options; - - tmp_all_fields = fields; - total_len = 0; - while (tmp_all_fields->group_name != NULL) { - total_len += tmp_all_fields->options_len; - tmp_all_fields += 1; - } - - /* +1 NULL end, means end of array. */ - result_flags = calloc(total_len + 1, sizeof(struct args_flag)); - if (result_flags == NULL) - return pr_enomem(); - - extra_long_options = ARRAY_LEN(manual_long_opts); - - result_options = calloc(total_len - + extra_long_options /* extra options handled manually. */ - + 1, /* long options must end with zeros */ - sizeof(struct option)); - if (result_options == NULL) { - free(result_flags); - return pr_enomem(); - } - - result_idx = 0; - tmp_all_fields = fields; - while (tmp_all_fields->group_name != NULL) { - tmp = tmp_all_fields->options; - while(tmp->name != NULL) { - result_flags[result_idx].field = tmp; - - result_options[result_idx].name = tmp->name; - result_options[result_idx].has_arg = tmp->has_arg; - result_options[result_idx].val = tmp->short_opt; - result_options[result_idx].flag = NULL; - - result_idx++; - tmp += 1; - } - tmp_all_fields += 1; - } - - for (i = 0; i < extra_long_options; i++) { - result_options[result_idx].name = manual_long_opts[i].name; - result_options[result_idx].has_arg = manual_long_opts[i].has_arg; - result_options[result_idx].val = manual_long_opts[i].val; - result_options[result_idx].flag = manual_long_opts[i].flag; - - result_idx++; - } - - *flags = result_flags; - *flags_len = total_len; - *long_options = result_options; - - return 0; + print_usage(stdout); + exit(0); } static int -set_string(void **field, char *str) +handle_version(struct option_field const *field, char *arg) { - char *result; - - /* malloc the string, because if the string comes from TOML_HANDLER, - * the string is freed later in that function. */ - result = malloc(strlen(str) + 1); - if (result == NULL) - return pr_enomem(); - - strcpy(result, str); - *field = result; + printf("0.0.1\n"); + exit(0); +} - return 0; +static int +handle_toml(struct option_field const *field, char *file_name) +{ + return set_config_from_file(file_name); } static void -set_config_param_for_string(void **field, void **config_param) +free_string(void *_string) { - *config_param = *field; + char **string = _string; + free(*string); } -int -handle_option(struct rpki_config *config, struct option_field *field, char *str) +static bool +is_alphanumeric(int chara) { - void *config_param; - int error = 0; - - /** - * TODO Should we use a switch case? - * In order to avoid: - * warning: pointer of type ‘void *’ used in arithmetic - * [-Wpointer-arith] - * https://stackoverflow.com/questions/23357442/ - * dynamically-access-member-variable-of-a-structure - */ - config_param = config; - config_param += field->offset; + return ('a' <= chara && chara <= 'z') + || ('A' <= chara && chara <= 'Z') + || ('0' <= chara && chara <= '9'); +} - if (field->type == >_string) { - error = set_string(config_param, str); - if (error) - return error; - set_config_param_for_string(config_param, &config_param); - } else if (field->type->parse != NULL){ - error = field->type->parse(field, str, config_param); +/** + * "struct option" is the array that getopt expects. + * "struct args_flag" is our option metadata. + */ +static int +construct_getopt_options(struct option **_long_opts, char **_short_opts) +{ + struct group_fields const *group; + struct option_field const *opt; + struct option *long_opts; + char *short_opts; + unsigned int total_long_options; + unsigned int total_short_options; + + total_long_options = 0; + total_short_options = 0; + FOREACH_OPTION(groups, group, opt, AVAILABILITY_GETOPT) { + total_long_options++; + if (is_alphanumeric(opt->id)) { + total_short_options++; + if (opt->type->has_arg != no_argument) + total_short_options++; /* ":" */ + } } - if (error) - return error; + /* +1 NULL end, means end of array. */ + long_opts = calloc(total_long_options + 1, sizeof(struct option)); + if (long_opts == NULL) + return pr_enomem(); + short_opts = malloc(total_short_options + 1); + if (short_opts == NULL) { + free(long_opts); + return pr_enomem(); + } - if (field->validate != NULL) - error = field->validate(field, config_param); - else if (field->type->validate != NULL) - error = field->type->validate(field, config_param); + *_long_opts = long_opts; + *_short_opts = short_opts; + + FOREACH_OPTION(groups, group, opt, AVAILABILITY_GETOPT) { + long_opts->name = opt->name; + long_opts->has_arg = opt->type->has_arg; + long_opts->flag = NULL; + long_opts->val = opt->id; + long_opts++; + + if (is_alphanumeric(opt->id)) { + *short_opts = opt->id; + short_opts++; + if (opt->type->has_arg != no_argument) { + *short_opts = ':'; + short_opts++; + } + } + } - return error; + *short_opts = '\0'; + return 0; } int -check_missing_flags(struct args_flag *flag) +parse_option(struct option_field const *field, char const *str) { - char *candidate = NULL; + return field->type->parse(field, str, + get_rpki_config_field(field)); +} - if (flag->is_set) - return 0; - if (!flag->field->required) - return 0; +static void +print_config(void) +{ + struct group_fields const *grp; + struct option_field const *opt; - printf("Missing param: %s", flag->field->name); - switch (flag->field->has_arg) { - case no_argument: - break; - case optional_argument: - if (flag->field->candidates != NULL) - candidate = flag->field->candidates; - else if (flag->field->type->candidates != NULL) - candidate = flag->field->type->candidates; - if (candidate != NULL) - printf("[=%s]", candidate); - break; - case required_argument: - if (flag->field->candidates != NULL) - candidate = flag->field->candidates; - else if (flag->field->type->candidates != NULL) - candidate = flag->field->type->candidates; - if (candidate != NULL) - printf(" <%s>", candidate); - break; - default: - break; - } + pr_info("Configuration {"); + pr_indent_add(); - printf("\n"); + FOREACH_OPTION(groups, grp, opt, 0xFFFF) + if (is_rpki_config_field(opt) && opt->type->print != NULL) + opt->type->print(grp, opt, get_rpki_config_field(opt)); - return -ENOENT; + pr_indent_rm(); + pr_info("}"); } -static void -print_config(struct rpki_config *config) +static int +set_default_values(void) { - pr_debug("Program configuration"); - pr_debug_add("{"); - pr_debug("%s: %s", "local-repository", config->local_repository); - pr_debug("%s: %s", "tal", config->tal); - pr_debug("%s: %s", "enable-rsync", - config->enable_rsync ? "true" : "false"); - pr_debug("%s: %s", "tal.shuffle-uris", - config->shuffle_uris ? "true" : "false"); - pr_debug("%s: %u", "tal.maximum-certificate-depth", - config->maximum_certificate_depth); - pr_debug_rm("}"); + rpki_config.tal = NULL; + rpki_config.local_repository = strdup("repository/"); + if (rpki_config.local_repository == NULL) + return pr_enomem(); + rpki_config.disable_rsync = false; + rpki_config.shuffle_uris = false; + rpki_config.maximum_certificate_depth = 32; + return 0; } -static void -set_default_values(struct rpki_config *config) +static int +validate_config(void) { - config->enable_rsync = true; - config->local_repository = NULL; - config->maximum_certificate_depth = 32; - config->shuffle_uris = false; - config->tal = NULL; + return (rpki_config.tal != NULL) + ? 0 + : pr_err("The TAL file (--tal) is mandatory."); } -static void _print_usage(bool only_required) +static void +print_usage(FILE *stream) { - struct option_field *tmp; - struct group_fields *tmp_all_fields; - char *candidates; - bool required; - - get_group_fields(&tmp_all_fields); - - while (tmp_all_fields->group_name != NULL) { - tmp = tmp_all_fields->options; - while(tmp->name != NULL) { - required = tmp->required; - - if (only_required != required) { - tmp += 1; - continue; - } + struct group_fields const *group; + struct option_field const *option; + char const *arg_doc; - fprintf(stderr, " "); + fprintf(stream, "Usage: %s\n", program_name); - if (!required) - fprintf(stderr, "["); - fprintf(stderr, "--%s", tmp->name); + FOREACH_OPTION(groups, group, option, AVAILABILITY_GETOPT) { + fprintf(stream, "\t["); + fprintf(stream, "--%s", option->name); - if (tmp->candidates != NULL) - candidates = tmp->candidates; - else if (tmp->type->candidates != NULL) - candidates = tmp->type->candidates; - else - candidates = NULL; + if (option->arg_doc != NULL) + arg_doc = option->arg_doc; + else if (option->type->arg_doc != NULL) + arg_doc = option->type->arg_doc; + else + arg_doc = NULL; - switch (tmp->has_arg) { - case no_argument: - break; - case optional_argument: - if(candidates == NULL) - break; - fprintf(stderr, "=<%s>", candidates); - break; - case required_argument: - if(candidates == NULL) - break; - fprintf(stderr, " <%s>", candidates); - break; - default: + switch (option->type->has_arg) { + case no_argument: + break; + case optional_argument: + case required_argument: + if(arg_doc == NULL) break; - } - if (!required) - fprintf(stderr, "]"); - tmp += 1; + fprintf(stream, "=%s", arg_doc); + break; } - tmp_all_fields += 1; - } + fprintf(stream, "]\n"); + } } -void -print_usage(char *progname) +static int +handle_opt(int opt) { - /* - * TODO openbsd styleguide said use "getprogname" to set the progam - * name. - */ - fprintf(stderr, "usage: %s", progname); - - fprintf(stderr, " [-f ] " - "[--configuration-file ]"); - - _print_usage(true); - _print_usage(false); + struct group_fields const *group; + struct option_field const *option; + + FOREACH_OPTION(groups, group, option, AVAILABILITY_GETOPT) { + if (option->id == opt) { + return is_rpki_config_field(option) + ? parse_option(option, optarg) + : option->handler(option, optarg); + } + } - fprintf(stderr, "\n"); - exit(1); + pr_err("Unrecognized option: %d", opt); + return -ESRCH; } int handle_flags_config(int argc, char **argv) { - struct args_flag *flags; - struct option *long_options; - struct rpki_config config; - int opt, indexptr, flags_len, error; - - set_default_values(&config); + struct option *long_opts; + char *short_opts; + int opt; + int error; - long_options = NULL; - flags = NULL; - flags_len = 0; + program_name = argv[0]; + error = set_default_values(); + if (error) + return error; - error = construct_options(&flags, &long_options, &flags_len); + long_opts = NULL; + short_opts = NULL; + error = construct_getopt_options(&long_opts, &short_opts); if (error) - return error; /* Error msg already printed. */ - - while ((opt = getopt_long(argc, argv, "f:", long_options, &indexptr)) - != -1) - switch (opt) { - case 0: - flags[indexptr].is_set = true; - error = handle_option(&config, flags[indexptr].field, - optarg); - if (error) { - print_usage(argv[0]); - goto end; - } - break; - case 'f': - error = set_config_from_file(optarg, &config, flags); - if (error) { - print_usage(argv[0]); - goto end; - } - break; - default: - print_usage(argv[0]); - error = -EINVAL; - goto end; - } + goto end; /* Error msg already printed. */ - for (indexptr = 0; indexptr < flags_len; indexptr++) - error |= check_missing_flags(&flags[indexptr]); + while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) + != -1) { + error = handle_opt(opt); + if (error) + goto end; + } - if (error) { - print_usage(argv[0]); + /* + * This triggers when the user runs something like + * `rpki-validator disable-rsync` instead of + * `rpki-validator --disable-rsync`. + * This program does not have unflagged payload. + */ + if (optind < argc) { + error = pr_err("I don't know what '%s' is.", argv[optind]); goto end; } - print_config(&config); - config_set(&config); + error = validate_config(); end: if (error) - _free_rpki_config(&config); + free_rpki_config(); + else + print_config(); - free(flags); - free(long_options); + free(long_opts); + free(short_opts); return error; } void -get_group_fields(struct group_fields **group_fields) +get_group_fields(struct group_fields const **group_fields) { - if (group_fields) - *group_fields = fields; -} - -void -config_set(struct rpki_config *new) -{ - rpki_config = *new; + *group_fields = groups; } char const * @@ -587,7 +552,7 @@ config_get_local_repository(void) bool config_get_enable_rsync(void) { - return rpki_config.enable_rsync; + return !rpki_config.disable_rsync; } bool @@ -602,18 +567,13 @@ config_get_max_cert_depth(void) return rpki_config.maximum_certificate_depth; } -static void -_free_rpki_config(struct rpki_config *config) -{ - if (config->local_repository != NULL) - free(config->local_repository); - - if (config->tal != NULL) - free(config->tal); -} - void free_rpki_config(void) { - _free_rpki_config(&rpki_config); + struct group_fields const *group; + struct option_field const *option; + + FOREACH_OPTION(groups, group, option, 0xFFFF) + if (is_rpki_config_field(option) && option->type->free != NULL) + option->type->free(get_rpki_config_field(option)); } diff --git a/src/config.h b/src/config.h index 14fac274..71fccefd 100644 --- a/src/config.h +++ b/src/config.h @@ -6,62 +6,106 @@ struct rpki_config; +struct group_fields; struct option_field; -typedef enum global_type_id { - GTI_BOOL, - GTI_STRING, - GTI_U_INT, -} global_type_id; - -typedef void (*print_function)(void *, bool); -typedef int (*parse_function)(struct option_field *, char *, void *); -/* This function does not need to validate type->size. */ -typedef int (*validate_function)(struct option_field *, void *); - -struct args_flag { - struct option_field *field; - bool is_set; -}; +typedef void (*print_function)(struct group_fields const *, + struct option_field const *, void *); +typedef int (*parse_function)(struct option_field const *, char const *, + void *); +typedef int (*handler_function)(struct option_field const *, char *); struct global_type { - global_type_id id; - const char *name; + /** Same as struct option.has_arg. Mandatory. */ + int has_arg; + /** + * Number of bytes this data type uses in the rpki_config structure. + * Optional. Defaults to zero, obviously. + */ size_t size; + + /** + * Prints this data type during the print_config() function. + * Optional. + */ print_function print; + /** + * Convers from string to this data type. + * If the option's handler is not NULL, this is optional. + */ parse_function parse; - validate_function validate; - char *candidates; + /** + * Function that will release this data type. + * If the option's handler is not NULL, this is optional. + */ + void (*free)(void *); + + /** + * Descriptor of this type's payload. Printed in usage documentation. + * For example, in `--tal=`, @arg_doc is "". + * The type might have no payload, so this is optional. + */ + char const *arg_doc; }; +/** This option can be set from the command line. */ +#define AVAILABILITY_GETOPT (1 << 0) +/** This option can be set from the TOML file. */ +#define AVAILABILITY_TOML (1 << 1) + struct option_field { - char *name; /* This being NULL means the end of the array. */ - struct global_type *type; - const char *doc; + /* + * Must be zero, alphanumeric or >= 1000. + * If zero, signals the end of the array. + * If alphanumeric, it's the short option name character. + * Otherwise it's just a non-printable identifier. + * Mandatory. + */ + int id; + /** + * For example, if the option name is '--potato', then @name is + * "potato". + * Mandatory. + */ + char const *name; + + /** Data type. Mandatory. */ + struct global_type const *type; + /** + * Number of bytes between the beginning of the struct rpki_config + * and the position where this option is stored. + * Only relevant when @handler == NULL. + */ size_t offset; - int has_arg; - char short_opt; - unsigned long min; - unsigned long max; - print_function print; /* Overrides type->print. */ - validate_function validate; /* Overrides type->validate. */ - char *candidates; /* Overrides type->candidates. */ - bool required; + /** Overrides @type->parser and @offset. Optional. */ + handler_function handler; + + /** + * Explanation of the field, for user consumption. + * Mandatory. + */ + const char *doc; + /** Overrides type->arg_doc. Optional. */ + char const *arg_doc; + /** + * AVAILABILITY_* flags above. + * Default availability is everywhere. + * Optional. + */ + int availability; + unsigned int min; + unsigned int max; }; struct group_fields { - char *group_name; - struct option_field *options; - unsigned int options_len; + char const *name; + struct option_field const *options; }; -void print_usage(char *progname); -int handle_option(struct rpki_config *, struct option_field *, char *); +int parse_option(struct option_field const *, char const *); int handle_flags_config(int , char **); -void get_group_fields(struct group_fields **); - -void config_set(struct rpki_config *); +void get_group_fields(struct group_fields const **); char const *config_get_tal(void); char const *config_get_local_repository(void); diff --git a/src/log.c b/src/log.c index cfc06c42..317aefd1 100644 --- a/src/log.c +++ b/src/log.c @@ -23,20 +23,20 @@ pr_file_name(FILE *stream) { #ifndef UNIT_TESTING char const *file = fnstack_peek(); - fprintf(stream, "%s: ", (file != NULL) ? file : "(Unknown file)"); + if (file == NULL) + return; + fprintf(stream, "%s: ", file); #endif } -#ifdef DEBUG - -static void -pr_add_indent(void) +void +pr_indent_add(void) { indent++; } -static void -pr_rm_indent(void) +void +pr_indent_rm(void) { if (indent > 0) indent--; @@ -44,6 +44,7 @@ pr_rm_indent(void) fprintf(STDERR, "Programming error: Too many pr_rm_indent()s.\n"); } +#ifdef DEBUG void pr_debug_prefix(void) @@ -77,7 +78,7 @@ pr_debug_add(const char *format, ...) va_end(args); fprintf(STDOUT, "\n"); - pr_add_indent(); + pr_indent_add(); } void @@ -85,7 +86,7 @@ pr_debug_rm(const char *format, ...) { va_list args; - pr_rm_indent(); + pr_indent_rm(); pr_debug_prefix(); @@ -113,6 +114,14 @@ pr_prefix(char const *level) va_end(args); \ } while (0) +void +pr_info(const char *format, ...) +{ + va_list args; + PR_PREFIX("INF", args); + fprintf(STDOUT, "\n"); +} + /** * Always appends a newline at the end. Always returs 0. (So you can interrupt * whatever you're doing without failing validation.) diff --git a/src/log.h b/src/log.h index 43ead610..90b1b0e3 100644 --- a/src/log.h +++ b/src/log.h @@ -21,6 +21,9 @@ #define CHECK_FORMAT(str, args) /* Nothing */ #endif +void pr_indent_add(void); +void pr_indent_rm(void); + #ifdef DEBUG void pr_debug(const char *, ...) CHECK_FORMAT(1, 2); @@ -37,6 +40,7 @@ void pr_debug_prefix(void); #endif +void pr_info(const char *, ...) CHECK_FORMAT(1, 2); int pr_warn(const char *, ...) CHECK_FORMAT(1, 2); int pr_err(const char *, ...) CHECK_FORMAT(1, 2); int pr_errno(int, const char *, ...) CHECK_FORMAT(2, 3); diff --git a/src/main.c b/src/main.c index 81991994..82f733ad 100644 --- a/src/main.c +++ b/src/main.c @@ -85,6 +85,9 @@ main(int argc, char **argv) print_stack_trace_on_segfault(); + thvar_init(); + fnstack_store(); + error = handle_flags_config(argc, argv); if (error) return error; @@ -99,8 +102,6 @@ main(int argc, char **argv) error = extension_init(); if (error) goto end2; - thvar_init(); - fnstack_store(); fnstack_push(config_get_tal()); error = tal_load(config_get_tal(), &tal); diff --git a/src/nid.c b/src/nid.c index 5decf801..408f48a1 100644 --- a/src/nid.c +++ b/src/nid.c @@ -22,7 +22,7 @@ register_oid(const char *oid, const char *sn, const char *ln) if (nid == 0) return crypto_err("Unable to register the %s NID.", sn); - printf("%s registered. Its nid is %d.\n", sn, nid); + pr_info("%s registered. Its nid is %d.", sn, nid); return nid; } diff --git a/src/object/tal.c b/src/object/tal.c index 3b83de77..bf47ee52 100644 --- a/src/object/tal.c +++ b/src/object/tal.c @@ -155,8 +155,10 @@ tal_load(char const *file_name, struct tal **result) int error; error = lfile_open(file_name, &lfile); - if (error) + if (error) { + pr_errno(error, "Error opening file '%s'", file_name); goto fail4; + } tal = malloc(sizeof(struct tal)); if (tal == NULL) { diff --git a/src/toml_handler.c b/src/toml_handler.c index 1f793104..07f7b419 100644 --- a/src/toml_handler.c +++ b/src/toml_handler.c @@ -2,124 +2,48 @@ #include #include -#include -#include - #include +#include "config.h" #include "file.h" #include "log.h" -#include "thread_var.h" #include "uri.h" static int -find_flag(struct args_flag *flags_handled, char *flag_to_find, - struct args_flag **result) -{ - int cmp; - *result = NULL; - - while(flags_handled->field != NULL) { - cmp = strcmp(flags_handled->field->name, flag_to_find); - if (cmp == 0) { - *result = flags_handled; - break; - } - flags_handled = flags_handled + 1; - } - - if (*result == NULL) - return pr_crit("Missing parameter %s.", flag_to_find); - - return 0; -} - -static int -iterate_fields(struct toml_table_t *table, struct rpki_config *config, - struct option_field *fields, struct args_flag *flags_handled) +toml_to_config(struct toml_table_t *root) { - struct option_field *tmp_field; - struct args_flag *tmp_arg; - const char *result; - char *str; + struct group_fields const *groups; + struct group_fields const *group; + struct option_field const *option; + struct toml_table_t *table; + const char *value; int error; - tmp_field = fields; - while (tmp_field->name != NULL) { - error = find_flag(flags_handled, tmp_field->name, &tmp_arg); - if (error) - return error; /* Error msg already printed. */ - if (tmp_arg->is_set) { - tmp_field += 1; - continue; - } + get_group_fields(&groups); - result = toml_raw_in(table, tmp_field->name); - if (result == 0) { - tmp_field += 1; + for (group = groups; group->name != NULL; group++) { + table = toml_table_in(root, group->name); + if (table == NULL) continue; - } - - str = (char *) result; - if (tmp_field->type->id == GTI_STRING) { - error = toml_rtos(result, &str); - if (error) - return pr_err("Bad value in '%s'", - tmp_field->name); - } - - error = handle_option(config, tmp_field, str); - if (error) - return error; - /* Free returned string from toml */ - if (tmp_field->type->id == GTI_STRING) - free(str); - - tmp_arg->is_set = true; - tmp_field += 1; - } - - return error; -} - -static int -toml_to_config(struct toml_table_t *root, struct rpki_config *config, - struct args_flag *flags_handled) -{ - struct toml_table_t *toml_table; - struct group_fields *group_fields; - int error; - - get_group_fields(&group_fields); - if (group_fields == NULL) - return 0; - - error = iterate_fields(root, config, group_fields->options, - flags_handled); - if (error) - return error; - group_fields += 1; - - while (group_fields->group_name != NULL) { - toml_table = toml_table_in(root, group_fields->group_name); - if (toml_table == 0) { - group_fields += 1; - continue; + for (option = group->options; option->id != 0; option++) { + if (option->availability == 0 + || (option->availability & AVAILABILITY_TOML)) { + value = toml_raw_in(table, option->name); + if (value == NULL) + continue; + error = parse_option(option, value); + if (error) + return error; + } } - error = iterate_fields(toml_table, config, - group_fields->options, flags_handled); - if (error) - return error; - group_fields += 1; } return error; } int -set_config_from_file(char *config_file, struct rpki_config *config, - struct args_flag *flags_handled) +set_config_from_file(char *config_file) { FILE *file; struct stat stat; @@ -127,40 +51,26 @@ set_config_from_file(char *config_file, struct rpki_config *config, struct rpki_uri uri; char errbuf[200]; int error; - bool is_config_file; - /* I think I'm not using this struct correctly but I need it to call - * some functions, so be careful using the struct rpki_uri here. - * Also no needs to be freed. */ uri.global = config_file; uri.global_len = strlen(config_file); uri.local = config_file; - is_config_file = uri_has_extension(&uri, ".ini"); - is_config_file |= uri_has_extension(&uri, ".toml"); - if (!is_config_file) { - error = pr_err("Invalid Config file extension for file '%s'", - uri.local); - goto end; - } - error = file_open(&uri, &file, &stat); if (error) - goto end; /* Error msg already printed. */ + return error; /* Error msg already printed. */ root = toml_parse_file(file, errbuf, sizeof(errbuf)); file_close(file); if (root == NULL) { - error = pr_err("Error while parsing configuration file: %s", + return pr_err("Error while parsing configuration file: %s", errbuf); - goto end; } - error = toml_to_config(root, config, flags_handled); + error = toml_to_config(root); toml_free(root); -end: return error; } diff --git a/src/toml_handler.h b/src/toml_handler.h index ae04120a..75136aaf 100644 --- a/src/toml_handler.h +++ b/src/toml_handler.h @@ -1,8 +1,6 @@ #ifndef SRC_TOML_HANDLER_H_ #define SRC_TOML_HANDLER_H_ -#include "config.h" - -int set_config_from_file(char *, struct rpki_config *, struct args_flag *); +int set_config_from_file(char *); #endif /* SRC_TOML_HANDLER_H_ */