/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) * on Sun Dec 22 22:40:51 2024. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2024 Yves Rutschle # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. */ #define _GNU_SOURCE #include #ifdef LIBCONFIG # include #endif #include #include #include #include #include #include "echosrv-conf.h" #include "argtable3.h" #ifdef LIBPCRE #define PCRE2_CODE_UNIT_WIDTH 8 #include typedef struct { PCRE2_SIZE rm_so; PCRE2_SIZE rm_eo; } regmatch_t; #else #include #endif /* This gets included in the output .c file */ /* Libconfig 1.4.9 is still used by major distributions * (e.g. CentOS7) and had a different name for * config_setting_lookup */ #if LIBCONFIG_VER_MAJOR == 1 #if LIBCONFIG_VER_MINOR == 4 #if LIBCONFIG_VER_REVISION == 9 #define config_setting_lookup config_lookup_from #endif #endif #endif /* config_type, lookup_fns, type2str are related, keep them together */ typedef enum { CFG_BOOL, CFG_INT, CFG_INT64, CFG_FLOAT, CFG_STRING, CFG_GROUP, CFG_ARRAY, CFG_LIST, } config_type; /* /config_type */ const char* type2str[] = { "boolean", "int", "int64", "float", "string", "group", "array", "list", }; typedef union { int def_bool; int def_int; long long def_int64; double def_float; char* def_string; } any_val; struct config_desc { const char* name; int type; struct config_desc * sub_group; /* Table for compound types (list and group) */ void* arg_cl; /* command-line argument for this setting */ void* base_addr; /* Base of the structure (filled at runtime). Probably not useable for list elements */ size_t offset; /* Offset of setting in the structure */ size_t offset_len; /* Offset of *_len field, for arrays and lists */ size_t offset_present; /* offset of *_is_present field, for optional settings */ size_t size; /* Size of element, or size of group for groups and lists */ int array_type; /* type of array elements, when type == CFG_ARRAY */ int mandatory; int optional; any_val default_val; }; #ifndef LIBCONFIG /* Stubs in case you don't want libconfig */ typedef void config_setting_t; typedef int config_t; #define CONFIG_TRUE 1 #define CONFIG_FALSE 0 #define make_config_setting_lookup(type) \ int config_setting_lookup_##type(const config_setting_t* a, const char* b, void* c) { \ return 0; \ } #define make_config_setting_get(type, ret_type) \ ret_type config_setting_get_##type(const config_setting_t* a) { \ return 0; \ } enum { CONFIG_TYPE_INT, CONFIG_TYPE_BOOL, CONFIG_TYPE_INT64, CONFIG_TYPE_FLOAT, CONFIG_TYPE_STRING }; make_config_setting_lookup(bool); make_config_setting_lookup(int); make_config_setting_lookup(int64); make_config_setting_lookup(float); make_config_setting_lookup(string); make_config_setting_get(bool, int); make_config_setting_get(int, int); make_config_setting_get(int64, long long int); make_config_setting_get(float, double); make_config_setting_get(string, char*); config_setting_t* config_root_setting(config_t* c) { return NULL; } config_setting_t* config_lookup(config_t* c, const char* b) { return NULL; } void config_init(config_t* c) { return; } char* config_setting_name(config_setting_t* c) { return NULL; } int config_setting_is_list(config_setting_t* c) { return 0; } int config_setting_is_array(config_setting_t* c) { return 0; } int config_setting_is_scalar(config_setting_t* c) { return 0; } int config_setting_index(const config_setting_t *setting) { return 0; } config_setting_t* config_setting_lookup(config_setting_t* a, char* b) { return NULL; } int config_setting_remove(config_setting_t* cfg, char* name) { return 0; } int config_setting_type(config_setting_t* s) { return -1; } int config_setting_length(config_setting_t* a) { return 0; } config_setting_t* config_setting_get_elem(config_setting_t* a, int i) { return NULL; } int config_read_file(config_t* a, const char* b) { return CONFIG_TRUE; } int config_error_line(config_t* c) { return 0; } char* config_error_text(config_t* c) { return NULL; } #endif static jmp_buf c2s_asprintf_fail; static int c2s_asprintf(char **restrict strp, const char *restrict fmt, ...) { va_list ap; va_start(ap,fmt); int res = vasprintf(strp, fmt, ap); va_end(ap); if (res == -1) longjmp(c2s_asprintf_fail, res); return res; } /* This is the same as config_setting_lookup_string() except it allocates a new string which belongs to the caller */ static int myconfig_setting_lookup_stringcpy( const config_setting_t* setting, const char* name, char** value) { const char* str; *value = NULL; if (config_setting_lookup_string(setting, name, &str) == CONFIG_TRUE) { c2s_asprintf(value, "%s", str); return CONFIG_TRUE; } else { return CONFIG_FALSE; } } typedef int (*lookup_fn)(const config_setting_t*, const char*, void*); lookup_fn lookup_fns[] = { (lookup_fn)config_setting_lookup_bool, (lookup_fn)config_setting_lookup_int, (lookup_fn)config_setting_lookup_int64, (lookup_fn)config_setting_lookup_float, (lookup_fn)myconfig_setting_lookup_stringcpy, NULL, /* CFG_GROUP */ NULL, /* CFG_ARRAY */ NULL, /* CFG_LIST */ }; /* Copy an any_val to arbitrary memory location */ /* 0: success * <0: error */ static int any_valcpy(config_type type, void* target, any_val val) { switch(type) { case CFG_BOOL: *(int*)target = val.def_bool; break; case CFG_INT: *(int*)target = val.def_int; break; case CFG_INT64: *(long long*)target = val.def_int64; break; case CFG_FLOAT: *(double*)target = val.def_float; break; case CFG_STRING: *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 1; } /* Copy the value of a setting to an arbitrary memory that * must be large enough */ /* 0: success * <0: error */ static int settingcpy(config_type type, void* target, const config_setting_t* setting) { any_val val; char* str; switch(type) { case CFG_BOOL: val.def_bool = config_setting_get_bool(setting); *(int*)target = val.def_bool; break; case CFG_INT: val.def_int = config_setting_get_int(setting); *(int*)target = val.def_int; break; case CFG_INT64: val.def_int64 = config_setting_get_int64(setting); *(long long*)target = val.def_int64; break; case CFG_FLOAT: val.def_float = config_setting_get_float(setting); *(double*)target = val.def_int64; break; case CFG_STRING: c2s_asprintf(&str, "%s", config_setting_get_string(setting)); val.def_string = str; *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 0; } /* Copy the value of a command line arg to arbitrary memory * that must be large enough for the type */ /* 0: success * <0: error */ static int clcpy(config_type type, void* target, const void* cl_arg) { any_val val; char* str; switch(type) { case CFG_BOOL: val.def_bool = (*(struct arg_lit**)cl_arg)->count; *(int*)target = val.def_bool; break; case CFG_INT: val.def_int = (*(struct arg_int**)cl_arg)->ival[0]; *(int*)target = val.def_int; break; case CFG_INT64: val.def_int64 = (*(struct arg_int**)cl_arg)->ival[0]; *(long long*)target = val.def_int64; break; case CFG_FLOAT: val.def_float = (*(struct arg_dbl**)cl_arg)->dval[0]; *(double*)target = val.def_float; break; case CFG_STRING: c2s_asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]); val.def_string = str; *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 0; } /* Copy the value of a string argument to arbitary memory * location that must be large enough, converting on the way * (i.e. CFG_INT gets atoi() and so on) */ /* 0: success * <0: error */ static int stringcpy(config_type type, void* target, char* from) { any_val val; switch(type) { case CFG_BOOL: val.def_bool = (*from != '0'); *(int*)target = val.def_bool; break; case CFG_INT: val.def_int = strtol(from, NULL, 10); *(int*)target = val.def_int; break; case CFG_INT64: val.def_int64 = strtoll(from, NULL, 10); *(long long*)target = val.def_int64; break; case CFG_FLOAT: val.def_float = strtod(from, NULL); *(double*)target = val.def_float; break; case CFG_STRING: val.def_string = from; *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 0; } /* Element to describe the target of a compound element * element: which config entry is being changed * match: if >0, index in pmatch to set * if 0, don't match but init with value * value: constant if not matching */ struct compound_cl_target { struct config_desc * element; int match; any_val value; }; /* Element to describe one compound command line argument * An argument is string that gets matched against a regex, * then match-groups get evaluated to each targets[]. * For lists, base_entry points to the config_setting so we * can append to it */ struct compound_cl_arg { const char* regex; struct arg_str** arg_cl; /* arg_str entry for this compound option */ struct config_desc * base_entry; struct compound_cl_target* targets; /* If override_desc is set, it points to the descriptor of the element in the group which will be checked for override. Then, override_matchindex indicates the command-line parameter match used to compare against override_desc to know if this group is overridden. If override_matchindex is 0, we don't match from the command-line but from a constant stored in override_const instead */ struct config_desc * override_desc; int override_matchindex; char* override_const; }; struct arg_file* echocfg_conffile; struct arg_lit* echocfg_udp; struct arg_str* echocfg_prefix; struct arg_str* echocfg_listen_host; struct arg_str* echocfg_listen_port; struct arg_str* echocfg_listen; struct arg_end* echocfg_end; static struct config_desc table_echocfg_listen[] = { { /* name */ "host", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & echocfg_listen_host, /* base_addr */ NULL, /* offset */ offsetof(struct echocfg_listen_item, host), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "port", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & echocfg_listen_port, /* base_addr */ NULL, /* offset */ offsetof(struct echocfg_listen_item, port), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_string = NULL }, { 0 } }; static struct config_desc table_echocfg[] = { { /* name */ "udp", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ & echocfg_udp, /* base_addr */ NULL, /* offset */ offsetof(struct echocfg_item, udp), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "prefix", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & echocfg_prefix, /* base_addr */ NULL, /* offset */ offsetof(struct echocfg_item, prefix), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "listen", /* type */ CFG_LIST, /* sub_group*/ table_echocfg_listen, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct echocfg_item, listen), /* offset_len */ offsetof(struct echocfg_item, listen_len), /* offset_present */ 0, /* size */ sizeof(struct echocfg_listen_item), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_int = 0 }, { 0 } }; static struct compound_cl_target echocfg_listen_targets [] = { { & table_echocfg_listen[0], 1, .value.def_string = "0" }, { & table_echocfg_listen[1], 2, .value.def_string = "0" }, { 0 } }; static struct compound_cl_arg compound_cl_args[] = { { /* arg: listen */ .regex = "(.+):(\\w+)", .arg_cl = & echocfg_listen, .base_entry = & table_echocfg [2], .targets = echocfg_listen_targets, .override_desc = NULL, .override_matchindex = 0, .override_const = NULL, }, { 0 } }; /* Enable debug to follow the parsing of tables */ #if 0 #define TRACE_READ(x) printf x #define TRACE_READ_PRINT_SETTING 1 #else #define TRACE_READ(x) #define TRACE_READ_PRINT_SETTING 0 #endif /* Enable debug to follow the parsing of compound options */ #if 0 #define TRACE_CMPD(x) printf x #define TRACE_CMPD_PRINT_SETTING 1 #else #define TRACE_CMPD(x) #define TRACE_CMPD_PRINT_SETTING 0 #endif static void print_setting(config_type type, void* val) { if (TRACE_READ_PRINT_SETTING || TRACE_CMPD_PRINT_SETTING) { switch(type) { case CFG_BOOL: case CFG_INT: printf("%d", *(int*)val); break; case CFG_INT64: printf("%lld", *(long long*)val); break; case CFG_FLOAT: printf("%f", *(double*)val); break; case CFG_STRING: printf("`%s'", *(char**)val); break; case CFG_GROUP: case CFG_LIST: case CFG_ARRAY: break; } } } /* Changes all dashes to underscores in a string of * vice-versa */ static void strswap_ud(const char target, char* str) { char* c; for (c = str; *c; c++) if (*c == (target == '_' ? '-' : '_')) *c = (target == '_' ? '_' : '-'); } /* Same as config_setting_lookup() but looks up with dash or * underscore so `my_setting` and `my-setting` match the same */ static config_setting_t* config_setting_lookup_ud(config_setting_t* cfg, struct config_desc* desc) { config_setting_t* setting; char name[strlen(desc->name)+1];; strcpy(name, desc->name); strswap_ud('_', name); setting = config_setting_lookup(cfg, name); if (setting) return setting; strswap_ud('-', name); setting = config_setting_lookup(cfg, name); return setting; } static int lookup_typed_ud(config_setting_t* cfg, void* target, struct config_desc *desc) { lookup_fn lookup_fn = lookup_fns[desc->type]; char name[strlen(desc->name)+1];; strcpy(name, desc->name); strswap_ud('_', name); if (lookup_fn(cfg, name, ((char*)target) + desc->offset) == CONFIG_TRUE) return CONFIG_TRUE; strswap_ud('-', name); return lookup_fn(cfg, name, ((char*)target) + desc->offset); } /* Removes a setting, trying both underscores and dashes as * name (so deleting 'my-setting' deletes both 'my_setting' * and 'my-setting') */ static int setting_delete_ud(config_setting_t* cfg, struct config_desc *desc) { char name[strlen(desc->name)+1];; strcpy(name, desc->name); strswap_ud('_', name); if (config_setting_remove(cfg, name) == CONFIG_TRUE) return CONFIG_TRUE; strswap_ud('-', name); return config_setting_remove(cfg, name); } /* When traversing configuration, allocate memory for plural * types, init for scalars */ static void read_block_init(void* target, config_setting_t* cfg, struct config_desc* desc) { size_t len = 0; void* block; config_setting_t* setting; switch (desc->type) { case CFG_LIST: case CFG_ARRAY: if (cfg) { setting = config_setting_lookup_ud(cfg, desc); if (setting) len = config_setting_length(setting); } block = calloc(len, desc->size); *(size_t*)(((char*)target) + desc->offset_len) = len; *(void**)(((char*)target) + desc->offset) = block; TRACE_READ((" sizing for %zu elems ", len)); break; case CFG_GROUP: block = calloc(1, desc->size); *(void**)(((char*)target) + desc->offset) = block; TRACE_READ((" sizing for %zu elems ", len)); break; default: /* scalar types: copy default */ memcpy(((char*)target) + desc->offset, &desc->default_val, desc->size); TRACE_READ(("setting %s to default ", desc->name)); print_setting(desc->type,(char*)target + desc->offset); break; } } static int read_block(config_setting_t* cfg, void* target, struct config_desc* desc, char** errmsg); /* When traversing configuration, set value from config * file, or command line * return: 0 if not set, 1 if set somehow */ static int read_block_setval(void* target, config_setting_t* cfg, struct config_desc* desc, char** errmsg) { int i; size_t len = 0; void* block; int in_cfg = 0, in_cl = 0; /* Present in config file? present on command line? */ config_setting_t* setting = NULL; switch (desc->type) { case CFG_LIST: if (cfg) { setting = config_setting_lookup_ud(cfg, desc); if (setting) len = config_setting_length(setting); block = *(void**)(((char*)target) + desc->offset); for (i = 0; i < len; i++) { config_setting_t* elem = config_setting_get_elem(setting, i); if (!read_block(elem, (char*)block + desc->size * i, desc->sub_group, errmsg)) return 0; } } break; case CFG_ARRAY: if (cfg) { setting = config_setting_lookup_ud(cfg, desc); if (setting) len = config_setting_length(setting); block = *(void**)(((char*)target) + desc->offset); for (i = 0; i < len; i++) { config_setting_t* elem = config_setting_get_elem(setting, i); settingcpy(desc->array_type, (char*)block + desc->size * i, elem); TRACE_READ(("[%d] = ", i)); print_setting(desc->array_type, (char*)block + desc->size *i); TRACE_READ(("\n")); } setting_delete_ud(cfg, desc); } break; case CFG_GROUP: if (cfg) setting = config_setting_lookup_ud(cfg, desc); block = *(void**)(((char*)target) + desc->offset); if (!read_block(setting, block, desc->sub_group, errmsg)) return 0; break; default: /* scalar types */ TRACE_READ((" `%s'", desc->name)); if (cfg && config_setting_lookup_ud(cfg, desc)) { TRACE_READ((" in config file: ")); /* setting is present in cfg, look it up */ if (lookup_typed_ud(cfg, target, desc) != CONFIG_TRUE) { TRACE_READ((" but wrong type (expected %s) ", type2str[desc->type])); c2s_asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n", desc->name, type2str[desc->type]); return 0; } print_setting(desc->type, (((char*)target) + desc->offset)); setting_delete_ud(cfg, desc); in_cfg = 1; } else { TRACE_READ((" not in config file")); } if (desc->arg_cl && (*(struct arg_int**)desc->arg_cl)->count) { clcpy(desc->type, ((char*)target) + desc->offset, desc->arg_cl); TRACE_READ((", command line sets to ")); print_setting(desc->type, (((char*)target) + desc->offset)); in_cl = 1; } else { TRACE_READ((", not in command line")); } if (!(in_cfg || in_cl)) { TRACE_READ(("\n")); return 0; } TRACE_READ(("\n")); break; } return 1; } /* Set *_is_present flag for target */ static void target_is_present(void* target, struct config_desc* desc, int val) { if (desc->optional) { /* _is_present only exists in target for optional settings */ TRACE_READ((" mark as set")); *(int*)((char*)target + desc->offset_present) = val; } } /* traverses the configuration; allocates memory if needed, * set to default if exists, * fill from configuration file if present, overrides or set from * command line if present, verifies mandatory options have * been set * target: base address of the group being processed */ static int read_block(config_setting_t* cfg, void* target, struct config_desc* desc, char** errmsg) { int set; for (; desc->name; desc++) { TRACE_READ(("reading %s%s%s: ", desc->optional ? "optional " : "", desc->mandatory ? "mandatory " : "", desc->name)); desc->base_addr = target; read_block_init(target, cfg, desc); set = read_block_setval(target, cfg, desc, errmsg); if (!set && desc->mandatory) { c2s_asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name); return 0; } if (desc->optional) target_is_present(target, desc, set && desc->optional); } return 1; } /* Copy regex match into newly allocated string * out: newly allocated string (caller has to free it) * in: string into which the match was made * pmatch: the match to extract */ static void pmatchcpy(char** out, const char* in, regmatch_t* pmatch) { int len = pmatch->rm_eo - pmatch->rm_so; *out = calloc(len+1, 1); memcpy(*out, in + pmatch->rm_so, len); } /* Processes a list of targets within one element, setting * the values in the target setting * target: where to put the data * arg: CL arg containing the target fields * clval: command line parameter * pmatch: regex match array into clval */ static int set_target_fields(void* target_addr, struct compound_cl_arg* arg, const char* clval, regmatch_t* pmatch) { int pmatch_cnt = 1; struct compound_cl_target* target; for (target = arg->targets; target->element; target++) { struct config_desc * element = target->element; if (target->match) { TRACE_CMPD((" match %d rm_so %d rm_eo %d type %d\n", pmatch_cnt, pmatch[pmatch_cnt].rm_so, pmatch[pmatch_cnt].rm_eo, element->type )); if (pmatch[pmatch_cnt].rm_so == -1) { /* This should not happen as regexec() did * match before, unless there is a * discrepency between the regex and the * number of backreferences */ return 0; } char* str; pmatchcpy(&str, clval, &pmatch[pmatch_cnt]); stringcpy(element->type, (char*)target_addr + element->offset, str); TRACE_CMPD(("setting %p+%zu to : ", target_addr , element->offset)); print_setting(element->type , (char*)target_addr + element->offset); TRACE_CMPD(("\n")); /* str is temporary buffer for type conversion, except for strings which we * need to keep around so don't free them */ if (element->type != CFG_STRING) free(str); pmatch_cnt++; } else { /* don't use matching, set constant */ any_valcpy(element->type, (char*)target_addr + element->offset, target->value); } } return 1; } /* Goes over a list, finds if a group matches the specified string and overwrite * it if it does. */ static int override_on_str(struct compound_cl_arg* arg, const char* str, regmatch_t* pmatch) { struct config_desc * desc = arg->base_entry; void* list_base = *(void**)(desc->base_addr + desc->offset); size_t list_len = *(size_t*)(desc->base_addr + desc->offset_len); size_t elem_size = desc->size; int i; for (i = 0; i < list_len; i++) { char* group_base = ((char*)list_base + i * elem_size); char* cfg_member = *(char**)(group_base + arg->override_desc->offset); if (!strcmp(str, cfg_member)) { memset(group_base, 0, elem_size); struct arg_str* arg_cl = *arg->arg_cl; if (!set_target_fields(group_base, arg, arg_cl->sval[0], pmatch)) return 0; return 1; } } return 0; } /* Goes over a list and override group if needed */ static int override_elem(struct compound_cl_arg* arg, int arg_index, regmatch_t* pmatch) { char* str; int allocated = 0; int res; if (arg->override_matchindex) { struct arg_str* arg_cl = *arg->arg_cl; pmatchcpy(&str, arg_cl->sval[arg_index], &pmatch[arg->override_matchindex]); allocated = 1; } else { str = arg->override_const; } res = override_on_str(arg, str, pmatch); if (allocated) free(str); return res; } /* Add an argument to a list, overriding if required or * appending otherwise */ static int add_arg_to_list(struct compound_cl_arg* arg, int arg_index, regmatch_t* pmatch) { struct config_desc * desc = arg->base_entry; void* list_base = *(void**)(desc->base_addr + desc->offset); size_t list_len = *(size_t*)(desc->base_addr + desc->offset_len); size_t elem_size = desc->size; /* are we overriding an existing group? */ if (arg->override_desc) if (override_elem(arg, arg_index, pmatch)) return 1; /* override not found or no override, append element and * zero it out */ list_len++; list_base = realloc(list_base, list_len * elem_size); *(size_t*)(desc->base_addr + desc->offset_len) = list_len; *(void**)(desc->base_addr + desc->offset) = list_base; memset(list_base + (list_len - 1) * elem_size, 0, elem_size); struct arg_str* arg_cl = *arg->arg_cl; if (!set_target_fields((char*)list_base + (list_len - 1) * elem_size, arg, arg_cl->sval[arg_index], pmatch)) { return 0; } return 1; } /* TODO: pass pmatch size as parameter or something */ #define MAX_MATCH 10 #ifndef LIBPCRE static int regcompmatch_posix( regmatch_t* pmatch, struct compound_cl_arg* arg, int arg_index, char** errmsg) { char* regerr; struct arg_str* arg_cl = *arg->arg_cl; regex_t preg; int res = regcomp(&preg, arg->regex, REG_EXTENDED); if (res) { int errlen = regerror(res, &preg, NULL, 0); regerr = malloc(errlen); regerror(res, &preg, regerr, errlen); c2s_asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr); free(regerr); return 0; } res = regexec(&preg, arg_cl->sval[arg_index], MAX_MATCH, &pmatch[0], 0); if (res) { c2s_asprintf(errmsg, "--%s %s: Illegal argument", arg_cl->hdr.longopts, arg->regex); return 0; } return 1; } #endif #ifdef LIBPCRE static int regcompmatch_pcre2( regmatch_t* pmatch, struct compound_cl_arg* arg, int arg_index, char** errmsg) { int i, error; pcre2_code* pcre; PCRE2_UCHAR8 err_str[120]; /* ample, according to pcre2api(3) */ pcre2_match_data* matches; PCRE2_SIZE error_offset; struct arg_str* arg_cl = *arg->arg_cl; pcre = pcre2_compile((PCRE2_SPTR8)arg->regex, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL); if (!pcre) { pcre2_get_error_message(error, err_str, sizeof(err_str)); c2s_asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n", arg->regex, error, err_str, error_offset); return 0; } matches = pcre2_match_data_create(MAX_MATCH, NULL); int res = pcre2_match(pcre, (PCRE2_SPTR8)arg_cl->sval[arg_index], PCRE2_ZERO_TERMINATED, 0, 0, matches, NULL); if (res < 0) { pcre2_get_error_message(res, err_str, sizeof(err_str)); c2s_asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n", arg_cl->sval[arg_index], arg->regex, res, err_str); return 0; } PCRE2_SIZE *ovec = pcre2_get_ovector_pointer(matches); if (res > MAX_MATCH) res = MAX_MATCH; /* From pcre2posix.c */ for (i = 0; i < res; i++) { pmatch[i].rm_so = (ovec[i*2] == PCRE2_UNSET) ? -1 : ovec[i*2]; pmatch[i].rm_eo = (ovec[i*2+1] == PCRE2_UNSET) ? -1 : ovec[i*2+1]; } for (; i < MAX_MATCH; i++) pmatch[i].rm_so = pmatch[i].rm_eo = -1; pcre2_match_data_free(matches); return 1; } #endif /* Regex fiddling: uses info in arg to fill pmatch * arg: description of the command line argument * arg_index: occurence of this argument on the command line */ static int regcompmatch(regmatch_t* pmatch, struct compound_cl_arg* arg, int arg_index, char** errmsg) { #if LIBPCRE return regcompmatch_pcre2(pmatch, arg, arg_index, errmsg); #else return regcompmatch_posix(pmatch, arg, arg_index, errmsg); #endif } /* Read compound options described in `arg`, from `cfg`, to `setting` */ static int read_compounds(config_setting_t* cfg, void* setting, struct compound_cl_arg* arg, char** errmsg) { int arg_i; struct arg_str* arg_cl; regmatch_t pmatch[MAX_MATCH]; for (; arg->regex; arg++) { arg_cl = *arg->arg_cl; TRACE_CMPD(("Compound %s occurs %d : ", arg_cl->hdr.longopts, arg_cl->count)); for (arg_i = 0; arg_i < arg_cl->count; arg_i++) { if (!regcompmatch(&pmatch[0], arg, arg_i, errmsg)) return 0; TRACE_CMPD(("`%s' matched\n", arg_cl->sval[arg_i])); switch (arg->base_entry->type) { case CFG_LIST: /* In a list, find the end or the element to override */ if (!add_arg_to_list(arg, arg_i, pmatch)) { return 0; } break; /* Semantics for CFG_ARRAY TBD */ case CFG_GROUP: if (!set_target_fields( /* base_addr is the same for all elements in the group */ arg->targets[0].element->base_addr, arg, arg_cl->sval[arg_i], pmatch)) return 0; default: TRACE_CMPD(("error, compound on type %d\n", arg->base_entry->type)); break; } } TRACE_CMPD(("done %s\n", arg_cl->hdr.longopts)); } return 1; } /* read config file `filename` into `c` */ static int c2s_parse_file(const char* filename, config_t* c, char**errmsg) { /* Read config file */ if (config_read_file(c, filename) == CONFIG_FALSE) { if (config_error_line(c) != 0) { c2s_asprintf(errmsg, "%s:%d:%s", filename, config_error_line(c), config_error_text(c)); return 0; } c2s_asprintf(errmsg, "%s:%s", filename, config_error_text(c)); return 0; } return 1; } /* Allocates a new string that represents the setting value, which must be a scalar */ static void scalar_to_string(char** strp, config_setting_t* s) { switch(config_setting_type(s)) { case CONFIG_TYPE_INT: c2s_asprintf(strp, "%d\n", config_setting_get_int(s)); break; case CONFIG_TYPE_BOOL: c2s_asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" ); break; case CONFIG_TYPE_INT64: c2s_asprintf(strp, "%lld\n", config_setting_get_int64(s)); break; case CONFIG_TYPE_FLOAT: c2s_asprintf(strp, "%lf\n", config_setting_get_float(s)); break; case CONFIG_TYPE_STRING: c2s_asprintf(strp, "%s\n", config_setting_get_string(s)); break; default: /* This means a bug */ fprintf(stderr, "Unexpected type %d\n", config_setting_type(s)); exit(1); } } /* Typesets all the settings in a configuration as a * newly-allocated string. The string management is caller's * responsability. * Returns the number of scalars in the configuration */ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp) { int i, len, res = 0; config_setting_t* child; char* subpath, *value, *old; const char* name; len = config_setting_length(parent); for (i = 0; i < len; i++) { child = config_setting_get_elem(parent, i); name = config_setting_name(child); if (!name) name = ""; if(config_setting_is_list(parent) || config_setting_is_array(parent)) { c2s_asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name); } else { c2s_asprintf(&subpath, "%s/%s", path, name); } if (config_setting_is_scalar(child)) { scalar_to_string(&value, child); /* Add value to the output string */ if (*strp) { c2s_asprintf(&old, "%s", *strp); free(*strp); } else { c2s_asprintf(&old, "%s", ""); } c2s_asprintf(strp, "%s%s:%s", old, subpath, value); free(value); free(old); res++; /* At least one scalar was found */ } else { /* It's an aggregate -- descend into it */ res += cfg_as_string(child, subpath, strp); } free(subpath); } return res; } /* 0: success <0: error */ int echocfg_cl_parse(int argc, char* argv[], struct echocfg_item* cfg) { int nerrors, res; config_t c; char* errmsg; config_setting_t* s; void* argtable[] = { #ifdef LIBCONFIG echocfg_conffile = arg_filen("F", "config", "", 0, 1, "Specify configuration file"), #endif echocfg_udp = arg_litn(NULL, "udp", 0, 1, ""), echocfg_prefix = arg_strn(NULL, "prefix", "", 0, 1, ""), echocfg_listen_host = arg_strn(NULL, "listen-host", "", 0, 10, ""), echocfg_listen_port = arg_strn(NULL, "listen-port", "", 0, 10, ""), echocfg_listen = arg_strn("p", "listen", "", 0, 10, "Listen on host:port"), echocfg_end = arg_end(10) }; /* Set up failure handler in case asprintf() runs out of * memory */ ; if (setjmp(c2s_asprintf_fail)) { fprintf(stderr, "asprintf: probably out of memory\n"); return -1; } /* Parse command line */ nerrors = arg_parse(argc, argv, argtable); if (nerrors) { arg_print_errors(stdout, echocfg_end, "echocfg"); arg_print_syntax(stdout, argtable, "\n"); arg_print_glossary(stdout, argtable, " %-25s\t%s\n"); return -1; } config_init(&c); if (echocfg_conffile && echocfg_conffile->count) { if (!c2s_parse_file(echocfg_conffile->filename[0], &c, &errmsg)) { fprintf(stderr, "%s\n", errmsg); return -1; } } s = config_root_setting(&c); res = read_block(s, cfg, table_echocfg, &errmsg); if (!res) { fprintf(stderr, "%s\n", errmsg); return -1; } res = read_compounds(s, cfg, compound_cl_args, &errmsg); if (!res) { fprintf(stderr, "%s\n", errmsg); return -1; } errmsg = NULL; res = cfg_as_string(s, "", &errmsg); if (res) fprintf(stderr, "Unknown settings:\n%s\n", errmsg); return 0; } static void indent(FILE* out, int depth) { int i; for (i = 0; i < depth; i++) fprintf(out, " "); } static void echocfg_listen_fprint( FILE* out, struct echocfg_listen_item* echocfg_listen, int depth) { indent(out, depth); fprintf(out, "host: %s", echocfg_listen->host); fprintf(out, "\n"); indent(out, depth); fprintf(out, "port: %s", echocfg_listen->port); fprintf(out, "\n"); } void echocfg_fprint( FILE* out, struct echocfg_item* echocfg, int depth) { int i; indent(out, depth); fprintf(out, "udp: %d", echocfg->udp); fprintf(out, "\n"); indent(out, depth); fprintf(out, "prefix: %s", echocfg->prefix); fprintf(out, "\n"); indent(out, depth); fprintf(out, "listen [%zu]:\n", echocfg->listen_len); for (i = 0; i < echocfg->listen_len; i++) { echocfg_listen_fprint(out, &echocfg->listen[i], depth+1); } }