/**
* 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 <ucs/arch/atomic.h>
#include <ucs/sys/sys.h>
#include <ucs/sys/string.h>
#include <ucs/datastruct/list.h>
#include <ucs/datastruct/khash.h>
#include <ucs/debug/assert.h>
#include <ucs/debug/log.h>
#include <ucs/debug/debug.h>
#include <ucs/time/time.h>
#include <fnmatch.h>
#include <ctype.h>
/* 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);
}