/** * Copyright (C) Mellanox Technologies Ltd. 2001-2019. ALL RIGHTS RESERVED. * * See file LICENSE for terms. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "parser.h" #include #include #include #include #include #include #include #include #include #include #include /* width of titles in docstring */ #define UCS_CONFIG_PARSER_DOCSTR_WIDTH 10 /* list of prefixes for a configuration variable, used to dump all possible * aliases. */ typedef struct ucs_config_parser_prefix_list { const char *prefix; ucs_list_link_t list; } ucs_config_parser_prefix_t; typedef UCS_CONFIG_ARRAY_FIELD(void, data) ucs_config_array_field_t; KHASH_SET_INIT_STR(ucs_config_env_vars) /* Process environment variables */ extern char **environ; UCS_LIST_HEAD(ucs_config_global_list); static khash_t(ucs_config_env_vars) ucs_config_parser_env_vars = {0}; static pthread_mutex_t ucs_config_parser_env_vars_hash_lock = PTHREAD_MUTEX_INITIALIZER; const char *ucs_async_mode_names[] = { [UCS_ASYNC_MODE_SIGNAL] = "signal", [UCS_ASYNC_MODE_THREAD_SPINLOCK] = "thread_spinlock", [UCS_ASYNC_MODE_THREAD_MUTEX] = "thread_mutex", [UCS_ASYNC_MODE_POLL] = "poll", [UCS_ASYNC_MODE_LAST] = NULL }; UCS_CONFIG_DEFINE_ARRAY(string, sizeof(char*), UCS_CONFIG_TYPE_STRING); /* Fwd */ static ucs_status_t ucs_config_parser_set_value_internal(void *opts, ucs_config_field_t *fields, const char *name, const char *value, const char *table_prefix, int recurse); static int __find_string_in_list(const char *str, const char **list) { int i; for (i = 0; *list; ++list, ++i) { if (strcasecmp(*list, str) == 0) { return i; } } return -1; } int ucs_config_sscanf_string(const char *buf, void *dest, const void *arg) { *((char**)dest) = strdup(buf); return 1; } int ucs_config_sprintf_string(char *buf, size_t max, const void *src, const void *arg) { strncpy(buf, *((char**)src), max); return 1; } ucs_status_t ucs_config_clone_string(const void *src, void *dest, const void *arg) { char *new_str = strdup(*(char**)src); if (new_str == NULL) { return UCS_ERR_NO_MEMORY; } *((char**)dest) = new_str; return UCS_OK; } void ucs_config_release_string(void *ptr, const void *arg) { free(*(char**)ptr); } int ucs_config_sscanf_int(const char *buf, void *dest, const void *arg) { return sscanf(buf, "%i", (unsigned*)dest); } ucs_status_t ucs_config_clone_int(const void *src, void *dest, const void *arg) { *(int*)dest = *(int*)src; return UCS_OK; } int ucs_config_sprintf_int(char *buf, size_t max, const void *src, const void *arg) { return snprintf(buf, max, "%i", *(unsigned*)src); } int ucs_config_sscanf_uint(const char *buf, void *dest, const void *arg) { if (!strcasecmp(buf, UCS_NUMERIC_INF_STR)) { *(unsigned*)dest = UINT_MAX; return 1; } else { return sscanf(buf, "%u", (unsigned*)dest); } } ucs_status_t ucs_config_clone_uint(const void *src, void *dest, const void *arg) { *(unsigned*)dest = *(unsigned*)src; return UCS_OK; } int ucs_config_sprintf_uint(char *buf, size_t max, const void *src, const void *arg) { unsigned value = *(unsigned*)src; if (value == UINT_MAX) { snprintf(buf, max, UCS_NUMERIC_INF_STR); return 1; } else { return snprintf(buf, max, "%u", value); } } int ucs_config_sscanf_ulong(const char *buf, void *dest, const void *arg) { return sscanf(buf, "%lu", (unsigned long*)dest); } int ucs_config_sprintf_ulong(char *buf, size_t max, const void *src, const void *arg) { return snprintf(buf, max, "%lu", *(unsigned long*)src); } ucs_status_t ucs_config_clone_ulong(const void *src, void *dest, const void *arg) { *(unsigned long*)dest = *(unsigned long*)src; return UCS_OK; } int ucs_config_sscanf_double(const char *buf, void *dest, const void *arg) { return sscanf(buf, "%lf", (double*)dest); } int ucs_config_sprintf_double(char *buf, size_t max, const void *src, const void *arg) { return snprintf(buf, max, "%.3f", *(double*)src); } ucs_status_t ucs_config_clone_double(const void *src, void *dest, const void *arg) { *(double*)dest = *(double*)src; return UCS_OK; } int ucs_config_sscanf_hex(const char *buf, void *dest, const void *arg) { /* Special value: auto */ if (!strcasecmp(buf, UCS_VALUE_AUTO_STR)) { *(size_t*)dest = UCS_HEXUNITS_AUTO; return 1; } else if (strncasecmp(buf, "0x", 2) == 0) { return (sscanf(buf + 2, "%x", (unsigned int*)dest)); } else { return 0; } } int ucs_config_sprintf_hex(char *buf, size_t max, const void *src, const void *arg) { uint16_t val = *(uint16_t*)src; if (val == UCS_HEXUNITS_AUTO) { return snprintf(buf, max, UCS_VALUE_AUTO_STR); } return snprintf(buf, max, "0x%x", *(unsigned int*)src); } int ucs_config_sscanf_bool(const char *buf, void *dest, const void *arg) { if (!strcasecmp(buf, "y") || !strcasecmp(buf, "yes") || !strcmp(buf, "1")) { *(int*)dest = 1; return 1; } else if (!strcasecmp(buf, "n") || !strcasecmp(buf, "no") || !strcmp(buf, "0")) { *(int*)dest = 0; return 1; } else { return 0; } } int ucs_config_sprintf_bool(char *buf, size_t max, const void *src, const void *arg) { return snprintf(buf, max, "%c", *(int*)src ? 'y' : 'n'); } int ucs_config_sscanf_ternary(const char *buf, void *dest, const void *arg) { UCS_STATIC_ASSERT(UCS_NO == 0); UCS_STATIC_ASSERT(UCS_YES == 1); if (!strcasecmp(buf, "try") || !strcasecmp(buf, "maybe")) { *(int*)dest = UCS_TRY; return 1; } else { return ucs_config_sscanf_bool(buf, dest, arg); } } int ucs_config_sprintf_ternary(char *buf, size_t max, const void *src, const void *arg) { if (*(int*)src == UCS_TRY) { return snprintf(buf, max, "try"); } else { return ucs_config_sprintf_bool(buf, max, src, arg); } } int ucs_config_sscanf_on_off(const char *buf, void *dest, const void *arg) { if (!strcasecmp(buf, "on") || !strcmp(buf, "1")) { *(int*)dest = UCS_CONFIG_ON; return 1; } else if (!strcasecmp(buf, "off") || !strcmp(buf, "0")) { *(int*)dest = UCS_CONFIG_OFF; return 1; } else { return 0; } } int ucs_config_sscanf_on_off_auto(const char *buf, void *dest, const void *arg) { if (!strcasecmp(buf, "try") || !strcasecmp(buf, "maybe") || !strcasecmp(buf, "auto")) { *(int*)dest = UCS_CONFIG_AUTO; return 1; } else { return ucs_config_sscanf_on_off(buf, dest, arg); } } int ucs_config_sprintf_on_off_auto(char *buf, size_t max, const void *src, const void *arg) { switch (*(int*)src) { case UCS_CONFIG_AUTO: return snprintf(buf, max, "auto"); case UCS_CONFIG_ON: return snprintf(buf, max, "on"); case UCS_CONFIG_OFF: return snprintf(buf, max, "off"); default: return snprintf(buf, max, "%d", *(int*)src); } } int ucs_config_sscanf_enum(const char *buf, void *dest, const void *arg) { int i; i = __find_string_in_list(buf, (const char**)arg); if (i < 0) { return 0; } *(unsigned*)dest = i; return 1; } int ucs_config_sprintf_enum(char *buf, size_t max, const void *src, const void *arg) { char * const *table = arg; strncpy(buf, table[*(unsigned*)src], max); return 1; } static void __print_table_values(char * const *table, char *buf, size_t max) { char *ptr = buf, *end = buf + max; for (; *table; ++table) { snprintf(ptr, end - ptr, "|%s", *table); ptr += strlen(ptr); } snprintf(ptr, end - ptr, "]"); *buf = '['; } void ucs_config_help_enum(char *buf, size_t max, const void *arg) { __print_table_values(arg, buf, max); } int ucs_config_sscanf_bitmap(const char *buf, void *dest, const void *arg) { char *str = strdup(buf); char *p, *saveptr; int ret, i; if (str == NULL) { return 0; } ret = 1; *((unsigned*)dest) = 0; p = strtok_r(str, ",", &saveptr); while (p != NULL) { i = __find_string_in_list(p, (const char**)arg); if (i < 0) { ret = 0; break; } *((unsigned*)dest) |= UCS_BIT(i); p = strtok_r(NULL, ",", &saveptr); } free(str); return ret; } int ucs_config_sprintf_bitmap(char *buf, size_t max, const void *src, const void *arg) { ucs_flags_str(buf, max, *((unsigned*)src), (const char**)arg); return 1; } void ucs_config_help_bitmap(char *buf, size_t max, const void *arg) { snprintf(buf, max, "comma-separated list of: "); __print_table_values(arg, buf + strlen(buf), max - strlen(buf)); } int ucs_config_sscanf_bitmask(const char *buf, void *dest, const void *arg) { int ret = sscanf(buf, "%u", (unsigned*)dest); if (*(unsigned*)dest != 0) { *(unsigned*)dest = UCS_BIT(*(unsigned*)dest) - 1; } return ret; } int ucs_config_sprintf_bitmask(char *buf, size_t max, const void *src, const void *arg) { return snprintf(buf, max, "%u", __builtin_popcount(*(unsigned*)src)); } int ucs_config_sscanf_time(const char *buf, void *dest, const void *arg) { char units[3]; int num_fields; double value; double per_sec; memset(units, 0, sizeof(units)); num_fields = sscanf(buf, "%lf%c%c", &value, &units[0], &units[1]); if (num_fields == 1) { per_sec = 1; } else if (num_fields == 2 || num_fields == 3) { if (!strcmp(units, "m")) { per_sec = 1.0 / 60.0; } else if (!strcmp(units, "s")) { per_sec = 1; } else if (!strcmp(units, "ms")) { per_sec = UCS_MSEC_PER_SEC; } else if (!strcmp(units, "us")) { per_sec = UCS_USEC_PER_SEC; } else if (!strcmp(units, "ns")) { per_sec = UCS_NSEC_PER_SEC; } else { return 0; } } else { return 0; } *(double*)dest = value / per_sec; return 1; } int ucs_config_sprintf_time(char *buf, size_t max, const void *src, const void *arg) { snprintf(buf, max, "%.2fus", *(double*)src * UCS_USEC_PER_SEC); return 1; } int ucs_config_sscanf_bw(const char *buf, void *dest, const void *arg) { double *dst = (double*)dest; char str[16] = {0}; int offset = 0; size_t divider; size_t units; double value; int num_fields; if (!strcasecmp(buf, UCS_VALUE_AUTO_STR)) { *dst = UCS_BANDWIDTH_AUTO; return 1; } num_fields = sscanf(buf, "%lf%15s", &value, str); if (num_fields < 2) { return 0; } ucs_assert(num_fields == 2); units = (str[0] == 'b') ? 1 : ucs_string_quantity_prefix_value(str[0]); if (!units) { return 0; } offset = (units == 1) ? 0 : 1; switch (str[offset]) { case 'B': divider = 1; break; case 'b': divider = 8; break; default: return 0; } offset++; if (strcmp(str + offset, "ps") && strcmp(str + offset, "/s") && strcmp(str + offset, "s")) { return 0; } ucs_assert((divider == 1) || (divider == 8)); /* bytes or bits */ *dst = value * units / divider; return 1; } int ucs_config_sprintf_bw(char *buf, size_t max, const void *src, const void *arg) { double value = *(double*)src; size_t len; if (value == UCS_BANDWIDTH_AUTO) { snprintf(buf, max, UCS_VALUE_AUTO_STR); } ucs_memunits_to_str((size_t)value, buf, max); len = strlen(buf); snprintf(buf + len, max - len, "Bps"); return 1; } int ucs_config_sscanf_bw_spec(const char *buf, void *dest, const void *arg) { ucs_config_bw_spec_t *dst = (ucs_config_bw_spec_t*)dest; char *delim; delim = strchr(buf, ':'); if (!delim) { return 0; } if (!ucs_config_sscanf_bw(delim + 1, &dst->bw, arg)) { return 0; } dst->name = ucs_strndup(buf, delim - buf, __func__); return dst->name != NULL; } int ucs_config_sprintf_bw_spec(char *buf, size_t max, const void *src, const void *arg) { ucs_config_bw_spec_t *bw = (ucs_config_bw_spec_t*)src; int len; if (max) { snprintf(buf, max, "%s:", bw->name); len = strlen(buf); ucs_config_sprintf_bw(buf + len, max - len, &bw->bw, arg); } return 1; } ucs_status_t ucs_config_clone_bw_spec(const void *src, void *dest, const void *arg) { ucs_config_bw_spec_t *s = (ucs_config_bw_spec_t*)src; ucs_config_bw_spec_t *d = (ucs_config_bw_spec_t*)dest; d->bw = s->bw; d->name = ucs_strdup(s->name, __func__); return d->name ? UCS_OK : UCS_ERR_NO_MEMORY; } void ucs_config_release_bw_spec(void *ptr, const void *arg) { ucs_free(((ucs_config_bw_spec_t*)ptr)->name); } int ucs_config_sscanf_signo(const char *buf, void *dest, const void *arg) { char *endptr; int signo; signo = strtol(buf, &endptr, 10); if (*endptr == '\0') { *(int*)dest = signo; return 1; } if (!strncmp(buf, "SIG", 3)) { buf += 3; } return ucs_config_sscanf_enum(buf, dest, ucs_signal_names); } int ucs_config_sprintf_signo(char *buf, size_t max, const void *src, const void *arg) { return ucs_config_sprintf_enum(buf, max, src, ucs_signal_names); } int ucs_config_sscanf_memunits(const char *buf, void *dest, const void *arg) { if (ucs_str_to_memunits(buf, dest) != UCS_OK) { return 0; } return 1; } int ucs_config_sprintf_memunits(char *buf, size_t max, const void *src, const void *arg) { size_t sz = *(size_t*)src; if (sz == UCS_MEMUNITS_INF) { snprintf(buf, max, UCS_NUMERIC_INF_STR); } else if (sz == UCS_MEMUNITS_AUTO) { snprintf(buf, max, UCS_VALUE_AUTO_STR); } else { ucs_memunits_to_str(sz, buf, max); } return 1; } int ucs_config_sscanf_ulunits(const char *buf, void *dest, const void *arg) { /* Special value: auto */ if (!strcasecmp(buf, UCS_VALUE_AUTO_STR)) { *(size_t*)dest = UCS_ULUNITS_AUTO; return 1; } else if (!strcasecmp(buf, UCS_NUMERIC_INF_STR)) { *(size_t*)dest = UCS_ULUNITS_INF; return 1; } return ucs_config_sscanf_ulong(buf, dest, arg); } int ucs_config_sprintf_ulunits(char *buf, size_t max, const void *src, const void *arg) { size_t val = *(size_t*)src; if (val == UCS_ULUNITS_AUTO) { return snprintf(buf, max, UCS_VALUE_AUTO_STR); } else if (val == UCS_ULUNITS_INF) { return snprintf(buf, max, UCS_NUMERIC_INF_STR); } return ucs_config_sprintf_ulong(buf, max, src, arg); } int ucs_config_sscanf_range_spec(const char *buf, void *dest, const void *arg) { ucs_range_spec_t *range_spec = dest; unsigned first, last; char *p, *str; int ret = 1; str = strdup(buf); if (str == NULL) { return 0; } /* Check if got a range or a single number */ p = strchr(str, '-'); if (p == NULL) { /* got only one value (not a range) */ if (1 != sscanf(buf, "%u", &first)) { ret = 0; goto out; } last = first; } else { /* got a range of numbers */ *p = 0; /* split str */ if ((1 != sscanf(str, "%u", &first)) || (1 != sscanf(p + 1, "%u", &last))) { ret = 0; goto out; } } range_spec->first = first; range_spec->last = last; out: free (str); return ret; } int ucs_config_sprintf_range_spec(char *buf, size_t max, const void *src, const void *arg) { const ucs_range_spec_t *range_spec = src; if (range_spec->first == range_spec->last) { snprintf(buf, max, "%d", range_spec->first); } else { snprintf(buf, max, "%d-%d", range_spec->first, range_spec->last); } return 1; } ucs_status_t ucs_config_clone_range_spec(const void *src, void *dest, const void *arg) { const ucs_range_spec_t *src_range_spec = src; ucs_range_spec_t *dest_ragne_spec = dest; dest_ragne_spec->first = src_range_spec->first; dest_ragne_spec->last = src_range_spec->last; return UCS_OK; } int ucs_config_sscanf_array(const char *buf, void *dest, const void *arg) { ucs_config_array_field_t *field = dest; void *temp_field; const ucs_config_array_t *array = arg; char *dup, *token, *saveptr; int ret; unsigned i; dup = strdup(buf); if (dup == NULL) { return 0; } saveptr = NULL; token = strtok_r(dup, ",", &saveptr); temp_field = ucs_calloc(UCS_CONFIG_ARRAY_MAX, array->elem_size, "config array"); i = 0; while (token != NULL) { ret = array->parser.read(token, (char*)temp_field + i * array->elem_size, array->parser.arg); if (!ret) { ucs_free(temp_field); free(dup); return 0; } ++i; if (i >= UCS_CONFIG_ARRAY_MAX) { break; } token = strtok_r(NULL, ",", &saveptr); } field->data = temp_field; field->count = i; free(dup); return 1; } int ucs_config_sprintf_array(char *buf, size_t max, const void *src, const void *arg) { const ucs_config_array_field_t *field = src; const ucs_config_array_t *array = arg; size_t offset; unsigned i; int ret; offset = 0; for (i = 0; i < field->count; ++i) { if (i > 0 && offset < max) { buf[offset++] = ','; } ret = array->parser.write(buf + offset, max - offset, (char*)field->data + i * array->elem_size, array->parser.arg); if (!ret) { return 0; } offset += strlen(buf + offset); } return 1; } ucs_status_t ucs_config_clone_array(const void *src, void *dest, const void *arg) { const ucs_config_array_field_t *src_array = src; const ucs_config_array_t *array = arg; ucs_config_array_field_t *dest_array = dest; ucs_status_t status; unsigned i; dest_array->data = ucs_calloc(src_array->count, array->elem_size, "config array"); if (dest_array->data == NULL) { return UCS_ERR_NO_MEMORY; } dest_array->count = src_array->count; for (i = 0; i < src_array->count; ++i) { status = array->parser.clone((const char*)src_array->data + i * array->elem_size, (char*)dest_array->data + i * array->elem_size, array->parser.arg); if (status != UCS_OK) { ucs_free(dest_array->data); return status; } } return UCS_OK; } void ucs_config_release_array(void *ptr, const void *arg) { ucs_config_array_field_t *array_field = ptr; const ucs_config_array_t *array = arg; unsigned i; for (i = 0; i < array_field->count; ++i) { array->parser.release((char*)array_field->data + i * array->elem_size, array->parser.arg); } ucs_free(array_field->data); } void ucs_config_help_array(char *buf, size_t max, const void *arg) { const ucs_config_array_t *array = arg; snprintf(buf, max, "comma-separated list of: "); array->parser.help(buf + strlen(buf), max - strlen(buf), array->parser.arg); } int ucs_config_sscanf_table(const char *buf, void *dest, const void *arg) { char *tokens; char *token, *saveptr1; char *name, *value, *saveptr2; ucs_status_t status; tokens = strdup(buf); if (tokens == NULL) { return 0; } saveptr1 = NULL; saveptr2 = NULL; token = strtok_r(tokens, ";", &saveptr1); while (token != NULL) { name = strtok_r(token, "=", &saveptr2); value = strtok_r(NULL, "=", &saveptr2); if (name == NULL || value == NULL) { free(tokens); ucs_error("Could not parse list of values in '%s' (token: '%s')", buf, token); return 0; } status = ucs_config_parser_set_value_internal(dest, (ucs_config_field_t*)arg, name, value, NULL, 1); if (status != UCS_OK) { if (status == UCS_ERR_NO_ELEM) { ucs_error("Field '%s' does not exist", name); } else { ucs_debug("Failed to set %s to '%s': %s", name, value, ucs_status_string(status)); } free(tokens); return 0; } token = strtok_r(NULL, ";", &saveptr1); } free(tokens); return 1; } ucs_status_t ucs_config_clone_table(const void *src, void *dst, const void *arg) { return ucs_config_parser_clone_opts(src, dst, (ucs_config_field_t*)arg); } void ucs_config_release_table(void *ptr, const void *arg) { ucs_config_parser_release_opts(ptr, (ucs_config_field_t*)arg); } void ucs_config_help_table(char *buf, size_t max, const void *arg) { snprintf(buf, max, "Table"); } void ucs_config_release_nop(void *ptr, const void *arg) { } void ucs_config_help_generic(char *buf, size_t max, const void *arg) { strncpy(buf, (char*)arg, max); } static inline int ucs_config_is_deprecated_field(const ucs_config_field_t *field) { return (field->offset == UCS_CONFIG_DEPRECATED_FIELD_OFFSET); } static inline int ucs_config_is_alias_field(const ucs_config_field_t *field) { return (field->dfl_value == NULL); } static inline int ucs_config_is_table_field(const ucs_config_field_t *field) { return (field->parser.read == ucs_config_sscanf_table); } static void ucs_config_print_doc_line_by_line(const ucs_config_field_t *field, void (*cb)(int num, const char *line, void *arg), void *arg) { char *doc, *line, *p; int num; line = doc = strdup(field->doc); p = strchr(line, '\n'); num = 0; while (p != NULL) { *p = '\0'; cb(num, line, arg); line = p + 1; p = strchr(line, '\n'); ++num; } cb(num, line, arg); free(doc); } static ucs_status_t ucs_config_parser_parse_field(ucs_config_field_t *field, const char *value, void *var) { char syntax_buf[256]; int ret; ret = field->parser.read(value, var, field->parser.arg); if (ret != 1) { if (ucs_config_is_table_field(field)) { ucs_error("Could not set table value for %s: '%s'", field->name, value); } else { field->parser.help(syntax_buf, sizeof(syntax_buf) - 1, field->parser.arg); ucs_error("Invalid value for %s: '%s'. Expected: %s", field->name, value, syntax_buf); } return UCS_ERR_INVALID_PARAM; } return UCS_OK; } static void ucs_config_parser_release_field(ucs_config_field_t *field, void *var) { field->parser.release(var, field->parser.arg); } static int ucs_config_field_is_last(const ucs_config_field_t *field) { return field->name == NULL; } ucs_status_t ucs_config_parser_set_default_values(void *opts, ucs_config_field_t *fields) { ucs_config_field_t *field, *sub_fields; ucs_status_t status; void *var; for (field = fields; !ucs_config_field_is_last(field); ++field) { if (ucs_config_is_alias_field(field) || ucs_config_is_deprecated_field(field)) { continue; } var = (char*)opts + field->offset; /* If this field is a sub-table, recursively set the values for it. * Defaults can be subsequently set by parser.read(). */ if (ucs_config_is_table_field(field)) { sub_fields = (ucs_config_field_t*)field->parser.arg; status = ucs_config_parser_set_default_values(var, sub_fields); if (status != UCS_OK) { return status; } } status = ucs_config_parser_parse_field(field, field->dfl_value, var); if (status != UCS_OK) { return status; } } return UCS_OK; } /** * table_prefix == NULL -> unused */ static ucs_status_t ucs_config_parser_set_value_internal(void *opts, ucs_config_field_t *fields, const char *name, const char *value, const char *table_prefix, int recurse) { ucs_config_field_t *field, *sub_fields; size_t prefix_len; ucs_status_t status; unsigned count; void *var; prefix_len = (table_prefix == NULL) ? 0 : strlen(table_prefix); count = 0; for (field = fields; !ucs_config_field_is_last(field); ++field) { var = (char*)opts + field->offset; if (ucs_config_is_table_field(field)) { sub_fields = (ucs_config_field_t*)field->parser.arg; /* Check with sub-table prefix */ if (recurse) { status = ucs_config_parser_set_value_internal(var, sub_fields, name, value, field->name, 1); if (status == UCS_OK) { ++count; } else if (status != UCS_ERR_NO_ELEM) { return status; } } /* Possible override with my prefix */ if (table_prefix != NULL) { status = ucs_config_parser_set_value_internal(var, sub_fields, name, value, table_prefix, 0); if (status == UCS_OK) { ++count; } else if (status != UCS_ERR_NO_ELEM) { return status; } } } else if (((table_prefix == NULL) || !strncmp(name, table_prefix, prefix_len)) && !strcmp(name + prefix_len, field->name)) { if (ucs_config_is_deprecated_field(field)) { return UCS_ERR_NO_ELEM; } ucs_config_parser_release_field(field, var); status = ucs_config_parser_parse_field(field, value, var); if (status != UCS_OK) { return status; } ++count; } } return (count == 0) ? UCS_ERR_NO_ELEM : UCS_OK; } static void ucs_config_parser_mark_env_var_used(const char *name, int *added) { khiter_t iter; char *key; int ret; *added = 0; if (!ucs_global_opts.warn_unused_env_vars) { return; } pthread_mutex_lock(&ucs_config_parser_env_vars_hash_lock); iter = kh_get(ucs_config_env_vars, &ucs_config_parser_env_vars, name); if (iter != kh_end(&ucs_config_parser_env_vars)) { goto out; /* already exists */ } key = ucs_strdup(name, "config_parser_env_var"); if (key == NULL) { ucs_error("strdup(%s) failed", name); goto out; } #ifndef __clang_analyzer__ /* Exclude this code from Clang examination as it generates * false-postive warning about potential leak of memory * pointed to by 'key' variable */ iter = kh_put(ucs_config_env_vars, &ucs_config_parser_env_vars, key, &ret); if ((ret <= 0) || (iter == kh_end(&ucs_config_parser_env_vars))) { ucs_warn("kh_put(key=%s) failed", key); ucs_free(key); goto out; } #else ucs_free(key); #endif *added = 1; out: pthread_mutex_unlock(&ucs_config_parser_env_vars_hash_lock); } static ucs_status_t ucs_config_apply_env_vars(void *opts, ucs_config_field_t *fields, const char *prefix, const char *table_prefix, int recurse, int ignore_errors) { ucs_config_field_t *field, *sub_fields; ucs_status_t status; size_t prefix_len; const char *env_value; void *var; char buf[256]; int added; /* Put prefix in the buffer. Later we replace only the variable name part */ snprintf(buf, sizeof(buf) - 1, "%s%s", prefix, table_prefix ? table_prefix : ""); prefix_len = strlen(buf); /* Parse environment variables */ for (field = fields; !ucs_config_field_is_last(field); ++field) { var = (char*)opts + field->offset; if (ucs_config_is_table_field(field)) { sub_fields = (ucs_config_field_t*)field->parser.arg; /* Parse with sub-table prefix */ if (recurse) { status = ucs_config_apply_env_vars(var, sub_fields, prefix, field->name, 1, ignore_errors); if (status != UCS_OK) { return status; } } /* Possible override with my prefix */ if (table_prefix) { status = ucs_config_apply_env_vars(var, sub_fields, prefix, table_prefix, 0, ignore_errors); if (status != UCS_OK) { return status; } } } else { /* Read and parse environment variable */ strncpy(buf + prefix_len, field->name, sizeof(buf) - prefix_len - 1); env_value = getenv(buf); if (env_value == NULL) { continue; } ucs_config_parser_mark_env_var_used(buf, &added); if (ucs_config_is_deprecated_field(field)) { if (added && !ignore_errors) { ucs_warn("%s is deprecated (set %s%s=n to suppress this warning)", buf, UCS_CONFIG_PREFIX, UCS_GLOBAL_OPTS_WARN_UNUSED_CONFIG); } } else { ucs_config_parser_release_field(field, var); status = ucs_config_parser_parse_field(field, env_value, var); if (status != UCS_OK) { /* If set to ignore errors, restore the default value */ ucs_status_t tmp_status = ucs_config_parser_parse_field(field, field->dfl_value, var); if (ignore_errors) { status = tmp_status; } } if (status != UCS_OK) { return status; } } } } return UCS_OK; } ucs_status_t ucs_config_parser_fill_opts(void *opts, ucs_config_field_t *fields, const char *env_prefix, const char *table_prefix, int ignore_errors) { ucs_status_t status; char prefix[128]; /* Set default values */ status = ucs_config_parser_set_default_values(opts, fields); if (status != UCS_OK) { goto err; } /* Apply environment variables */ status = ucs_config_apply_env_vars(opts, fields, UCS_CONFIG_PREFIX, table_prefix, 1, ignore_errors); if (status != UCS_OK) { goto err_free; } /* Apply environment variables with custom prefix */ if ((env_prefix != NULL) && (strlen(env_prefix) > 0)) { snprintf(prefix, sizeof(prefix), "%s%s_", UCS_CONFIG_PREFIX, env_prefix); status = ucs_config_apply_env_vars(opts, fields, prefix, table_prefix, 1, ignore_errors); if (status != UCS_OK) { goto err_free; } } return UCS_OK; err_free: ucs_config_parser_release_opts(opts, fields); /* Release default values */ err: return status; } ucs_status_t ucs_config_parser_set_value(void *opts, ucs_config_field_t *fields, const char *name, const char *value) { return ucs_config_parser_set_value_internal(opts, fields, name, value, NULL, 1); } ucs_status_t ucs_config_parser_get_value(void *opts, ucs_config_field_t *fields, const char *name, char *value, size_t max) { ucs_config_field_t *field; ucs_config_field_t *sub_fields; void *sub_opts; void *value_ptr; size_t name_len; ucs_status_t status; if (!opts || !fields || !name || (!value && (max > 0))) { return UCS_ERR_INVALID_PARAM; } for (field = fields, status = UCS_ERR_NO_ELEM; !ucs_config_field_is_last(field) && (status == UCS_ERR_NO_ELEM); ++field) { name_len = strlen(field->name); ucs_trace("compare name \"%s\" with field \"%s\" which is %s subtable", name, field->name, ucs_config_is_table_field(field) ? "a" : "NOT a"); if (ucs_config_is_table_field(field) && !strncmp(field->name, name, name_len)) { sub_fields = (ucs_config_field_t*)field->parser.arg; sub_opts = (char*)opts + field->offset; status = ucs_config_parser_get_value(sub_opts, sub_fields, name + name_len, value, max); } else if (!strncmp(field->name, name, strlen(name))) { if (value) { value_ptr = (char *)opts + field->offset; field->parser.write(value, max, value_ptr, field->parser.arg); } status = UCS_OK; } } return status; } ucs_status_t ucs_config_parser_clone_opts(const void *src, void *dst, ucs_config_field_t *fields) { ucs_status_t status; ucs_config_field_t *field; for (field = fields; !ucs_config_field_is_last(field); ++field) { if (ucs_config_is_alias_field(field) || ucs_config_is_deprecated_field(field)) { continue; } status = field->parser.clone((const char*)src + field->offset, (char*)dst + field->offset, field->parser.arg); if (status != UCS_OK) { ucs_error("Failed to clone the filed '%s': %s", field->name, ucs_status_string(status)); return status; } } return UCS_OK; } void ucs_config_parser_release_opts(void *opts, ucs_config_field_t *fields) { ucs_config_field_t *field; for (field = fields; !ucs_config_field_is_last(field); ++field) { if (ucs_config_is_alias_field(field) || ucs_config_is_deprecated_field(field)) { continue; } ucs_config_parser_release_field(field, (char*)opts + field->offset); } } /* * Finds the "real" field, which the given field is alias of. * *p_alias_table_offset is filled with the offset of the sub-table containing * the field, it may be non-0 if the alias is found in a sub-table. */ static const ucs_config_field_t * ucs_config_find_aliased_field(const ucs_config_field_t *fields, const ucs_config_field_t *alias, size_t *p_alias_table_offset) { const ucs_config_field_t *field, *result; size_t offset; for (field = fields; !ucs_config_field_is_last(field); ++field) { if (field == alias) { /* skip */ continue; } else if (ucs_config_is_table_field(field)) { result = ucs_config_find_aliased_field(field->parser.arg, alias, &offset); if (result != NULL) { *p_alias_table_offset = offset + field->offset; return result; } } else if (field->offset == alias->offset) { *p_alias_table_offset = 0; return field; } } return NULL; } static void __print_stream_cb(int num, const char *line, void *arg) { FILE *stream = arg; fprintf(stream, "# %s\n", line); } static void ucs_config_parser_print_field(FILE *stream, const void *opts, const char *env_prefix, ucs_list_link_t *prefix_list, const char *name, const ucs_config_field_t *field, unsigned long flags, const char *docstr, ...) { ucs_config_parser_prefix_t *prefix, *head; char value_buf[128] = {0}; char syntax_buf[256] = {0}; va_list ap; ucs_assert(!ucs_list_is_empty(prefix_list)); head = ucs_list_head(prefix_list, ucs_config_parser_prefix_t, list); if (ucs_config_is_deprecated_field(field)) { snprintf(value_buf, sizeof(value_buf), " (deprecated)"); snprintf(syntax_buf, sizeof(syntax_buf), "N/A"); } else { snprintf(value_buf, sizeof(value_buf), "="); field->parser.write(value_buf + 1, sizeof(value_buf) - 2, (char*)opts + field->offset, field->parser.arg); field->parser.help(syntax_buf, sizeof(syntax_buf) - 1, field->parser.arg); } if (flags & UCS_CONFIG_PRINT_DOC) { fprintf(stream, "#\n"); ucs_config_print_doc_line_by_line(field, __print_stream_cb, stream); fprintf(stream, "#\n"); fprintf(stream, "# %-*s %s\n", UCS_CONFIG_PARSER_DOCSTR_WIDTH, "syntax:", syntax_buf); /* Extra docstring */ if (docstr != NULL) { fprintf(stream, "# "); va_start(ap, docstr); vfprintf(stream, docstr, ap); va_end(ap); fprintf(stream, "\n"); } /* Parents in configuration hierarchy */ if (prefix_list->next != prefix_list->prev) { fprintf(stream, "# %-*s", UCS_CONFIG_PARSER_DOCSTR_WIDTH, "inherits:"); ucs_list_for_each(prefix, prefix_list, list) { if (prefix == head) { continue; } fprintf(stream, " %s%s%s", env_prefix, prefix->prefix, name); if (prefix != ucs_list_tail(prefix_list, ucs_config_parser_prefix_t, list)) { fprintf(stream, ","); } } fprintf(stream, "\n"); } fprintf(stream, "#\n"); } fprintf(stream, "%s%s%s%s\n", env_prefix, head->prefix, name, value_buf); if (flags & UCS_CONFIG_PRINT_DOC) { fprintf(stream, "\n"); } } static void ucs_config_parser_print_opts_recurs(FILE *stream, const void *opts, const ucs_config_field_t *fields, unsigned flags, const char *env_prefix, ucs_list_link_t *prefix_list) { const ucs_config_field_t *field, *aliased_field; ucs_config_parser_prefix_t *head; ucs_config_parser_prefix_t inner_prefix; size_t alias_table_offset; for (field = fields; !ucs_config_field_is_last(field); ++field) { if (ucs_config_is_table_field(field)) { /* Parse with sub-table prefix. * We start the leaf prefix and continue up the hierarchy. */ /* Do not add the same prefix several times in a sequence. It can * happen when similiar prefix names were used during config * table inheritance, e.g. "IB_" -> "RC_" -> "RC_". We check the * previous entry only, since it is currently impossible if * something like "RC_" -> "IB_" -> "RC_" will be used. */ if (ucs_list_is_empty(prefix_list) || strcmp(ucs_list_tail(prefix_list, ucs_config_parser_prefix_t, list)->prefix, field->name)) { inner_prefix.prefix = field->name; ucs_list_add_tail(prefix_list, &inner_prefix.list); } else { inner_prefix.prefix = NULL; } ucs_config_parser_print_opts_recurs(stream, UCS_PTR_BYTE_OFFSET(opts, field->offset), field->parser.arg, flags, env_prefix, prefix_list); if (inner_prefix.prefix != NULL) { ucs_list_del(&inner_prefix.list); } } else if (ucs_config_is_alias_field(field)) { if (flags & UCS_CONFIG_PRINT_HIDDEN) { aliased_field = ucs_config_find_aliased_field(fields, field, &alias_table_offset); if (aliased_field == NULL) { ucs_fatal("could not find aliased field of %s", field->name); } head = ucs_list_head(prefix_list, ucs_config_parser_prefix_t, list); ucs_config_parser_print_field(stream, UCS_PTR_BYTE_OFFSET(opts, alias_table_offset), env_prefix, prefix_list, field->name, aliased_field, flags, "%-*s %s%s%s", UCS_CONFIG_PARSER_DOCSTR_WIDTH, "alias of:", env_prefix, head->prefix, aliased_field->name); } } else { if (ucs_config_is_deprecated_field(field) && !(flags & UCS_CONFIG_PRINT_HIDDEN)) { continue; } ucs_config_parser_print_field(stream, opts, env_prefix, prefix_list, field->name, field, flags, NULL); } } } void ucs_config_parser_print_opts(FILE *stream, const char *title, const void *opts, ucs_config_field_t *fields, const char *table_prefix, ucs_config_print_flags_t flags) { ucs_config_parser_prefix_t table_prefix_elem; UCS_LIST_HEAD(prefix_list); if (flags & UCS_CONFIG_PRINT_HEADER) { fprintf(stream, "\n"); fprintf(stream, "#\n"); fprintf(stream, "# %s\n", title); fprintf(stream, "#\n"); fprintf(stream, "\n"); } if (flags & UCS_CONFIG_PRINT_CONFIG) { table_prefix_elem.prefix = table_prefix ? table_prefix : ""; ucs_list_add_tail(&prefix_list, &table_prefix_elem.list); ucs_config_parser_print_opts_recurs(stream, opts, fields, flags, UCS_CONFIG_PREFIX, &prefix_list); } if (flags & UCS_CONFIG_PRINT_HEADER) { fprintf(stream, "\n"); } } void ucs_config_parser_print_all_opts(FILE *stream, ucs_config_print_flags_t flags) { const ucs_config_global_list_entry_t *entry; ucs_status_t status; char title[64]; void *opts; ucs_list_for_each(entry, &ucs_config_global_list, list) { if ((entry->table == NULL) || (ucs_config_field_is_last(&entry->table[0]))) { /* don't print title for an empty configuration table */ continue; } opts = ucs_malloc(entry->size, "tmp_opts"); if (opts == NULL) { ucs_error("could not allocate configuration of size %zu", entry->size); continue; } status = ucs_config_parser_fill_opts(opts, entry->table, NULL, entry->prefix, 0); if (status != UCS_OK) { ucs_free(opts); continue; } snprintf(title, sizeof(title), "%s configuration", entry->name); ucs_config_parser_print_opts(stream, title, opts, entry->table, entry->prefix, flags); ucs_config_parser_release_opts(opts, entry->table); ucs_free(opts); } } void ucs_config_parser_warn_unused_env_vars_once() { static uint32_t warn_once = 1; if (!ucs_atomic_cswap32(&warn_once, 1, 0)) { return; } ucs_config_parser_warn_unused_env_vars(); } void ucs_config_parser_warn_unused_env_vars() { char unused_env_vars_names[40]; int num_unused_vars; char **envp, *envstr; size_t prefix_len; char *var_name; char *p, *endp; khiter_t iter; char *saveptr; int truncated; int ret; if (!ucs_global_opts.warn_unused_env_vars) { return; } pthread_mutex_lock(&ucs_config_parser_env_vars_hash_lock); prefix_len = strlen(UCS_CONFIG_PREFIX); p = unused_env_vars_names; endp = p + sizeof(unused_env_vars_names) - 1; *endp = '\0'; truncated = 0; num_unused_vars = 0; for (envp = environ; !truncated && (*envp != NULL); ++envp) { envstr = ucs_strdup(*envp, "env_str"); if (envstr == NULL) { continue; } var_name = strtok_r(envstr, "=", &saveptr); if (!var_name || strncmp(var_name, UCS_CONFIG_PREFIX, prefix_len)) { ucs_free(envstr); continue; /* Not UCX */ } iter = kh_get(ucs_config_env_vars, &ucs_config_parser_env_vars, var_name); if (iter == kh_end(&ucs_config_parser_env_vars)) { ret = snprintf(p, endp - p, " %s,", var_name); if (ret > endp - p) { truncated = 1; *p = '\0'; } else { p += strlen(p); ++num_unused_vars; } } ucs_free(envstr); } if (num_unused_vars > 0) { if (!truncated) { p[-1] = '\0'; /* remove trailing comma */ } ucs_warn("unused env variable%s:%s%s (set %s%s=n to suppress this warning)", (num_unused_vars > 1) ? "s" : "", unused_env_vars_names, truncated ? "..." : "", UCS_CONFIG_PREFIX, UCS_GLOBAL_OPTS_WARN_UNUSED_CONFIG); } pthread_mutex_unlock(&ucs_config_parser_env_vars_hash_lock); } size_t ucs_config_memunits_get(size_t config_size, size_t auto_size, size_t max_size) { if (config_size == UCS_MEMUNITS_AUTO) { return auto_size; } else { return ucs_min(config_size, max_size); } } int ucs_config_names_search(ucs_config_names_array_t config_names, const char *str) { unsigned i; for (i = 0; i < config_names.count; ++i) { if (!fnmatch(config_names.names[i], str, 0)) { return i; } } return -1; } UCS_STATIC_CLEANUP { const char *key; kh_foreach_key(&ucs_config_parser_env_vars, key, { ucs_free((void*)key); }) kh_destroy_inplace(ucs_config_env_vars, &ucs_config_parser_env_vars); }