/** * \file conf.c * \ingroup Configuration * \brief Configuration helper functions * \author Abramo Bagnara * \author Jaroslav Kysela * \date 2000-2001 * * Tree based, full nesting configuration functions. * * See the \ref conf page for more details. */ /* * Configuration helper functions * Copyright (c) 2000 by Abramo Bagnara , * Jaroslav Kysela * * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ /*! \page conf Configuration files

Configuration files use a simple format allowing modern data description like nesting and array assignments.

\section conf_whitespace Whitespace Whitespace is the collective name given to spaces (blanks), horizontal and vertical tabs, newline characters, and comments. Whitespace can indicate where configuration tokens start and end, but beyond this function, any surplus whitespace is discarded. For example, the two sequences \code a 1 b 2 \endcode and \code a 1 b 2 \endcode are lexically equivalent and parse identically to give the four tokens: \code a 1 b 2 \endcode The ASCII characters representing whitespace can occur within literal strings, in which case they are protected from the normal parsing process (they remain as part of the string). For example: \code name "John Smith" \endcode parses to two tokens, including the single literal-string token "John Smith". \section conf_linesplicing Line continuation with \ A special case occurs if a newline character in a string is preceded by a backslash (\). The backslash and the new line are both discarded, allowing two physical lines of text to be treated as one unit. \code "John \ Smith" \endcode is parsed as "John Smith". \section conf_comments Comments A single-line comment begins with the character #. The comment can start at any position, and extends to the end of the line. \code a 1 # this is a comment \endcode \section conf_include Including configuration files To include another configuration file, write the file name in angle brackets. The prefix \c confdir: will reference the global configuration directory. \code \endcode \section conf_punctuators Punctuators The configuration punctuators (also known as separators) are: \code {} [] , ; = . ' " new-line form-feed carriage-return whitespace \endcode \subsection conf_braces Braces Opening and closing braces { } indicate the start and end of a compound statement: \code a { b 1 } \endcode \subsection conf_brackets Brackets Opening and closing brackets indicate a single array definition. The identifiers are automatically generated starting with zero. \code a [ "first" "second" ] \endcode The above code is equal to \code a.0 "first" a.1 "second" \endcode \subsection conf_comma_semicolon Comma and semicolon The comma (,) or semicolon (;) can separate value assignments. It is not strictly required to use these separators because whitespace suffices to separate tokens. \code a 1; b 1, \endcode \subsection conf_equal Equal sign The equal sign (=) can separate variable declarations from initialization lists: \code a=1 b=2 \endcode Using equal signs is not required because whitespace suffices to separate tokens. \section conf_assigns Assignments The configuration file defines id (key) and value pairs. The id (key) can be composed from ASCII digits, characters from a to z and A to Z, and the underscore (_). The value can be either a string, an integer, a real number, or a compound statement. \subsection conf_single Single assignments \code a 1 # is equal to a=1 # is equal to a=1; # is equal to a 1, \endcode \subsection conf_compound Compound assignments (definitions using braces) \code a { b = 1 } a={ b 1, } \endcode \section conf_compound1 Compound assignments (one key definitions) \code a.b 1 a.b=1 \endcode \subsection conf_array Array assignments (definitions using brackets) \code a [ "first" "second" ] \endcode \subsection conf_array1 Array assignments (one key definitions) \code a.0 "first" a.1 "second" \endcode \section conf_mode Operation modes for parsing nodes By default, the node operation mode is 'merge+create', i.e., if a configuration node is not present a new one is created, otherwise the latest assignment is merged (if possible - type checking). The 'merge+create' operation mode is specified with the prefix character plus (+). The operation mode 'merge' merges the node with the old one (which must exist). Type checking is done, so strings cannot be assigned to integers and so on. This mode is specified with the prefix character minus (-). The operation mode 'do not override' ignores a new configuration node if a configuration node with the same name exists. This mode is specified with the prefix character question mark (?). The operation mode 'override' always overrides the old configuration node with new contents. This mode is specified with the prefix character exclamation mark (!). \code defaults.pcm.!device 1 \endcode \section conf_syntax_summary Syntax summary \code # Configuration file syntax # Include a new configuration file # Simple assignment name [=] value [,|;] # Compound assignment (first style) name [=] { name1 [=] value [,|;] ... } # Compound assignment (second style) name.name1 [=] value [,|;] # Array assignment (first style) name [ value0 [,|;] value1 [,|;] ... ] # Array assignment (second style) name.0 [=] value0 [,|;] name.1 [=] value1 [,|;] \endcode \section conf_syntax_ref References \ref confarg \ref conffunc \ref confhooks */ /*! \page confarg Runtime arguments in configuration files

The ALSA library can accept runtime arguments for some configuration blocks. This extension is built on top of the basic configuration file syntax.

\section confarg_define Defining arguments Arguments are defined using the id (key) \c \@args and array values containing the string names of the arguments: \code @args [ CARD ] # or @args.0 CARD \endcode \section confarg_type Defining argument types and default values An argument's type is specified with the id (key) \c \@args and the argument name. The type and the default value are specified in the compound block: \code @args.CARD { type string default "abcd" } \endcode \section confarg_refer Referring to arguments Arguments are referred to with a dollar-sign ($) and the name of the argument: \code card $CARD \endcode \section confarg_usage Usage To use a block with arguments, write the argument values after the key, separated with a colon (:). For example, all these names for PCM interfaces give the same result: \code hw:0,1 hw:CARD=0,DEV=1 hw:{CARD 0 DEV 1} plug:"hw:0,1" plug:{SLAVE="hw:{CARD 0 DEV 1}"} \endcode As you see, arguments can be specified in their proper order or by name. Note that arguments enclosed in braces are parsed in the same way as in configuration files, but using the override method by default. \section confarg_example Example \code pcm.demo { @args [ CARD DEVICE ] @args.CARD { type string default "supersonic" } @args.DEVICE { type integer default 0 } type hw card $CARD device $DEVICE } \endcode */ /*! \page conffunc Runtime functions in configuration files

The ALSA library can modify the configuration at runtime. Several built-in functions are available.

A function is defined with the id \c \@func and the function name. All other values in the current compound are used as configuration for the function. If the compound func.\ is defined in the root node, then the library and function from this compound configuration are used, otherwise 'snd_func_' is prefixed to the string and code from the ALSA library is used. The definition of a function looks like:

\code func.remove_first_char { lib "/usr/lib/libasoundextend.so" func "extend_remove_first_char" } \endcode */ /*! \page confhooks Hooks in configuration files

The hook extension in the ALSA library allows expansion of configuration nodes at run-time. The existence of a hook is determined by the presence of a \@hooks compound node.

This example defines a hook which loads two configuration files at the beginning:

\code @hooks [ { func load files [ "/etc/asound.conf" "~/.asoundrc" ] errors false } ] \endcode \section confhooks_ref Function reference
  • The function load - \c snd_config_hook_load() - loads and parses the given configuration files.
  • The function load_for_all_cards - \c snd_config_hook_load_for_all_cards() - loads and parses the given configuration files for each installed sound card. The driver name (the type of the sound card) is passed in the private configuration node.
*/ #include "local.h" #include #include #include #include #include #include #ifdef HAVE_LIBPTHREAD #include #endif #ifndef DOC_HIDDEN #ifdef HAVE_LIBPTHREAD static pthread_mutex_t snd_config_update_mutex; static pthread_once_t snd_config_update_mutex_once = PTHREAD_ONCE_INIT; #endif struct _snd_config { char *id; snd_config_type_t type; int refcount; /* default = 0 */ union { long integer; long long integer64; char *string; double real; const void *ptr; struct { struct list_head fields; bool join; } compound; } u; struct list_head list; snd_config_t *parent; int hop; }; struct filedesc { char *name; snd_input_t *in; unsigned int line, column; struct filedesc *next; /* list of the include paths (configuration directories), * defined by , * for searching its included files. */ struct list_head include_paths; }; /* path to search included files */ struct include_path { char *dir; struct list_head list; }; #define LOCAL_ERROR (-0x68000000) #define LOCAL_UNTERMINATED_STRING (LOCAL_ERROR - 0) #define LOCAL_UNTERMINATED_QUOTE (LOCAL_ERROR - 1) #define LOCAL_UNEXPECTED_CHAR (LOCAL_ERROR - 2) #define LOCAL_UNEXPECTED_EOF (LOCAL_ERROR - 3) typedef struct { struct filedesc *current; int unget; int ch; } input_t; #ifdef HAVE_LIBPTHREAD static void snd_config_init_mutex(void) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); #ifdef HAVE_PTHREAD_MUTEX_RECURSIVE pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); #endif pthread_mutex_init(&snd_config_update_mutex, &attr); pthread_mutexattr_destroy(&attr); } static inline void snd_config_lock(void) { pthread_once(&snd_config_update_mutex_once, snd_config_init_mutex); pthread_mutex_lock(&snd_config_update_mutex); } static inline void snd_config_unlock(void) { pthread_mutex_unlock(&snd_config_update_mutex); } #else static inline void snd_config_lock(void) { } static inline void snd_config_unlock(void) { } #endif /* * Add a diretory to the paths to search included files. * param fd - File object that owns these paths to search files included by it. * param dir - Path of the directory to add. Allocated externally and need to * be freed manually later. * return - Zero if successful, otherwise a negative error code. * * The direcotry should be a subdiretory of top configuration directory * "/usr/share/alsa/". */ static int add_include_path(struct filedesc *fd, const char *dir) { struct include_path *path; struct filedesc *fd1; struct list_head *pos; /* check, if dir is already registered (also in parents) */ for (fd1 = fd; fd1; fd1 = fd1->next) { list_for_each(pos, &fd1->include_paths) { path = list_entry(pos, struct include_path, list); if (strcmp(path->dir, dir) == 0) return 0; } } path = calloc(1, sizeof(*path)); if (!path) return -ENOMEM; path->dir = strdup(dir); if (path->dir == NULL) { free(path); return -ENOMEM; } list_add_tail(&path->list, &fd->include_paths); return 0; } /* * Free all include paths of a file descriptor. * param fd - File object that owns these paths to search files included by it. */ static void free_include_paths(struct filedesc *fd) { struct list_head *pos, *npos, *base; struct include_path *path; base = &fd->include_paths; list_for_each_safe(pos, npos, base) { path = list_entry(pos, struct include_path, list); list_del(&path->list); if (path->dir) free(path->dir); free(path); } } /** * \brief Returns the default top-level config directory * \return The top-level config directory path string * * This function returns the string of the top-level config directory path. * If the path is specified via the environment variable \c ALSA_CONFIG_DIR * and the value is a valid path, it returns this value. If unspecified, it * returns the default value, "/usr/share/alsa". */ const char *snd_config_topdir(void) { static char *topdir; if (!topdir) { topdir = getenv("ALSA_CONFIG_DIR"); if (!topdir || *topdir != '/' || strlen(topdir) >= PATH_MAX) topdir = ALSA_CONFIG_DIR; } return topdir; } static char *_snd_config_path(const char *name) { const char *root = snd_config_topdir(); char *path = malloc(strlen(root) + strlen(name) + 2); if (!path) return NULL; sprintf(path, "%s/%s", root, name); return path; } /* * Search and open a file, and creates a new input object reading from the file. * param inputp - The functions puts the pointer to the new input object * at the address specified by \p inputp. * param file - Name of the configuration file. * param include_paths - Optional, addtional directories to search the file. * return - Zero if successful, otherwise a negative error code. * * This function will search and open the file in the following order * of priority: * 1. directly open the file by its name (only if absolute) * 2. search for the file name in in additional configuration directories * specified by users, via alsaconf syntax * ; * These directories should be subdirectories of /usr/share/alsa. */ static int input_stdio_open(snd_input_t **inputp, const char *file, struct filedesc *current) { struct list_head *pos; struct include_path *path; char full_path[PATH_MAX]; int err; if (file[0] == '/') return snd_input_stdio_open(inputp, file, "r"); /* search file in user specified include paths. These directories * are subdirectories of /usr/share/alsa. */ err = -ENOENT; while (current) { list_for_each(pos, ¤t->include_paths) { path = list_entry(pos, struct include_path, list); if (!path->dir) continue; snprintf(full_path, PATH_MAX, "%s/%s", path->dir, file); err = snd_input_stdio_open(inputp, full_path, "r"); if (err == 0) return 0; } current = current->next; } return err; } static int safe_strtoll(const char *str, long long *val) { long long v; int endidx; if (!*str) return -EINVAL; errno = 0; if (sscanf(str, "%lli%n", &v, &endidx) < 1) return -EINVAL; if (str[endidx]) return -EINVAL; *val = v; return 0; } int safe_strtol(const char *str, long *val) { char *end; long v; if (!*str) return -EINVAL; errno = 0; v = strtol(str, &end, 0); if (errno) return -errno; if (*end) return -EINVAL; *val = v; return 0; } static int safe_strtod(const char *str, double *val) { char *end; double v; #ifdef HAVE_USELOCALE locale_t saved_locale, c_locale; #else char *saved_locale; char locstr[64]; /* enough? */ #endif int err; if (!*str) return -EINVAL; #ifdef HAVE_USELOCALE c_locale = newlocale(LC_NUMERIC_MASK, "C", 0); saved_locale = uselocale(c_locale); #else saved_locale = setlocale(LC_NUMERIC, NULL); if (saved_locale) { snprintf(locstr, sizeof(locstr), "%s", saved_locale); setlocale(LC_NUMERIC, "C"); } #endif errno = 0; v = strtod(str, &end); err = -errno; #ifdef HAVE_USELOCALE if (c_locale != (locale_t)0) { uselocale(saved_locale); freelocale(c_locale); } #else if (saved_locale) setlocale(LC_NUMERIC, locstr); #endif if (err) return err; if (*end) return -EINVAL; *val = v; return 0; } static int get_char(input_t *input) { int c; struct filedesc *fd; if (input->unget) { input->unget = 0; return input->ch; } again: fd = input->current; c = snd_input_getc(fd->in); switch (c) { case '\n': fd->column = 0; fd->line++; break; case '\t': fd->column += 8 - fd->column % 8; break; case EOF: if (fd->next) { snd_input_close(fd->in); free(fd->name); input->current = fd->next; free(fd); goto again; } return LOCAL_UNEXPECTED_EOF; default: fd->column++; break; } return (unsigned char)c; } static void unget_char(int c, input_t *input) { assert(!input->unget); input->ch = c; input->unget = 1; } static int get_delimstring(char **string, int delim, input_t *input); static int get_char_skip_comments(input_t *input) { int c; while (1) { c = get_char(input); if (c == '<') { char *str; snd_input_t *in; struct filedesc *fd; DIR *dirp; int err = get_delimstring(&str, '>', input); if (err < 0) return err; if (!strncmp(str, "searchdir:", 10)) { /* directory to search included files */ char *tmp = _snd_config_path(str + 10); free(str); if (tmp == NULL) return -ENOMEM; str = tmp; dirp = opendir(str); if (!dirp) { SNDERR("Invalid search dir %s", str); free(str); return -EINVAL; } closedir(dirp); err = add_include_path(input->current, str); free(str); if (err < 0) { SNDERR("Cannot add search dir %s", str); return err; } continue; } if (!strncmp(str, "confdir:", 8)) { /* file in the specified directory */ char *tmp = _snd_config_path(str + 8); free(str); if (tmp == NULL) return -ENOMEM; str = tmp; err = snd_input_stdio_open(&in, str, "r"); } else { /* absolute or relative file path */ err = input_stdio_open(&in, str, input->current); } if (err < 0) { SNDERR("Cannot access file %s", str); free(str); return err; } fd = malloc(sizeof(*fd)); if (!fd) { free(str); return -ENOMEM; } fd->name = str; fd->in = in; fd->next = input->current; fd->line = 1; fd->column = 0; INIT_LIST_HEAD(&fd->include_paths); input->current = fd; continue; } if (c != '#') break; while (1) { c = get_char(input); if (c < 0) return c; if (c == '\n') break; } } return c; } static int get_nonwhite(input_t *input) { int c; while (1) { c = get_char_skip_comments(input); switch (c) { case ' ': case '\f': case '\t': case '\n': case '\r': break; default: return c; } } } static inline int get_hexachar(input_t *input) { int c, num = 0; c = get_char(input); if (c >= '0' && c <= '9') num |= (c - '0') << 4; else if (c >= 'a' && c <= 'f') num |= (c - 'a') << 4; else if (c >= 'A' && c <= 'F') num |= (c - 'A') << 4; c = get_char(input); if (c >= '0' && c <= '9') num |= (c - '0') << 0; else if (c >= 'a' && c <= 'f') num |= (c - 'a') << 0; else if (c >= 'A' && c <= 'F') num |= (c - 'A') << 0; return c; } static int get_quotedchar(input_t *input) { int c; c = get_char(input); switch (c) { case 'n': return '\n'; case 't': return '\t'; case 'v': return '\v'; case 'b': return '\b'; case 'r': return '\r'; case 'f': return '\f'; case 'x': return get_hexachar(input); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { int num = c - '0'; int i = 1; do { c = get_char(input); if (c < '0' || c > '7') { unget_char(c, input); break; } num = num * 8 + c - '0'; i++; } while (i < 3); return num; } default: return c; } } #define LOCAL_STR_BUFSIZE 64 struct local_string { char *buf; size_t alloc; size_t idx; char tmpbuf[LOCAL_STR_BUFSIZE]; }; static void init_local_string(struct local_string *s) { memset(s, 0, sizeof(*s)); s->buf = s->tmpbuf; s->alloc = LOCAL_STR_BUFSIZE; } static void free_local_string(struct local_string *s) { if (s->buf != s->tmpbuf) free(s->buf); } static int add_char_local_string(struct local_string *s, int c) { if (s->idx >= s->alloc) { size_t nalloc = s->alloc * 2; if (s->buf == s->tmpbuf) { s->buf = malloc(nalloc); if (s->buf == NULL) return -ENOMEM; memcpy(s->buf, s->tmpbuf, s->alloc); } else { char *ptr = realloc(s->buf, nalloc); if (ptr == NULL) return -ENOMEM; s->buf = ptr; } s->alloc = nalloc; } s->buf[s->idx++] = c; return 0; } static char *copy_local_string(struct local_string *s) { char *dst = malloc(s->idx + 1); if (dst) { memcpy(dst, s->buf, s->idx); dst[s->idx] = '\0'; } return dst; } static int get_freestring(char **string, int id, input_t *input) { struct local_string str; int c; init_local_string(&str); while (1) { c = get_char(input); if (c < 0) { if (c == LOCAL_UNEXPECTED_EOF) { *string = copy_local_string(&str); if (! *string) c = -ENOMEM; else c = 0; } break; } switch (c) { case '.': if (!id) break; /* fall through */ case ' ': case '\f': case '\t': case '\n': case '\r': case '=': case ',': case ';': case '{': case '}': case '[': case ']': case '\'': case '"': case '\\': case '#': *string = copy_local_string(&str); if (! *string) c = -ENOMEM; else { unget_char(c, input); c = 0; } goto _out; default: break; } if (add_char_local_string(&str, c) < 0) { c = -ENOMEM; break; } } _out: free_local_string(&str); return c; } static int get_delimstring(char **string, int delim, input_t *input) { struct local_string str; int c; init_local_string(&str); while (1) { c = get_char(input); if (c < 0) break; if (c == '\\') { c = get_quotedchar(input); if (c < 0) break; if (c == '\n') continue; } else if (c == delim) { *string = copy_local_string(&str); if (! *string) c = -ENOMEM; else c = 0; break; } if (add_char_local_string(&str, c) < 0) { c = -ENOMEM; break; } } free_local_string(&str); return c; } /* Return 0 for free string, 1 for delimited string */ static int get_string(char **string, int id, input_t *input) { int c = get_nonwhite(input), err; if (c < 0) return c; switch (c) { case '=': case ',': case ';': case '.': case '{': case '}': case '[': case ']': case '\\': return LOCAL_UNEXPECTED_CHAR; case '\'': case '"': err = get_delimstring(string, c, input); if (err < 0) return err; return 1; default: unget_char(c, input); err = get_freestring(string, id, input); if (err < 0) return err; return 0; } } static int _snd_config_make(snd_config_t **config, char **id, snd_config_type_t type) { snd_config_t *n; assert(config); n = calloc(1, sizeof(*n)); if (n == NULL) { if (*id) { free(*id); *id = NULL; } return -ENOMEM; } if (id) { n->id = *id; *id = NULL; } n->type = type; if (type == SND_CONFIG_TYPE_COMPOUND) INIT_LIST_HEAD(&n->u.compound.fields); *config = n; return 0; } static int _snd_config_make_add(snd_config_t **config, char **id, snd_config_type_t type, snd_config_t *parent) { snd_config_t *n; int err; assert(parent->type == SND_CONFIG_TYPE_COMPOUND); err = _snd_config_make(&n, id, type); if (err < 0) return err; n->parent = parent; list_add_tail(&n->list, &parent->u.compound.fields); *config = n; return 0; } static int _snd_config_search(snd_config_t *config, const char *id, int len, snd_config_t **result) { snd_config_iterator_t i, next; snd_config_for_each(i, next, config) { snd_config_t *n = snd_config_iterator_entry(i); if (len < 0) { if (strcmp(n->id, id) != 0) continue; } else if (strlen(n->id) != (size_t) len || memcmp(n->id, id, (size_t) len) != 0) continue; if (result) *result = n; return 0; } return -ENOENT; } static int parse_value(snd_config_t **_n, snd_config_t *parent, input_t *input, char **id, int skip) { snd_config_t *n = *_n; char *s; int err; err = get_string(&s, 0, input); if (err < 0) return err; if (skip) { free(s); return 0; } if (err == 0 && ((s[0] >= '0' && s[0] <= '9') || s[0] == '-')) { long long i; errno = 0; err = safe_strtoll(s, &i); if (err < 0) { double r; err = safe_strtod(s, &r); if (err >= 0) { free(s); if (n) { if (n->type != SND_CONFIG_TYPE_REAL) { SNDERR("%s is not a real", *id); return -EINVAL; } } else { err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_REAL, parent); if (err < 0) return err; } n->u.real = r; *_n = n; return 0; } } else { free(s); if (n) { if (n->type != SND_CONFIG_TYPE_INTEGER && n->type != SND_CONFIG_TYPE_INTEGER64) { SNDERR("%s is not an integer", *id); return -EINVAL; } } else { if (i <= INT_MAX) err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_INTEGER, parent); else err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_INTEGER64, parent); if (err < 0) return err; } if (n->type == SND_CONFIG_TYPE_INTEGER) n->u.integer = (long) i; else n->u.integer64 = i; *_n = n; return 0; } } if (n) { if (n->type != SND_CONFIG_TYPE_STRING) { SNDERR("%s is not a string", *id); free(s); return -EINVAL; } } else { err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_STRING, parent); if (err < 0) return err; } free(n->u.string); n->u.string = s; *_n = n; return 0; } static int parse_defs(snd_config_t *parent, input_t *input, int skip, int override); static int parse_array_defs(snd_config_t *farther, input_t *input, int skip, int override); static int parse_array_def(snd_config_t *parent, input_t *input, int *idx, int skip, int override) { char *id = NULL; int c; int err; snd_config_t *n = NULL; if (!skip) { snd_config_t *g; char static_id[12]; while (1) { snprintf(static_id, sizeof(static_id), "%i", *idx); if (_snd_config_search(parent, static_id, -1, &g) == 0) { if (override) { snd_config_delete(n); } else { /* merge */ (*idx)++; continue; } } break; } id = strdup(static_id); if (id == NULL) return -ENOMEM; } c = get_nonwhite(input); if (c < 0) { err = c; goto __end; } switch (c) { case '{': case '[': { char endchr; if (!skip) { if (n) { if (n->type != SND_CONFIG_TYPE_COMPOUND) { SNDERR("%s is not a compound", id); err = -EINVAL; goto __end; } } else { err = _snd_config_make_add(&n, &id, SND_CONFIG_TYPE_COMPOUND, parent); if (err < 0) goto __end; } } if (c == '{') { err = parse_defs(n, input, skip, override); endchr = '}'; } else { err = parse_array_defs(n, input, skip, override); endchr = ']'; } c = get_nonwhite(input); if (c < 0) { err = c; goto __end; } if (c != endchr) { if (n) snd_config_delete(n); err = LOCAL_UNEXPECTED_CHAR; goto __end; } break; } default: unget_char(c, input); err = parse_value(&n, parent, input, &id, skip); if (err < 0) goto __end; break; } err = 0; __end: free(id); return err; } static int parse_array_defs(snd_config_t *parent, input_t *input, int skip, int override) { int idx = 0; while (1) { int c = get_nonwhite(input), err; if (c < 0) return c; unget_char(c, input); if (c == ']') return 0; err = parse_array_def(parent, input, &idx, skip, override); if (err < 0) return err; idx++; } return 0; } static int parse_def(snd_config_t *parent, input_t *input, int skip, int override) { char *id = NULL; int c; int err; snd_config_t *n; enum {MERGE_CREATE, MERGE, OVERRIDE, DONT_OVERRIDE} mode; while (1) { c = get_nonwhite(input); if (c < 0) return c; switch (c) { case '+': mode = MERGE_CREATE; break; case '-': mode = MERGE; break; case '?': mode = DONT_OVERRIDE; break; case '!': mode = OVERRIDE; break; default: mode = !override ? MERGE_CREATE : OVERRIDE; unget_char(c, input); } err = get_string(&id, 1, input); if (err < 0) return err; c = get_nonwhite(input); if (c != '.') break; if (skip) { free(id); continue; } if (_snd_config_search(parent, id, -1, &n) == 0) { if (mode == DONT_OVERRIDE) { skip = 1; free(id); continue; } if (mode != OVERRIDE) { if (n->type != SND_CONFIG_TYPE_COMPOUND) { SNDERR("%s is not a compound", id); return -EINVAL; } n->u.compound.join = true; parent = n; free(id); continue; } snd_config_delete(n); } if (mode == MERGE) { SNDERR("%s does not exists", id); err = -ENOENT; goto __end; } err = _snd_config_make_add(&n, &id, SND_CONFIG_TYPE_COMPOUND, parent); if (err < 0) goto __end; n->u.compound.join = true; parent = n; } if (c == '=') { c = get_nonwhite(input); if (c < 0) return c; } if (!skip) { if (_snd_config_search(parent, id, -1, &n) == 0) { if (mode == DONT_OVERRIDE) { skip = 1; n = NULL; } else if (mode == OVERRIDE) { snd_config_delete(n); n = NULL; } } else { n = NULL; if (mode == MERGE) { SNDERR("%s does not exists", id); err = -ENOENT; goto __end; } } } switch (c) { case '{': case '[': { char endchr; if (!skip) { if (n) { if (n->type != SND_CONFIG_TYPE_COMPOUND) { SNDERR("%s is not a compound", id); err = -EINVAL; goto __end; } } else { err = _snd_config_make_add(&n, &id, SND_CONFIG_TYPE_COMPOUND, parent); if (err < 0) goto __end; } } if (c == '{') { err = parse_defs(n, input, skip, override); endchr = '}'; } else { err = parse_array_defs(n, input, skip, override); endchr = ']'; } c = get_nonwhite(input); if (c != endchr) { if (n) snd_config_delete(n); err = LOCAL_UNEXPECTED_CHAR; goto __end; } break; } default: unget_char(c, input); err = parse_value(&n, parent, input, &id, skip); if (err < 0) goto __end; break; } c = get_nonwhite(input); switch (c) { case ';': case ',': break; default: unget_char(c, input); } __end: free(id); return err; } static int parse_defs(snd_config_t *parent, input_t *input, int skip, int override) { int c, err; while (1) { c = get_nonwhite(input); if (c < 0) return c == LOCAL_UNEXPECTED_EOF ? 0 : c; unget_char(c, input); if (c == '}') return 0; err = parse_def(parent, input, skip, override); if (err < 0) return err; } return 0; } static void string_print(char *str, int id, snd_output_t *out) { unsigned char *p = (unsigned char *)str; if (!p || !*p) { snd_output_puts(out, "''"); return; } if (!id) { switch (*p) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': goto quoted; } } loop: switch (*p) { case 0: goto nonquoted; case ' ': case '=': case ';': case ',': case '.': case '{': case '}': case '[': case ']': case '\'': case '"': case '*': case '#': goto quoted; default: if (*p <= 31 || *p >= 127) goto quoted; p++; goto loop; } nonquoted: snd_output_puts(out, str); return; quoted: snd_output_putc(out, '\''); p = (unsigned char *)str; while (*p) { int c; c = *p; switch (c) { case '\n': snd_output_putc(out, '\\'); snd_output_putc(out, 'n'); break; case '\t': snd_output_putc(out, '\\'); snd_output_putc(out, 't'); break; case '\v': snd_output_putc(out, '\\'); snd_output_putc(out, 'v'); break; case '\b': snd_output_putc(out, '\\'); snd_output_putc(out, 'b'); break; case '\r': snd_output_putc(out, '\\'); snd_output_putc(out, 'r'); break; case '\f': snd_output_putc(out, '\\'); snd_output_putc(out, 'f'); break; case '\'': snd_output_putc(out, '\\'); snd_output_putc(out, c); break; default: if (c >= 32 && c <= 126 && c != '\'') snd_output_putc(out, c); else snd_output_printf(out, "\\%04o", c); break; } p++; } snd_output_putc(out, '\''); } static int _snd_config_save_children(snd_config_t *config, snd_output_t *out, unsigned int level, unsigned int joins); static int _snd_config_save_node_value(snd_config_t *n, snd_output_t *out, unsigned int level) { int err; unsigned int k; switch (n->type) { case SND_CONFIG_TYPE_INTEGER: snd_output_printf(out, "%ld", n->u.integer); break; case SND_CONFIG_TYPE_INTEGER64: snd_output_printf(out, "%lld", n->u.integer64); break; case SND_CONFIG_TYPE_REAL: snd_output_printf(out, "%-16g", n->u.real); break; case SND_CONFIG_TYPE_STRING: string_print(n->u.string, 0, out); break; case SND_CONFIG_TYPE_POINTER: SNDERR("cannot save runtime pointer type"); return -EINVAL; case SND_CONFIG_TYPE_COMPOUND: snd_output_putc(out, '{'); snd_output_putc(out, '\n'); err = _snd_config_save_children(n, out, level + 1, 0); if (err < 0) return err; for (k = 0; k < level; ++k) { snd_output_putc(out, '\t'); } snd_output_putc(out, '}'); break; } return 0; } static void id_print(snd_config_t *n, snd_output_t *out, unsigned int joins) { if (joins > 0) { assert(n->parent); id_print(n->parent, out, joins - 1); snd_output_putc(out, '.'); } string_print(n->id, 1, out); } static int _snd_config_save_children(snd_config_t *config, snd_output_t *out, unsigned int level, unsigned int joins) { unsigned int k; int err; snd_config_iterator_t i, next; assert(config && out); snd_config_for_each(i, next, config) { snd_config_t *n = snd_config_iterator_entry(i); if (n->type == SND_CONFIG_TYPE_COMPOUND && n->u.compound.join) { err = _snd_config_save_children(n, out, level, joins + 1); if (err < 0) return err; continue; } for (k = 0; k < level; ++k) { snd_output_putc(out, '\t'); } id_print(n, out, joins); #if 0 snd_output_putc(out, ' '); snd_output_putc(out, '='); #endif snd_output_putc(out, ' '); err = _snd_config_save_node_value(n, out, level); if (err < 0) return err; #if 0 snd_output_putc(out, ';'); #endif snd_output_putc(out, '\n'); } return 0; } #endif /** * \brief Substitutes one configuration node to another. * \param dst Handle to the destination node. * \param src Handle to the source node. Must not be the same as \a dst. * \return Zero if successful, otherwise a negative error code. * * If both nodes are compounds, the source compound node members are * appended to the destination compound node. * * If the destination node is a compound and the source node is * an ordinary type, the compound members are deleted (including * their contents). * * Otherwise, the source node's value replaces the destination node's * value. * * In any case, a successful call to this function frees the source * node. */ int snd_config_substitute(snd_config_t *dst, snd_config_t *src) { assert(dst && src); if (dst->type == SND_CONFIG_TYPE_COMPOUND && src->type == SND_CONFIG_TYPE_COMPOUND) { /* append */ snd_config_iterator_t i, next; snd_config_for_each(i, next, src) { snd_config_t *n = snd_config_iterator_entry(i); n->parent = dst; } src->u.compound.fields.next->prev = &dst->u.compound.fields; src->u.compound.fields.prev->next = &dst->u.compound.fields; } else if (dst->type == SND_CONFIG_TYPE_COMPOUND) { int err; err = snd_config_delete_compound_members(dst); if (err < 0) return err; } free(dst->id); dst->id = src->id; dst->type = src->type; dst->u = src->u; free(src); return 0; } /** * \brief Converts an ASCII string to a configuration node type. * \param[in] ascii A string containing a configuration node type. * \param[out] type The node type corresponding to \a ascii. * \return Zero if successful, otherwise a negative error code. * * This function recognizes at least the following node types: *
*
integer
#SND_CONFIG_TYPE_INTEGER *
integer64
#SND_CONFIG_TYPE_INTEGER64 *
real
#SND_CONFIG_TYPE_REAL *
string
#SND_CONFIG_TYPE_STRING *
compound
#SND_CONFIG_TYPE_COMPOUND *
* * \par Errors: *
*
-EINVAL
Unknown note type in \a type. *
*/ int snd_config_get_type_ascii(const char *ascii, snd_config_type_t *type) { assert(ascii && type); if (!strcmp(ascii, "integer")) { *type = SND_CONFIG_TYPE_INTEGER; return 0; } if (!strcmp(ascii, "integer64")) { *type = SND_CONFIG_TYPE_INTEGER64; return 0; } if (!strcmp(ascii, "real")) { *type = SND_CONFIG_TYPE_REAL; return 0; } if (!strcmp(ascii, "string")) { *type = SND_CONFIG_TYPE_STRING; return 0; } if (!strcmp(ascii, "compound")) { *type = SND_CONFIG_TYPE_COMPOUND; return 0; } return -EINVAL; } /** * \brief Returns the type of a configuration node. * \param config Handle to the configuration node. * \return The node's type. * * \par Conforming to: * LSB 3.2 */ snd_config_type_t snd_config_get_type(const snd_config_t *config) { return config->type; } static int check_array_item(const char *id, int index) { const char *p; long val; for (p = id; *p; p++) { if (*p < '0' || *p > '9') return 0; } if (safe_strtol(id, &val)) return 0; return val == index; } /** * \brief Returns if the compound is an array. * \param config Handle to the configuration node. * \return A positive value when true, zero when false, otherwise a negative error code. */ int snd_config_is_array(const snd_config_t *config) { int idx; snd_config_iterator_t i, next; snd_config_t *node; assert(config); if (config->type != SND_CONFIG_TYPE_COMPOUND) return -EINVAL; idx = 0; snd_config_for_each(i, next, config) { node = snd_config_iterator_entry(i); if (!check_array_item(node->id, idx)) return 0; idx++; } return 1; } /** * \brief Returns the id of a configuration node. * \param[in] config Handle to the configuration node. * \param[out] id The function puts the pointer to the id string at the * address specified by \a id. * \return Zero if successful, otherwise a negative error code. * * The returned string is owned by the configuration node; the application * must not modify or delete it, and the string becomes invalid when the * node's id changes or when the node is freed. * * If the node does not have an id, \a *id is set to \c NULL. * * \par Conforming to: * LSB 3.2 */ int snd_config_get_id(const snd_config_t *config, const char **id) { assert(config && id); *id = config->id; return 0; } /** * \brief Sets the id of a configuration node. * \param config Handle to the configuration node. * \param id The new node id, must not be \c NULL. * \return Zero if successful, otherwise a negative error code. * * This function stores a copy of \a id in the node. * * \par Errors: *
*
-EEXIST
One of \a config's siblings already has the id \a id. *
-EINVAL
The id of a node with a parent cannot be set to \c NULL. *
-ENOMEM
Out of memory. *
*/ int snd_config_set_id(snd_config_t *config, const char *id) { snd_config_iterator_t i, next; char *new_id; assert(config); if (id) { if (config->parent) { snd_config_for_each(i, next, config->parent) { snd_config_t *n = snd_config_iterator_entry(i); if (n != config && strcmp(id, n->id) == 0) return -EEXIST; } } new_id = strdup(id); if (!new_id) return -ENOMEM; } else { if (config->parent) return -EINVAL; new_id = NULL; } free(config->id); config->id = new_id; return 0; } /** * \brief Creates a top level configuration node. * \param[out] config Handle to the new node. * \return Zero if successful, otherwise a negative error code. * * The returned node is an empty compound node without a parent and * without an id. * * \par Errors: *
*
-ENOMEM
Out of memory. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_top(snd_config_t **config) { assert(config); return _snd_config_make(config, 0, SND_CONFIG_TYPE_COMPOUND); } #ifndef DOC_HIDDEN int _snd_config_load_with_include(snd_config_t *config, snd_input_t *in, int override, const char * const *include_paths) { int err; input_t input; struct filedesc *fd, *fd_next; assert(config && in); fd = malloc(sizeof(*fd)); if (!fd) return -ENOMEM; fd->name = NULL; fd->in = in; fd->line = 1; fd->column = 0; fd->next = NULL; INIT_LIST_HEAD(&fd->include_paths); if (include_paths) { for (; *include_paths; include_paths++) { err = add_include_path(fd, *include_paths); if (err < 0) goto _end; } } else { err = add_include_path(fd, snd_config_topdir()); if (err < 0) goto _end; } input.current = fd; input.unget = 0; err = parse_defs(config, &input, 0, override); fd = input.current; if (err < 0) { const char *str; switch (err) { case LOCAL_UNTERMINATED_STRING: str = "Unterminated string"; err = -EINVAL; break; case LOCAL_UNTERMINATED_QUOTE: str = "Unterminated quote"; err = -EINVAL; break; case LOCAL_UNEXPECTED_CHAR: str = "Unexpected char"; err = -EINVAL; break; case LOCAL_UNEXPECTED_EOF: str = "Unexpected end of file"; err = -EINVAL; break; default: str = strerror(-err); break; } SNDERR("%s:%d:%d:%s", fd->name ? fd->name : "_toplevel_", fd->line, fd->column, str); goto _end; } if (get_char(&input) != LOCAL_UNEXPECTED_EOF) { SNDERR("%s:%d:%d:Unexpected }", fd->name ? fd->name : "", fd->line, fd->column); err = -EINVAL; goto _end; } _end: while (fd->next) { fd_next = fd->next; snd_input_close(fd->in); free(fd->name); free_include_paths(fd); free(fd); fd = fd_next; } free_include_paths(fd); free(fd); return err; } #endif /** * \brief Loads a configuration tree. * \param config Handle to a top level configuration node. * \param in Input handle to read the configuration from. * \return Zero if successful, otherwise a negative error code. * * The definitions loaded from the input are added to \a config, which * must be a compound node. * * \par Errors: * Any errors encountered when parsing the input or returned by hooks or * functions. * * \par Conforming to: * LSB 3.2 */ int snd_config_load(snd_config_t *config, snd_input_t *in) { return _snd_config_load_with_include(config, in, 0, NULL); } /** * \brief Loads a configuration tree and overrides existing configuration nodes. * \param config Handle to a top level configuration node. * \param in Input handle to read the configuration from. * \return Zero if successful, otherwise a negative error code. * * This function loads definitions from \a in into \a config like * #snd_config_load, but the default mode for input nodes is 'override' * (!) instead of 'merge+create' (+). */ int snd_config_load_override(snd_config_t *config, snd_input_t *in) { return _snd_config_load_with_include(config, in, 1, NULL); } /** * \brief Adds a child to a compound configuration node. * \param parent Handle to a compound configuration node. * \param child Handle to the configuration node to be added. * \return Zero if successful, otherwise a negative error code. * * This function makes the node \a child a child of the node \a parent. * * The parent node then owns the child node, i.e., the child node gets * deleted together with its parent. * * \a child must have an id. * * \par Errors: *
*
-EINVAL
\a child does not have an id. *
-EINVAL
\a child already has a parent. *
-EEXIST
\a parent already contains a child node with the same * id as \a child. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_add(snd_config_t *parent, snd_config_t *child) { snd_config_iterator_t i, next; assert(parent && child); if (!child->id || child->parent) return -EINVAL; snd_config_for_each(i, next, parent) { snd_config_t *n = snd_config_iterator_entry(i); if (strcmp(child->id, n->id) == 0) return -EEXIST; } child->parent = parent; list_add_tail(&child->list, &parent->u.compound.fields); return 0; } /** * \brief Adds a child after another child configuration node. * \param after Handle to the start configuration node. * \param child Handle to the configuration node to be added. * \return Zero if successful, otherwise a negative error code. * * This function makes the node \a child a child of the parent of * the node \a after. * * The parent node then owns the child node, i.e., the child node gets * deleted together with its parent. * * \a child must have an id. * * \par Errors: *
*
-EINVAL
\a child does not have an id. *
-EINVAL
\a child already has a parent. *
-EEXIST
\a parent already contains a child node with the same * id as \a child. *
*/ int snd_config_add_after(snd_config_t *after, snd_config_t *child) { snd_config_iterator_t i, next; snd_config_t *parent; assert(after && child); parent = after->parent; assert(parent); if (!child->id || child->parent) return -EINVAL; snd_config_for_each(i, next, parent) { snd_config_t *n = snd_config_iterator_entry(i); if (strcmp(child->id, n->id) == 0) return -EEXIST; } child->parent = parent; list_insert(&child->list, &after->list, after->list.next); return 0; } /** * \brief Adds a child before another child configuration node. * \param before Handle to the start configuration node. * \param child Handle to the configuration node to be added. * \return Zero if successful, otherwise a negative error code. * * This function makes the node \a child a child of the parent of * the node \a before. * * The parent node then owns the child node, i.e., the child node gets * deleted together with its parent. * * \a child must have an id. * * \par Errors: *
*
-EINVAL
\a child does not have an id. *
-EINVAL
\a child already has a parent. *
-EEXIST
\a parent already contains a child node with the same * id as \a child. *
*/ int snd_config_add_before(snd_config_t *before, snd_config_t *child) { snd_config_iterator_t i, next; snd_config_t *parent; assert(before && child); parent = before->parent; assert(parent); if (!child->id || child->parent) return -EINVAL; snd_config_for_each(i, next, parent) { snd_config_t *n = snd_config_iterator_entry(i); if (strcmp(child->id, n->id) == 0) return -EEXIST; } child->parent = parent; list_insert(&child->list, before->list.prev, &before->list); return 0; } /** * \brief Removes a configuration node from its tree. * \param config Handle to the configuration node to be removed. * \return Zero if successful, otherwise a negative error code. * * This function makes \a config a top-level node, i.e., if \a config * has a parent, then \a config is removed from the list of the parent's * children. * * This functions does \e not free the removed node. * * \sa snd_config_delete */ int snd_config_remove(snd_config_t *config) { assert(config); if (config->parent) list_del(&config->list); config->parent = NULL; return 0; } /** * \brief Frees a configuration node. * \param config Handle to the configuration node to be deleted. * \return Zero if successful, otherwise a negative error code. * * This function frees a configuration node and all its resources. * * If the node is a child node, it is removed from the tree before being * deleted. * * If the node is a compound node, its descendants (the whole subtree) * are deleted recursively. * * The function is supposed to be called only for locally copied config * trees. For the global tree, take the reference via #snd_config_update_ref * and free it via #snd_config_unref. * * \par Conforming to: * LSB 3.2 * * \sa snd_config_remove */ int snd_config_delete(snd_config_t *config) { assert(config); if (config->refcount > 0) { config->refcount--; return 0; } switch (config->type) { case SND_CONFIG_TYPE_COMPOUND: { int err; struct list_head *i; i = config->u.compound.fields.next; while (i != &config->u.compound.fields) { struct list_head *nexti = i->next; snd_config_t *child = snd_config_iterator_entry(i); err = snd_config_delete(child); if (err < 0) return err; i = nexti; } break; } case SND_CONFIG_TYPE_STRING: free(config->u.string); break; default: break; } if (config->parent) list_del(&config->list); free(config->id); free(config); return 0; } /** * \brief Deletes the children of a node. * \param config Handle to the compound configuration node. * \return Zero if successful, otherwise a negative error code. * * This function removes and frees all children of a configuration node. * * Any compound nodes among the children of \a config are deleted * recursively. * * After a successful call to this function, \a config is an empty * compound node. * * \par Errors: *
*
-EINVAL
\a config is not a compound node. *
*/ int snd_config_delete_compound_members(const snd_config_t *config) { int err; struct list_head *i; assert(config); if (config->type != SND_CONFIG_TYPE_COMPOUND) return -EINVAL; i = config->u.compound.fields.next; while (i != &config->u.compound.fields) { struct list_head *nexti = i->next; snd_config_t *child = snd_config_iterator_entry(i); err = snd_config_delete(child); if (err < 0) return err; i = nexti; } return 0; } /** * \brief Creates a configuration node. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \param[in] type The type of the new node. * \return Zero if successful, otherwise a negative error code. * * This functions creates a new node of the specified type. * The new node has id \a id, which may be \c NULL. * * The value of the new node is zero (for numbers), or \c NULL (for * strings and pointers), or empty (for compound nodes). * * \par Errors: *
*
-ENOMEM
Out of memory. *
*/ int snd_config_make(snd_config_t **config, const char *id, snd_config_type_t type) { char *id1; assert(config); if (id) { id1 = strdup(id); if (!id1) return -ENOMEM; } else id1 = NULL; return _snd_config_make(config, &id1, type); } /** * \brief Creates an integer configuration node. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \return Zero if successful, otherwise a negative error code. * * This function creates a new node of type #SND_CONFIG_TYPE_INTEGER and * with value \c 0. * * \par Errors: *
*
-ENOMEM
Out of memory. *
* * \par Conforming to: * LSB 3.2 * * \sa snd_config_imake_integer */ int snd_config_make_integer(snd_config_t **config, const char *id) { return snd_config_make(config, id, SND_CONFIG_TYPE_INTEGER); } /** * \brief Creates a 64-bit-integer configuration node. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \return Zero if successful, otherwise a negative error code. * * This function creates a new node of type #SND_CONFIG_TYPE_INTEGER64 * and with value \c 0. * * \par Errors: *
*
-ENOMEM
Out of memory. *
* * \par Conforming to: * LSB 3.2 * * \sa snd_config_imake_integer64 */ int snd_config_make_integer64(snd_config_t **config, const char *id) { return snd_config_make(config, id, SND_CONFIG_TYPE_INTEGER64); } /** * \brief Creates a real number configuration node. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \return Zero if successful, otherwise a negative error code. * * This function creates a new node of type #SND_CONFIG_TYPE_REAL and * with value \c 0.0. * * \par Errors: *
*
-ENOMEM
Out of memory. *
* * \sa snd_config_imake_real */ int snd_config_make_real(snd_config_t **config, const char *id) { return snd_config_make(config, id, SND_CONFIG_TYPE_REAL); } /** * \brief Creates a string configuration node. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \return Zero if successful, otherwise a negative error code. * * This function creates a new node of type #SND_CONFIG_TYPE_STRING and * with value \c NULL. * * \par Errors: *
*
-ENOMEM
Out of memory. *
* * \par Conforming to: * LSB 3.2 * * \sa snd_config_imake_string */ int snd_config_make_string(snd_config_t **config, const char *id) { return snd_config_make(config, id, SND_CONFIG_TYPE_STRING); } /** * \brief Creates a pointer configuration node. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \return Zero if successful, otherwise a negative error code. * * This function creates a new node of type #SND_CONFIG_TYPE_POINTER and * with value \c NULL. * * \par Errors: *
*
-ENOMEM
Out of memory. *
* * \sa snd_config_imake_pointer */ int snd_config_make_pointer(snd_config_t **config, const char *id) { return snd_config_make(config, id, SND_CONFIG_TYPE_POINTER); } /** * \brief Creates an empty compound configuration node. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \param[in] join Join flag. * \return Zero if successful, otherwise a negative error code. * * This function creates a new empty node of type * #SND_CONFIG_TYPE_COMPOUND. * * \a join determines how the compound node's id is printed when the * configuration is saved to a text file. For example, if the join flag * of compound node \c a is zero, the output will look as follows: * \code * a { * b "hello" * c 42 * } * \endcode * If, however, the join flag of \c a is nonzero, its id will be joined * with its children's ids, like this: * \code * a.b "hello" * a.c 42 * \endcode * An \e empty compound node with its join flag set would result in no * output, i.e., after saving and reloading the configuration file, that * compound node would be lost. * * \par Errors: *
*
-ENOMEM
Out of memory. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_make_compound(snd_config_t **config, const char *id, int join) { int err; err = snd_config_make(config, id, SND_CONFIG_TYPE_COMPOUND); if (err < 0) return err; (*config)->u.compound.join = join; return 0; } /** * \brief Creates an integer configuration node with the given initial value. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \param[in] value The initial value of the new node. * \return Zero if successful, otherwise a negative error code. * * This function creates a new node of type #SND_CONFIG_TYPE_INTEGER and * with value \a value. * * \par Errors: *
*
-ENOMEM
Out of memory. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_imake_integer(snd_config_t **config, const char *id, const long value) { int err; err = snd_config_make(config, id, SND_CONFIG_TYPE_INTEGER); if (err < 0) return err; (*config)->u.integer = value; return 0; } /** * \brief Creates a 64-bit-integer configuration node with the given initial value. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \param[in] value The initial value of the new node. * \return Zero if successful, otherwise a negative error code. * * This function creates a new node of type #SND_CONFIG_TYPE_INTEGER64 * and with value \a value. * * \par Errors: *
*
-ENOMEM
Out of memory. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_imake_integer64(snd_config_t **config, const char *id, const long long value) { int err; err = snd_config_make(config, id, SND_CONFIG_TYPE_INTEGER64); if (err < 0) return err; (*config)->u.integer64 = value; return 0; } /** * \brief Creates a real number configuration node with the given initial value. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \param[in] value The initial value of the new node. * \return Zero if successful, otherwise a negative error code. * * This function creates a new node of type #SND_CONFIG_TYPE_REAL and * with value \a value. * * \par Errors: *
*
-ENOMEM
Out of memory. *
*/ int snd_config_imake_real(snd_config_t **config, const char *id, const double value) { int err; err = snd_config_make(config, id, SND_CONFIG_TYPE_REAL); if (err < 0) return err; (*config)->u.real = value; return 0; } /** * \brief Creates a string configuration node with the given initial value. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \param[in] value The initial value of the new node. May be \c NULL. * \return Zero if successful, otherwise a negative error code. * * This function creates a new node of type #SND_CONFIG_TYPE_STRING and * with a copy of the string \c value. * * \par Errors: *
*
-ENOMEM
Out of memory. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_imake_string(snd_config_t **config, const char *id, const char *value) { int err; snd_config_t *tmp; err = snd_config_make(&tmp, id, SND_CONFIG_TYPE_STRING); if (err < 0) return err; if (value) { tmp->u.string = strdup(value); if (!tmp->u.string) { snd_config_delete(tmp); return -ENOMEM; } } else { tmp->u.string = NULL; } *config = tmp; return 0; } int snd_config_imake_safe_string(snd_config_t **config, const char *id, const char *value) { int err; snd_config_t *tmp; char *c; err = snd_config_make(&tmp, id, SND_CONFIG_TYPE_STRING); if (err < 0) return err; if (value) { tmp->u.string = strdup(value); if (!tmp->u.string) { snd_config_delete(tmp); return -ENOMEM; } for (c = tmp->u.string; *c; c++) { if (*c == ' ' || *c == '-' || *c == '_' || (*c >= '0' && *c <= '9') || (*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z')) continue; *c = '_'; } } else { tmp->u.string = NULL; } *config = tmp; return 0; } /** * \brief Creates a pointer configuration node with the given initial value. * \param[out] config The function puts the handle to the new node at * the address specified by \a config. * \param[in] id The id of the new node. * \param[in] value The initial value of the new node. * \return Zero if successful, otherwise a negative error code. * * This function creates a new node of type #SND_CONFIG_TYPE_POINTER and * with value \c value. * * \par Errors: *
*
-ENOMEM
Out of memory. *
*/ int snd_config_imake_pointer(snd_config_t **config, const char *id, const void *value) { int err; err = snd_config_make(config, id, SND_CONFIG_TYPE_POINTER); if (err < 0) return err; (*config)->u.ptr = value; return 0; } /** * \brief Changes the value of an integer configuration node. * \param config Handle to the configuration node. * \param value The new value for the node. * \return Zero if successful, otherwise a negative error code. * * \par Errors: *
*
-EINVAL
\a config is not an integer node. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_set_integer(snd_config_t *config, long value) { assert(config); if (config->type != SND_CONFIG_TYPE_INTEGER) return -EINVAL; config->u.integer = value; return 0; } /** * \brief Changes the value of a 64-bit-integer configuration node. * \param config Handle to the configuration node. * \param value The new value for the node. * \return Zero if successful, otherwise a negative error code. * * \par Errors: *
*
-EINVAL
\a config is not a 64-bit-integer node. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_set_integer64(snd_config_t *config, long long value) { assert(config); if (config->type != SND_CONFIG_TYPE_INTEGER64) return -EINVAL; config->u.integer64 = value; return 0; } /** * \brief Changes the value of a real-number configuration node. * \param config Handle to the configuration node. * \param value The new value for the node. * \return Zero if successful, otherwise a negative error code. * * \par Errors: *
*
-EINVAL
\a config is not a real-number node. *
*/ int snd_config_set_real(snd_config_t *config, double value) { assert(config); if (config->type != SND_CONFIG_TYPE_REAL) return -EINVAL; config->u.real = value; return 0; } /** * \brief Changes the value of a string configuration node. * \param config Handle to the configuration node. * \param value The new value for the node. May be \c NULL. * \return Zero if successful, otherwise a negative error code. * * This function deletes the old string in the node and stores a copy of * \a value string in the node. * * \par Errors: *
*
-EINVAL
\a config is not a string node. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_set_string(snd_config_t *config, const char *value) { char *new_string; assert(config); if (config->type != SND_CONFIG_TYPE_STRING) return -EINVAL; if (value) { new_string = strdup(value); if (!new_string) return -ENOMEM; } else { new_string = NULL; } free(config->u.string); config->u.string = new_string; return 0; } /** * \brief Changes the value of a pointer configuration node. * \param config Handle to the configuration node. * \param value The new value for the node. May be \c NULL. * \return Zero if successful, otherwise a negative error code. * * This function does not free the old pointer in the node. * * \par Errors: *
*
-EINVAL
\a config is not a pointer node. *
*/ int snd_config_set_pointer(snd_config_t *config, const void *value) { assert(config); if (config->type != SND_CONFIG_TYPE_POINTER) return -EINVAL; config->u.ptr = value; return 0; } /** * \brief Changes the value of a configuration node. * \param config Handle to the configuration node. * \param ascii The new value for the node, as an ASCII string. * \return Zero if successful, otherwise a negative error code. * * This function changes the node's value to a new value that is parsed * from the string \a ascii. \a ascii must not be \c NULL, not even for * a string node. * * The node's type does not change, i.e., the string must contain a * valid value with the same type as the node's type. For a string * node, the node's new value is a copy of \a ascii. * * \par Errors: *
*
-EINVAL
\a config is not a number or string node. *
-EINVAL
The value in \a ascii cannot be parsed. *
-ERANGE
The value in \a ascii is too big for the node's type. *
-ENOMEM
Out of memory. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_set_ascii(snd_config_t *config, const char *ascii) { assert(config && ascii); switch (config->type) { case SND_CONFIG_TYPE_INTEGER: { long i; int err = safe_strtol(ascii, &i); if (err < 0) return err; config->u.integer = i; } break; case SND_CONFIG_TYPE_INTEGER64: { long long i; int err = safe_strtoll(ascii, &i); if (err < 0) return err; config->u.integer64 = i; } break; case SND_CONFIG_TYPE_REAL: { double d; int err = safe_strtod(ascii, &d); if (err < 0) return err; config->u.real = d; break; } case SND_CONFIG_TYPE_STRING: { char *ptr = strdup(ascii); if (ptr == NULL) return -ENOMEM; free(config->u.string); config->u.string = ptr; } break; default: return -EINVAL; } return 0; } /** * \brief Returns the value of an integer configuration node. * \param[in] config Handle to the configuration node. * \param[out] ptr The node's value. * \return Zero if successful, otherwise a negative error code. * * \par Errors: *
*
-EINVAL
\a config is not an integer node. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_get_integer(const snd_config_t *config, long *ptr) { assert(config && ptr); if (config->type != SND_CONFIG_TYPE_INTEGER) return -EINVAL; *ptr = config->u.integer; return 0; } /** * \brief Returns the value of a 64-bit-integer configuration node. * \param[in] config Handle to the configuration node. * \param[out] ptr The node's value. * \return Zero if successful, otherwise a negative error code. * * \par Errors: *
*
-EINVAL
\a config is not a 64-bit-integer node. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_get_integer64(const snd_config_t *config, long long *ptr) { assert(config && ptr); if (config->type != SND_CONFIG_TYPE_INTEGER64) return -EINVAL; *ptr = config->u.integer64; return 0; } /** * \brief Returns the value of a real-number configuration node. * \param[in] config Handle to the configuration node. * \param[out] ptr The node's value. * \return Zero if successful, otherwise a negative error code. * * \par Errors: *
*
-EINVAL
\a config is not a real-number node. *
*/ int snd_config_get_real(const snd_config_t *config, double *ptr) { assert(config && ptr); if (config->type != SND_CONFIG_TYPE_REAL) return -EINVAL; *ptr = config->u.real; return 0; } /** * \brief Returns the value of a real or integer configuration node. * \param[in] config Handle to the configuration node. * \param[out] ptr The node's value. * \return Zero if successful, otherwise a negative error code. * * If the node's type is integer or integer64, the value is converted * to the \c double type on the fly. * * \par Errors: *
*
-EINVAL
\a config is not a number node. *
*/ int snd_config_get_ireal(const snd_config_t *config, double *ptr) { assert(config && ptr); if (config->type == SND_CONFIG_TYPE_REAL) *ptr = config->u.real; else if (config->type == SND_CONFIG_TYPE_INTEGER) *ptr = config->u.integer; else if (config->type == SND_CONFIG_TYPE_INTEGER64) *ptr = config->u.integer64; else return -EINVAL; return 0; } /** * \brief Returns the value of a string configuration node. * \param[in] config Handle to the configuration node. * \param[out] ptr The function puts the node's value at the address * specified by \a ptr. * \return Zero if successful, otherwise a negative error code. * * The returned string is owned by the configuration node; the * application must not modify or delete it, and the string becomes * invalid when the node's value changes or when the node is freed. * * The string may be \c NULL. * * \par Errors: *
*
-EINVAL
\a config is not a string node. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_get_string(const snd_config_t *config, const char **ptr) { assert(config && ptr); if (config->type != SND_CONFIG_TYPE_STRING) return -EINVAL; *ptr = config->u.string; return 0; } /** * \brief Returns the value of a pointer configuration node. * \param[in] config Handle to the configuration node. * \param[out] ptr The function puts the node's value at the address * specified by \a ptr. * \return Zero if successful, otherwise a negative error code. * * \par Errors: *
*
-EINVAL
\a config is not a string node. *
*/ int snd_config_get_pointer(const snd_config_t *config, const void **ptr) { assert(config && ptr); if (config->type != SND_CONFIG_TYPE_POINTER) return -EINVAL; *ptr = config->u.ptr; return 0; } /** * \brief Returns the value of a configuration node as a string. * \param[in] config Handle to the configuration node. * \param[out] ascii The function puts the pointer to the returned * string at the address specified by \a ascii. * \return Zero if successful, otherwise a negative error code. * * This function dynamically allocates the returned string. The * application is responsible for deleting it with \c free() when it is * no longer used. * * For a string node with \c NULL value, the returned string is \c NULL. * * Supported node types are #SND_CONFIG_TYPE_INTEGER, * #SND_CONFIG_TYPE_INTEGER64, #SND_CONFIG_TYPE_REAL, and * #SND_CONFIG_TYPE_STRING. * * \par Errors: *
*
-EINVAL
\a config is not a (64-bit) integer or real number or * string node. *
-ENOMEM
Out of memory. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_get_ascii(const snd_config_t *config, char **ascii) { assert(config && ascii); switch (config->type) { case SND_CONFIG_TYPE_INTEGER: { char res[12]; int err; err = snprintf(res, sizeof(res), "%li", config->u.integer); if (err < 0 || err == sizeof(res)) { assert(0); return -ENOMEM; } *ascii = strdup(res); } break; case SND_CONFIG_TYPE_INTEGER64: { char res[32]; int err; err = snprintf(res, sizeof(res), "%lli", config->u.integer64); if (err < 0 || err == sizeof(res)) { assert(0); return -ENOMEM; } *ascii = strdup(res); } break; case SND_CONFIG_TYPE_REAL: { char res[32]; int err; err = snprintf(res, sizeof(res), "%-16g", config->u.real); if (err < 0 || err == sizeof(res)) { assert(0); return -ENOMEM; } if (res[0]) { /* trim the string */ char *ptr; ptr = res + strlen(res) - 1; while (ptr != res && *ptr == ' ') ptr--; if (*ptr != ' ') ptr++; *ptr = '\0'; } *ascii = strdup(res); } break; case SND_CONFIG_TYPE_STRING: if (config->u.string) *ascii = strdup(config->u.string); else { *ascii = NULL; return 0; } break; default: return -EINVAL; } if (*ascii == NULL) return -ENOMEM; return 0; } /** * \brief Compares the id of a configuration node to a given string. * \param config Handle to the configuration node. * \param id ASCII id. * \return The same value as the result of the \c strcmp function, i.e., * less than zero if \a config's id is lexicographically less * than \a id, zero if \a config's id is equal to id, greater * than zero otherwise. */ int snd_config_test_id(const snd_config_t *config, const char *id) { assert(config && id); if (config->id) return strcmp(config->id, id); else return -1; } /** * \brief Dumps the contents of a configuration node or tree. * \param config Handle to the (root) configuration node. * \param out Output handle. * \return Zero if successful, otherwise a negative error code. * * This function writes a textual representation of \a config's value to * the output \a out. * * \par Errors: *
*
-EINVAL
A node in the tree has a type that cannot be printed, * i.e., #SND_CONFIG_TYPE_POINTER. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_save(snd_config_t *config, snd_output_t *out) { assert(config && out); if (config->type == SND_CONFIG_TYPE_COMPOUND) return _snd_config_save_children(config, out, 0, 0); else return _snd_config_save_node_value(config, out, 0); } /* * *** search macros *** */ #ifndef DOC_HIDDEN #define SND_CONFIG_SEARCH(config, key, result, extra_code) \ { \ snd_config_t *n; \ int err; \ const char *p; \ assert(config && key); \ while (1) { \ if (config->type != SND_CONFIG_TYPE_COMPOUND) \ return -ENOENT; \ { extra_code ; } \ p = strchr(key, '.'); \ if (p) { \ err = _snd_config_search(config, key, p - key, &n); \ if (err < 0) \ return err; \ config = n; \ key = p + 1; \ } else \ return _snd_config_search(config, key, -1, result); \ } \ } #define SND_CONFIG_SEARCHA(root, config, key, result, fcn, extra_code) \ { \ snd_config_t *n; \ int err; \ const char *p; \ assert(config && key); \ while (1) { \ if (config->type != SND_CONFIG_TYPE_COMPOUND) { \ if (snd_config_get_string(config, &p) < 0) \ return -ENOENT; \ err = fcn(root, root, p, &config); \ if (err < 0) \ return err; \ } \ { extra_code ; } \ p = strchr(key, '.'); \ if (p) { \ err = _snd_config_search(config, key, p - key, &n); \ if (err < 0) \ return err; \ config = n; \ key = p + 1; \ } else \ return _snd_config_search(config, key, -1, result); \ } \ } #define SND_CONFIG_SEARCHV(config, result, fcn) \ { \ snd_config_t *n; \ va_list arg; \ assert(config); \ va_start(arg, result); \ while (1) { \ const char *k = va_arg(arg, const char *); \ int err; \ if (!k) \ break; \ err = fcn(config, k, &n); \ if (err < 0) { \ va_end(arg); \ return err; \ } \ config = n; \ } \ va_end(arg); \ if (result) \ *result = n; \ return 0; \ } #define SND_CONFIG_SEARCHVA(root, config, result, fcn) \ { \ snd_config_t *n; \ va_list arg; \ assert(config); \ va_start(arg, result); \ while (1) { \ const char *k = va_arg(arg, const char *); \ int err; \ if (!k) \ break; \ err = fcn(root, config, k, &n); \ if (err < 0) { \ va_end(arg); \ return err; \ } \ config = n; \ } \ va_end(arg); \ if (result) \ *result = n; \ return 0; \ } #define SND_CONFIG_SEARCH_ALIAS(config, base, key, result, fcn1, fcn2) \ { \ snd_config_t *res = NULL; \ char *old_key; \ int err, first = 1, maxloop = 1000; \ assert(config && key); \ while (1) { \ old_key = strdup(key); \ if (old_key == NULL) { \ err = -ENOMEM; \ res = NULL; \ break; \ } \ err = first && base ? -EIO : fcn1(config, config, key, &res); \ if (err < 0) { \ if (!base) \ break; \ err = fcn2(config, config, &res, base, key, NULL); \ if (err < 0) \ break; \ } \ if (snd_config_get_string(res, &key) < 0) \ break; \ assert(key); \ if (!first && (strcmp(key, old_key) == 0 || maxloop <= 0)) { \ if (maxloop == 0) \ SNDERR("maximum loop count reached (circular configuration?)"); \ else \ SNDERR("key %s refers to itself", key); \ err = -EINVAL; \ res = NULL; \ break; \ } \ free(old_key); \ first = 0; \ maxloop--; \ } \ free(old_key); \ if (!res) \ return err; \ if (result) \ *result = res; \ return 0; \ } #endif /* DOC_HIDDEN */ /** * \brief Searches for a node in a configuration tree. * \param[in] config Handle to the root of the configuration (sub)tree to search. * \param[in] key Search key: one or more node ids, separated with dots. * \param[out] result When \a result != \c NULL, the function puts the * handle to the node found at the address specified * by \a result. * \return Zero if successful, otherwise a negative error code. * * This function searches for a child node of \a config that is * identified by \a key, which contains either the id of a direct child * node of \a config, or a series of ids, separated with dots, where * each id specifies a node that is contained in the previous compound * node. * * In the following example, the comment after each node shows the * search key to find that node, assuming that \a config is a handle to * the compound node with id \c config: * \code * config { * a 42 # "a" * b { # "b" * c "cee" # "b.c" * d { # "b.d" * e 2.71828 # "b.d.e" * } * } * } * \endcode * * \par Errors: *
*
-ENOENT
An id in \a key does not exist. *
-ENOENT
\a config or one of its child nodes to be searched is * not a compound node. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_search(snd_config_t *config, const char *key, snd_config_t **result) { SND_CONFIG_SEARCH(config, key, result, ); } /** * \brief Searches for a node in a configuration tree, expanding aliases. * \param[in] root Handle to the root configuration node containing * alias definitions. * \param[in] config Handle to the root of the configuration (sub)tree to search. * \param[in] key Search key: one or more node keys, separated with dots. * \param[out] result When \a result != \c NULL, the function puts the * handle to the node found at the address specified * by \a result. * \return Zero if successful, otherwise a negative error code. * * This functions searches for a child node of \a config like * #snd_config_search. However, any compound node can also be * identified by an alias, which is a string node whose value is taken * as the id of a compound node below \a root. * * \a root must be a compound node. * \a root and \a config may be the same node. * * For example, with the following configuration, the call * \code * snd_config_searcha(root, config, "a.b.c.d", &result); * \endcode * would return the node with id \c d: * \code * config { * a { * b bb * } * } * root { * bb { * c cc * } * cc ccc * ccc { * d { * x "icks" * } * } * } * \endcode * * \par Errors: *
*
-ENOENT
An id in \a key or an alias id does not exist. *
-ENOENT
\a config or one of its child nodes to be searched is * not a compound or string node. *
*/ int snd_config_searcha(snd_config_t *root, snd_config_t *config, const char *key, snd_config_t **result) { SND_CONFIG_SEARCHA(root, config, key, result, snd_config_searcha, ); } /** * \brief Searches for a node in a configuration tree. * \param[in] config Handle to the root of the configuration (sub)tree to search. * \param[out] result When \a result != \c NULL, the function puts the * handle to the node found at the address specified * by \a result. * \param[in] ... One or more concatenated dot-separated search keys, * terminated with \c NULL. * \return Zero if successful, otherwise a negative error code. * * This functions searches for a child node of \a config like * #snd_config_search, but the search key is the concatenation of all * passed search key strings. For example, the call * \code * snd_config_searchv(cfg, &res, "a", "b.c", "d.e", NULL); * \endcode * is equivalent to the call * \code * snd_config_search(cfg, "a.b.c.d.e", &res); * \endcode * * \par Errors: *
*
-ENOENT
An id in a search key does not exist. *
-ENOENT
\a config or one of its child nodes to be searched is * not a compound node. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_searchv(snd_config_t *config, snd_config_t **result, ...) { SND_CONFIG_SEARCHV(config, result, snd_config_search); } /** * \brief Searches for a node in a configuration tree, expanding aliases. * \param[in] root Handle to the root configuration node containing * alias definitions. * \param[in] config Handle to the root of the configuration (sub)tree to search. * \param[out] result When \a result != \c NULL, the function puts the * handle to the node found at the address specified * by \a result. * \param[in] ... One or more concatenated dot separated search keys, * terminated with \c NULL. * \return Zero if successful, otherwise a negative error code. * * This function searches for a child node of \a config, allowing * aliases, like #snd_config_searcha, but the search key is the * concatenation of all passed seach key strings, like with * #snd_config_searchv. * * \par Errors: *
*
-ENOENT
An id in a search key does not exist. *
-ENOENT
\a config or one of its child nodes to be searched is * not a compound or string node. *
*/ int snd_config_searchva(snd_config_t *root, snd_config_t *config, snd_config_t **result, ...) { SND_CONFIG_SEARCHVA(root, config, result, snd_config_searcha); } /** * \brief Searches for a node in a configuration tree, expanding aliases. * \param[in] config Handle to the root of the configuration (sub)tree to search. * \param[in] base Search key base, or \c NULL. * \param[in] key Search key suffix. * \param[out] result When \a result != \c NULL, the function puts the * handle to the node found at the address specified * by \a result. * \return Zero if successful, otherwise a negative error code. * * This functions searches for a child node of \a config, allowing * aliases, like #snd_config_searcha. However, alias definitions are * searched below \a config (there is no separate \a root parameter), * and \a base specifies a seach key that identifies a compound node * that is used to search for an alias definitions that is not found * directly below \a config and that does not contain a period. In * other words, when \c "id" is not found in \a config, this function * also tries \c "base.id". * * \par Errors: *
*
-ENOENT
An id in \a key or an alias id does not exist. *
-ENOENT
\a config or one of its child nodes to be searched is * not a compound or string node. *
*/ int snd_config_search_alias(snd_config_t *config, const char *base, const char *key, snd_config_t **result) { SND_CONFIG_SEARCH_ALIAS(config, base, key, result, snd_config_searcha, snd_config_searchva); } static int snd_config_hooks(snd_config_t *config, snd_config_t *private_data); /** * \brief Searches for a node in a configuration tree and expands hooks. * \param[in,out] config Handle to the root of the configuration * (sub)tree to search. * \param[in] key Search key: one or more node keys, separated with dots. * \param[out] result The function puts the handle to the node found at * the address specified by \a result. * \return Zero if successful, otherwise a negative error code. * * This functions searches for a child node of \a config like * #snd_config_search, but any compound nodes to be searched that * contain hooks are modified by the respective hook functions. * * \par Errors: *
*
-ENOENT
An id in \a key does not exist. *
-ENOENT
\a config or one of its child nodes to be searched is * not a compound node. *
* Additionally, any errors encountered when parsing the hook * definitions or returned by the hook functions. */ int snd_config_search_hooks(snd_config_t *config, const char *key, snd_config_t **result) { SND_CONFIG_SEARCH(config, key, result, \ err = snd_config_hooks(config, NULL); \ if (err < 0) \ return err; \ ); } /** * \brief Searches for a node in a configuration tree, expanding aliases and hooks. * \param[in] root Handle to the root configuration node containing * alias definitions. * \param[in,out] config Handle to the root of the configuration * (sub)tree to search. * \param[in] key Search key: one or more node keys, separated with dots. * \param[out] result The function puts the handle to the node found at * the address specified by \a result. * \return Zero if successful, otherwise a negative error code. * * This function searches for a child node of \a config, allowing * aliases, like #snd_config_searcha, and expanding hooks, like * #snd_config_search_hooks. * * \par Errors: *
*
-ENOENT
An id in \a key or an alias id does not exist. *
-ENOENT
\a config or one of its child nodes to be searched is * not a compound node. *
* Additionally, any errors encountered when parsing the hook * definitions or returned by the hook functions. */ int snd_config_searcha_hooks(snd_config_t *root, snd_config_t *config, const char *key, snd_config_t **result) { SND_CONFIG_SEARCHA(root, config, key, result, snd_config_searcha_hooks, err = snd_config_hooks(config, NULL); \ if (err < 0) \ return err; \ ); } /** * \brief Searches for a node in a configuration tree, expanding aliases and hooks. * \param[in] root Handle to the root configuration node containing * alias definitions. * \param[in,out] config Handle to the root of the configuration * (sub)tree to search. * \param[out] result The function puts the handle to the node found at * the address specified by \a result. * \param[in] ... One or more concatenated dot separated search keys, * terminated with \c NULL. * \return Zero if successful, otherwise a negative error code. * * This function searches for a child node of \a config, allowing * aliases and expanding hooks like #snd_config_searcha_hooks, but the * search key is the concatenation of all passed seach key strings, like * with #snd_config_searchv. * * \par Errors: *
*
-ENOENT
An id in \a key or an alias id does not exist. *
-ENOENT
\a config or one of its child nodes to be searched is * not a compound node. *
* Additionally, any errors encountered when parsing the hook * definitions or returned by the hook functions. */ int snd_config_searchva_hooks(snd_config_t *root, snd_config_t *config, snd_config_t **result, ...) { SND_CONFIG_SEARCHVA(root, config, result, snd_config_searcha_hooks); } /** * \brief Searches for a node in a configuration tree, using an alias and expanding hooks. * \param[in] config Handle to the root of the configuration (sub)tree * to search. * \param[in] base Search key base, or \c NULL. * \param[in] key Search key suffix. * \param[out] result The function puts the handle to the node found at * the address specified by \a result. * \return Zero if successful, otherwise a negative error code. * * This functions searches for a child node of \a config, allowing * aliases, like #snd_config_search_alias, and expanding hooks, like * #snd_config_search_hooks. * * \par Errors: *
*
-ENOENT
An id in \a key or an alias id does not exist. *
-ENOENT
\a config or one of its child nodes to be searched is * not a compound node. *
* Additionally, any errors encountered when parsing the hook * definitions or returned by the hook functions. */ int snd_config_search_alias_hooks(snd_config_t *config, const char *base, const char *key, snd_config_t **result) { SND_CONFIG_SEARCH_ALIAS(config, base, key, result, snd_config_searcha_hooks, snd_config_searchva_hooks); } /** The name of the environment variable containing the files list for #snd_config_update. */ #define ALSA_CONFIG_PATH_VAR "ALSA_CONFIG_PATH" /** * \ingroup Config * \brief Configuration top-level node (the global configuration). * * This variable contains a handle to the top-level configuration node, * as loaded from global configuration file. * * This variable is initialized or updated by #snd_config_update. * Functions like #snd_pcm_open (that use a device name from the global * configuration) automatically call #snd_config_update. Before the * first call to #snd_config_update, this variable is \c NULL. * * The global configuration files are specified in the environment * variable \c ALSA_CONFIG_PATH. If this is not set, the default value * is "/usr/share/alsa/alsa.conf". * * \warning Whenever the configuration tree is updated, all string * pointers and configuration node handles previously obtained from this * variable may become invalid. * * \par Conforming to: * LSB 3.2 */ snd_config_t *snd_config = NULL; #ifndef DOC_HIDDEN struct finfo { char *name; dev_t dev; ino_t ino; time_t mtime; }; struct _snd_config_update { unsigned int count; struct finfo *finfo; }; #endif /* DOC_HIDDEN */ static snd_config_update_t *snd_config_global_update = NULL; static int snd_config_hooks_call(snd_config_t *root, snd_config_t *config, snd_config_t *private_data) { void *h = NULL; snd_config_t *c, *func_conf = NULL; char *buf = NULL, errbuf[256]; const char *lib = NULL, *func_name = NULL; const char *str; int (*func)(snd_config_t *root, snd_config_t *config, snd_config_t **dst, snd_config_t *private_data) = NULL; int err; err = snd_config_search(config, "func", &c); if (err < 0) { SNDERR("Field func is missing"); return err; } err = snd_config_get_string(c, &str); if (err < 0) { SNDERR("Invalid type for field func"); return err; } assert(str); err = snd_config_search_definition(root, "hook_func", str, &func_conf); if (err >= 0) { snd_config_iterator_t i, next; if (snd_config_get_type(func_conf) != SND_CONFIG_TYPE_COMPOUND) { SNDERR("Invalid type for func %s definition", str); err = -EINVAL; goto _err; } snd_config_for_each(i, next, func_conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = n->id; if (strcmp(id, "comment") == 0) continue; if (strcmp(id, "lib") == 0) { err = snd_config_get_string(n, &lib); if (err < 0) { SNDERR("Invalid type for %s", id); goto _err; } continue; } if (strcmp(id, "func") == 0) { err = snd_config_get_string(n, &func_name); if (err < 0) { SNDERR("Invalid type for %s", id); goto _err; } continue; } SNDERR("Unknown field %s", id); } } if (!func_name) { int len = 16 + strlen(str) + 1; buf = malloc(len); if (! buf) { err = -ENOMEM; goto _err; } snprintf(buf, len, "snd_config_hook_%s", str); buf[len-1] = '\0'; func_name = buf; } h = INTERNAL(snd_dlopen)(lib, RTLD_NOW, errbuf, sizeof(errbuf)); func = h ? snd_dlsym(h, func_name, SND_DLSYM_VERSION(SND_CONFIG_DLSYM_VERSION_HOOK)) : NULL; err = 0; if (!h) { SNDERR("Cannot open shared library %s (%s)", lib, errbuf); err = -ENOENT; } else if (!func) { SNDERR("symbol %s is not defined inside %s", func_name, lib); snd_dlclose(h); err = -ENXIO; } _err: if (func_conf) snd_config_delete(func_conf); if (err >= 0) { snd_config_t *nroot; err = func(root, config, &nroot, private_data); if (err < 0) SNDERR("function %s returned error: %s", func_name, snd_strerror(err)); snd_dlclose(h); if (err >= 0 && nroot) err = snd_config_substitute(root, nroot); } free(buf); if (err < 0) return err; return 0; } static int snd_config_hooks(snd_config_t *config, snd_config_t *private_data) { snd_config_t *n; snd_config_iterator_t i, next; int err, hit, idx = 0; if ((err = snd_config_search(config, "@hooks", &n)) < 0) return 0; snd_config_lock(); snd_config_remove(n); do { hit = 0; snd_config_for_each(i, next, n) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = n->id; long i; err = safe_strtol(id, &i); if (err < 0) { SNDERR("id of field %s is not and integer", id); err = -EINVAL; goto _err; } if (i == idx) { err = snd_config_hooks_call(config, n, private_data); if (err < 0) goto _err; idx++; hit = 1; } } } while (hit); err = 0; _err: snd_config_delete(n); snd_config_unlock(); return err; } static int config_filename_filter(const struct dirent *dirent) { size_t flen; if (dirent == NULL) return 0; if (dirent->d_type == DT_DIR) return 0; flen = strlen(dirent->d_name); if (flen <= 5) return 0; if (strncmp(&dirent->d_name[flen-5], ".conf", 5) == 0) return 1; return 0; } static int config_file_open(snd_config_t *root, const char *filename) { snd_input_t *in; int err; err = snd_input_stdio_open(&in, filename, "r"); if (err >= 0) { err = snd_config_load(root, in); snd_input_close(in); if (err < 0) SNDERR("%s may be old or corrupted: consider to remove or fix it", filename); } else SNDERR("cannot access file %s", filename); return err; } static int config_file_load(snd_config_t *root, const char *fn, int errors) { struct stat st; struct dirent **namelist; int err, n; if (!errors && access(fn, R_OK) < 0) return 1; if (stat(fn, &st) < 0) { SNDERR("cannot stat file/directory %s", fn); return 1; } if (!S_ISDIR(st.st_mode)) return config_file_open(root, fn); #ifndef DOC_HIDDEN #if defined(_GNU_SOURCE) && !defined(__NetBSD__) && !defined(__FreeBSD__) && !defined(__sun) && !defined(ANDROID) #define SORTFUNC versionsort #else #define SORTFUNC alphasort #endif #endif n = scandir(fn, &namelist, config_filename_filter, SORTFUNC); if (n > 0) { int j; err = 0; for (j = 0; j < n; ++j) { if (err >= 0) { int sl = strlen(fn) + strlen(namelist[j]->d_name) + 2; char *filename = malloc(sl); snprintf(filename, sl, "%s/%s", fn, namelist[j]->d_name); filename[sl-1] = '\0'; err = config_file_open(root, filename); free(filename); } free(namelist[j]); } free(namelist); if (err < 0) return err; } return 0; } static int config_file_load_user(snd_config_t *root, const char *fn, int errors) { char *fn2; int err; err = snd_user_file(fn, &fn2); if (err < 0) return config_file_load(root, fn, errors); err = config_file_load(root, fn2, errors); free(fn2); return err; } /** * \brief Loads and parses the given configurations files. * \param[in] root Handle to the root configuration node. * \param[in] config Handle to the configuration node for this hook. * \param[out] dst The function puts the handle to the configuration * node loaded from the file(s) at the address specified * by \a dst. * \param[in] private_data Handle to the private data configuration node. * \return Zero if successful, otherwise a negative error code. * * See \ref confhooks for an example. */ int snd_config_hook_load(snd_config_t *root, snd_config_t *config, snd_config_t **dst, snd_config_t *private_data) { snd_config_t *n; snd_config_iterator_t i, next; int err, idx = 0, errors = 1, hit; assert(root && dst); if ((err = snd_config_search(config, "errors", &n)) >= 0) { char *tmp; err = snd_config_get_ascii(n, &tmp); if (err < 0) return err; errors = snd_config_get_bool_ascii(tmp); free(tmp); if (errors < 0) { SNDERR("Invalid bool value in field errors"); return errors; } } if ((err = snd_config_search(config, "files", &n)) < 0) { SNDERR("Unable to find field files in the pre-load section"); return -EINVAL; } if ((err = snd_config_expand(n, root, NULL, private_data, &n)) < 0) { SNDERR("Unable to expand filenames in the pre-load section"); return err; } if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) { SNDERR("Invalid type for field filenames"); goto _err; } do { hit = 0; snd_config_for_each(i, next, n) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = n->id; long i; err = safe_strtol(id, &i); if (err < 0) { SNDERR("id of field %s is not and integer", id); err = -EINVAL; goto _err; } if (i == idx) { char *name, *name2, *remain; if ((err = snd_config_get_ascii(n, &name)) < 0) goto _err; name2 = name; remain = strstr(name, "|||"); while (1) { if (remain) { *remain = '\0'; remain += 3; } err = config_file_load_user(root, name2, errors); if (err < 0) goto _err; if (err == 0) /* first hit wins */ break; if (!remain) break; name2 = remain; remain = strstr(remain, "|||"); } free(name); idx++; hit = 1; } } } while (hit); *dst = NULL; err = 0; _err: snd_config_delete(n); return err; } #ifndef DOC_HIDDEN SND_DLSYM_BUILD_VERSION(snd_config_hook_load, SND_CONFIG_DLSYM_VERSION_HOOK); #endif #ifndef DOC_HIDDEN int snd_determine_driver(int card, char **driver); #endif /** * \brief Loads and parses the given configurations files for each * installed sound card. * \param[in] root Handle to the root configuration node. * \param[in] config Handle to the configuration node for this hook. * \param[out] dst The function puts the handle to the configuration * node loaded from the file(s) at the address specified * by \a dst. * \param[in] private_data Handle to the private data configuration node. * \return Zero if successful, otherwise a negative error code. * * This function works like #snd_config_hook_load, but the files are * loaded once for each sound card. The driver name is available with * the \c private_string function to customize the file name. */ int snd_config_hook_load_for_all_cards(snd_config_t *root, snd_config_t *config, snd_config_t **dst, snd_config_t *private_data ATTRIBUTE_UNUSED) { int card = -1, err; do { err = snd_card_next(&card); if (err < 0) return err; if (card >= 0) { snd_config_t *n, *private_data = NULL; const char *driver; char *fdriver = NULL; err = snd_determine_driver(card, &fdriver); if (err < 0) return err; if (snd_config_search(root, fdriver, &n) >= 0) { if (snd_config_get_string(n, &driver) < 0) goto __err; assert(driver); while (1) { char *s = strchr(driver, '.'); if (s == NULL) break; driver = s + 1; } if (snd_config_search(root, driver, &n) >= 0) goto __err; } else { driver = fdriver; } err = snd_config_imake_string(&private_data, "string", driver); if (err < 0) goto __err; err = snd_config_hook_load(root, config, &n, private_data); __err: if (private_data) snd_config_delete(private_data); free(fdriver); if (err < 0) return err; } } while (card >= 0); *dst = NULL; return 0; } #ifndef DOC_HIDDEN SND_DLSYM_BUILD_VERSION(snd_config_hook_load_for_all_cards, SND_CONFIG_DLSYM_VERSION_HOOK); #endif /** * \brief Updates a configuration tree by rereading the configuration files (if needed). * \param[in,out] _top Address of the handle to the top-level node. * \param[in,out] _update Address of a pointer to private update information. * \param[in] cfgs A list of configuration file names, delimited with ':'. * If \p cfgs is \c NULL, the default global * configuration file is used. * \return 0 if \a _top was up to date, 1 if the configuration files * have been reread, otherwise a negative error code. * * The variables pointed to by \a _top and \a _update can be initialized * to \c NULL before the first call to this function. The private * update information holds information about all used configuration * files that allows this function to detects changes to them; this data * can be freed with #snd_config_update_free. * * The global configuration files are specified in the environment variable * \c ALSA_CONFIG_PATH. * * \warning If the configuration tree is reread, all string pointers and * configuration node handles previously obtained from this tree become * invalid. * * \par Errors: * Any errors encountered when parsing the input or returned by hooks or * functions. */ int snd_config_update_r(snd_config_t **_top, snd_config_update_t **_update, const char *cfgs) { int err; const char *configs, *c; unsigned int k; size_t l; snd_config_update_t *local; snd_config_update_t *update; snd_config_t *top; assert(_top && _update); top = *_top; update = *_update; configs = cfgs; if (!configs) { configs = getenv(ALSA_CONFIG_PATH_VAR); if (!configs || !*configs) { const char *topdir = snd_config_topdir(); char *s = alloca(strlen(topdir) + strlen("alsa.conf") + 2); sprintf(s, "%s/alsa.conf", topdir); configs = s; } } for (k = 0, c = configs; (l = strcspn(c, ": ")) > 0; ) { c += l; k++; if (!*c) break; c++; } if (k == 0) { local = NULL; goto _reread; } local = (snd_config_update_t *)calloc(1, sizeof(snd_config_update_t)); if (!local) return -ENOMEM; local->count = k; local->finfo = calloc(local->count, sizeof(struct finfo)); if (!local->finfo) { free(local); return -ENOMEM; } for (k = 0, c = configs; (l = strcspn(c, ": ")) > 0; ) { char name[l + 1]; memcpy(name, c, l); name[l] = 0; err = snd_user_file(name, &local->finfo[k].name); if (err < 0) goto _end; c += l; k++; if (!*c) break; c++; } for (k = 0; k < local->count; ++k) { struct stat st; struct finfo *lf = &local->finfo[k]; if (stat(lf->name, &st) >= 0) { lf->dev = st.st_dev; lf->ino = st.st_ino; lf->mtime = st.st_mtime; } else { SNDERR("Cannot access file %s", lf->name); free(lf->name); memmove(&local->finfo[k], &local->finfo[k+1], sizeof(struct finfo) * (local->count - k - 1)); k--; local->count--; } } if (!update) goto _reread; if (local->count != update->count) goto _reread; for (k = 0; k < local->count; ++k) { struct finfo *lf = &local->finfo[k]; struct finfo *uf = &update->finfo[k]; if (strcmp(lf->name, uf->name) != 0 || lf->dev != uf->dev || lf->ino != uf->ino || lf->mtime != uf->mtime) goto _reread; } err = 0; _end: if (err < 0) { if (top) { snd_config_delete(top); *_top = NULL; } if (update) { snd_config_update_free(update); *_update = NULL; } } if (local) snd_config_update_free(local); return err; _reread: *_top = NULL; *_update = NULL; if (update) { snd_config_update_free(update); update = NULL; } if (top) { snd_config_delete(top); top = NULL; } err = snd_config_top(&top); if (err < 0) goto _end; if (!local) goto _skip; for (k = 0; k < local->count; ++k) { snd_input_t *in; err = snd_input_stdio_open(&in, local->finfo[k].name, "r"); if (err >= 0) { err = snd_config_load(top, in); snd_input_close(in); if (err < 0) { SNDERR("%s may be old or corrupted: consider to remove or fix it", local->finfo[k].name); goto _end; } } else { SNDERR("cannot access file %s", local->finfo[k].name); } } _skip: err = snd_config_hooks(top, NULL); if (err < 0) { SNDERR("hooks failed, removing configuration"); goto _end; } *_top = top; *_update = local; return 1; } /** * \brief Updates #snd_config by rereading the global configuration files (if needed). * \return 0 if #snd_config was up to date, 1 if #snd_config was * updated, otherwise a negative error code. * * \warning Whenever #snd_config is updated, all string pointers and * configuration node handles previously obtained from it may become * invalid. * For safer operations, use #snd_config_update_ref and release the config * via #snd_config_unref. * * \par Errors: * Any errors encountered when parsing the input or returned by hooks or * functions. * * \par Conforming to: * LSB 3.2 */ int snd_config_update(void) { int err; snd_config_lock(); err = snd_config_update_r(&snd_config, &snd_config_global_update, NULL); snd_config_unlock(); return err; } /** * \brief Updates #snd_config and takes its reference. * \return 0 if #snd_config was up to date, 1 if #snd_config was * updated, otherwise a negative error code. * * Unlike #snd_config_update, this function increases a reference counter * so that the obtained tree won't be deleted until unreferenced by * #snd_config_unref. * * This function is supposed to be thread-safe. */ int snd_config_update_ref(snd_config_t **top) { int err; if (top) *top = NULL; snd_config_lock(); err = snd_config_update_r(&snd_config, &snd_config_global_update, NULL); if (err >= 0) { if (snd_config) { if (top) { snd_config->refcount++; *top = snd_config; } } else { err = -ENODEV; } } snd_config_unlock(); return err; } /** * \brief Take the reference of the config tree. * * Increases a reference counter of the given config tree. * * This function is supposed to be thread-safe. */ void snd_config_ref(snd_config_t *cfg) { snd_config_lock(); if (cfg) cfg->refcount++; snd_config_unlock(); } /** * \brief Unreference the config tree. * * Decreases a reference counter of the given config tree, and eventually * deletes the tree if all references are gone. This is the counterpart of * #snd_config_unref. * * Also, the config taken via #snd_config_update_ref must be unreferenced * by this function, too. * * This function is supposed to be thread-safe. */ void snd_config_unref(snd_config_t *cfg) { snd_config_lock(); if (cfg) snd_config_delete(cfg); snd_config_unlock(); } /** * \brief Frees a private update structure. * \param[in] update The private update structure to free. * \return Zero if successful, otherwise a negative error code. */ int snd_config_update_free(snd_config_update_t *update) { unsigned int k; assert(update); for (k = 0; k < update->count; k++) free(update->finfo[k].name); free(update->finfo); free(update); return 0; } /** * \brief Frees the global configuration tree in #snd_config. * \return Zero if successful, otherwise a negative error code. * * This functions releases all resources of the global configuration * tree, and sets #snd_config to \c NULL. * * \par Conforming to: * LSB 3.2 */ int snd_config_update_free_global(void) { snd_config_lock(); if (snd_config) snd_config_delete(snd_config); snd_config = NULL; if (snd_config_global_update) snd_config_update_free(snd_config_global_update); snd_config_global_update = NULL; snd_config_unlock(); /* FIXME: better to place this in another place... */ snd_dlobj_cache_cleanup(); return 0; } /** * \brief Returns an iterator pointing to a node's first child. * \param[in] config Handle to a configuration node. * \return An iterator pointing to \a config's first child. * * \a config must be a compound node. * * The returned iterator is valid if it is not equal to the return value * of #snd_config_iterator_end on \a config. * * Use #snd_config_iterator_entry to get the handle of the node pointed * to. * * \par Conforming to: * LSB 3.2 */ snd_config_iterator_t snd_config_iterator_first(const snd_config_t *config) { assert(config->type == SND_CONFIG_TYPE_COMPOUND); return config->u.compound.fields.next; } /** * \brief Returns an iterator pointing to the next sibling. * \param[in] iterator An iterator pointing to a child configuration node. * \return An iterator pointing to the next sibling of \a iterator. * * The returned iterator is valid if it is not equal to the return value * of #snd_config_iterator_end on the node's parent. * * Use #snd_config_iterator_entry to get the handle of the node pointed * to. * * \par Conforming to: * LSB 3.2 */ snd_config_iterator_t snd_config_iterator_next(const snd_config_iterator_t iterator) { return iterator->next; } /** * \brief Returns an iterator that ends a node's children list. * \param[in] config Handle to a configuration node. * \return An iterator that indicates the end of \a config's children list. * * \a config must be a compound node. * * The return value can be understood as pointing past the last child of * \a config. * * \par Conforming to: * LSB 3.2 */ snd_config_iterator_t snd_config_iterator_end(const snd_config_t *config) { assert(config->type == SND_CONFIG_TYPE_COMPOUND); return (const snd_config_iterator_t)&config->u.compound.fields; } /** * \brief Returns the configuration node handle pointed to by an iterator. * \param[in] iterator A configuration node iterator. * \return The configuration node handle pointed to by \a iterator. * * \par Conforming to: * LSB 3.2 */ snd_config_t *snd_config_iterator_entry(const snd_config_iterator_t iterator) { return list_entry(iterator, snd_config_t, list); } #ifndef DOC_HIDDEN typedef enum _snd_config_walk_pass { SND_CONFIG_WALK_PASS_PRE, SND_CONFIG_WALK_PASS_POST, SND_CONFIG_WALK_PASS_LEAF, } snd_config_walk_pass_t; #endif /* Return 1 if node needs to be attached to parent */ /* Return 2 if compound is replaced with standard node */ #ifndef DOC_HIDDEN typedef int (*snd_config_walk_callback_t)(snd_config_t *src, snd_config_t *root, snd_config_t **dst, snd_config_walk_pass_t pass, snd_config_t *private_data); #endif static int snd_config_walk(snd_config_t *src, snd_config_t *root, snd_config_t **dst, snd_config_walk_callback_t callback, snd_config_t *private_data) { int err; snd_config_iterator_t i, next; switch (snd_config_get_type(src)) { case SND_CONFIG_TYPE_COMPOUND: err = callback(src, root, dst, SND_CONFIG_WALK_PASS_PRE, private_data); if (err <= 0) return err; snd_config_for_each(i, next, src) { snd_config_t *s = snd_config_iterator_entry(i); snd_config_t *d = NULL; err = snd_config_walk(s, root, (dst && *dst) ? &d : NULL, callback, private_data); if (err < 0) goto _error; if (err && d) { err = snd_config_add(*dst, d); if (err < 0) goto _error; } } err = callback(src, root, dst, SND_CONFIG_WALK_PASS_POST, private_data); if (err <= 0) { _error: if (dst && *dst) snd_config_delete(*dst); } break; default: err = callback(src, root, dst, SND_CONFIG_WALK_PASS_LEAF, private_data); break; } return err; } static int _snd_config_copy(snd_config_t *src, snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t **dst, snd_config_walk_pass_t pass, snd_config_t *private_data ATTRIBUTE_UNUSED) { int err; const char *id = src->id; snd_config_type_t type = snd_config_get_type(src); switch (pass) { case SND_CONFIG_WALK_PASS_PRE: err = snd_config_make_compound(dst, id, src->u.compound.join); if (err < 0) return err; break; case SND_CONFIG_WALK_PASS_LEAF: err = snd_config_make(dst, id, type); if (err < 0) return err; switch (type) { case SND_CONFIG_TYPE_INTEGER: { long v; err = snd_config_get_integer(src, &v); assert(err >= 0); snd_config_set_integer(*dst, v); break; } case SND_CONFIG_TYPE_INTEGER64: { long long v; err = snd_config_get_integer64(src, &v); assert(err >= 0); snd_config_set_integer64(*dst, v); break; } case SND_CONFIG_TYPE_REAL: { double v; err = snd_config_get_real(src, &v); assert(err >= 0); snd_config_set_real(*dst, v); break; } case SND_CONFIG_TYPE_STRING: { const char *s; err = snd_config_get_string(src, &s); assert(err >= 0); err = snd_config_set_string(*dst, s); if (err < 0) return err; break; } default: assert(0); } break; default: break; } return 1; } /** * \brief Creates a copy of a configuration node. * \param[out] dst The function puts the handle to the new configuration * node at the address specified by \a dst. * \param[in] src Handle to the source configuration node. * \return A non-negative value if successful, otherwise a negative error code. * * This function creates a deep copy, i.e., if \a src is a compound * node, all children are copied recursively. * * \par Errors: *
*
-ENOMEM
Out of memory. *
* * \par Conforming to: * LSB 3.2 */ int snd_config_copy(snd_config_t **dst, snd_config_t *src) { return snd_config_walk(src, NULL, dst, _snd_config_copy, NULL); } static int _snd_config_expand(snd_config_t *src, snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t **dst, snd_config_walk_pass_t pass, snd_config_t *private_data) { int err; const char *id = src->id; snd_config_type_t type = snd_config_get_type(src); switch (pass) { case SND_CONFIG_WALK_PASS_PRE: { if (id && strcmp(id, "@args") == 0) return 0; err = snd_config_make_compound(dst, id, src->u.compound.join); if (err < 0) return err; break; } case SND_CONFIG_WALK_PASS_LEAF: switch (type) { case SND_CONFIG_TYPE_INTEGER: { long v; err = snd_config_get_integer(src, &v); assert(err >= 0); err = snd_config_imake_integer(dst, id, v); if (err < 0) return err; break; } case SND_CONFIG_TYPE_INTEGER64: { long long v; err = snd_config_get_integer64(src, &v); assert(err >= 0); err = snd_config_imake_integer64(dst, id, v); if (err < 0) return err; break; } case SND_CONFIG_TYPE_REAL: { double v; err = snd_config_get_real(src, &v); assert(err >= 0); err = snd_config_imake_real(dst, id, v); if (err < 0) return err; break; } case SND_CONFIG_TYPE_STRING: { const char *s; snd_config_t *val; snd_config_t *vars = private_data; snd_config_get_string(src, &s); if (s && *s == '$') { s++; if (snd_config_search(vars, s, &val) < 0) return 0; err = snd_config_copy(dst, val); if (err < 0) return err; err = snd_config_set_id(*dst, id); if (err < 0) { snd_config_delete(*dst); return err; } } else { err = snd_config_imake_string(dst, id, s); if (err < 0) return err; } break; } default: assert(0); } break; default: break; } return 1; } static int _snd_config_evaluate(snd_config_t *src, snd_config_t *root, snd_config_t **dst ATTRIBUTE_UNUSED, snd_config_walk_pass_t pass, snd_config_t *private_data) { int err; if (pass == SND_CONFIG_WALK_PASS_PRE) { char *buf = NULL, errbuf[256]; const char *lib = NULL, *func_name = NULL; const char *str; int (*func)(snd_config_t **dst, snd_config_t *root, snd_config_t *src, snd_config_t *private_data) = NULL; void *h = NULL; snd_config_t *c, *func_conf = NULL; err = snd_config_search(src, "@func", &c); if (err < 0) return 1; err = snd_config_get_string(c, &str); if (err < 0) { SNDERR("Invalid type for @func"); return err; } assert(str); err = snd_config_search_definition(root, "func", str, &func_conf); if (err >= 0) { snd_config_iterator_t i, next; if (snd_config_get_type(func_conf) != SND_CONFIG_TYPE_COMPOUND) { SNDERR("Invalid type for func %s definition", str); err = -EINVAL; goto _err; } snd_config_for_each(i, next, func_conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = n->id; if (strcmp(id, "comment") == 0) continue; if (strcmp(id, "lib") == 0) { err = snd_config_get_string(n, &lib); if (err < 0) { SNDERR("Invalid type for %s", id); goto _err; } continue; } if (strcmp(id, "func") == 0) { err = snd_config_get_string(n, &func_name); if (err < 0) { SNDERR("Invalid type for %s", id); goto _err; } continue; } SNDERR("Unknown field %s", id); } } if (!func_name) { int len = 9 + strlen(str) + 1; buf = malloc(len); if (! buf) { err = -ENOMEM; goto _err; } snprintf(buf, len, "snd_func_%s", str); buf[len-1] = '\0'; func_name = buf; } h = INTERNAL(snd_dlopen)(lib, RTLD_NOW, errbuf, sizeof(errbuf)); if (h) func = snd_dlsym(h, func_name, SND_DLSYM_VERSION(SND_CONFIG_DLSYM_VERSION_EVALUATE)); err = 0; if (!h) { SNDERR("Cannot open shared library %s (%s)", lib, errbuf); err = -ENOENT; goto _errbuf; } else if (!func) { SNDERR("symbol %s is not defined inside %s", func_name, lib); snd_dlclose(h); err = -ENXIO; goto _errbuf; } _err: if (func_conf) snd_config_delete(func_conf); if (err >= 0) { snd_config_t *eval; err = func(&eval, root, src, private_data); if (err < 0) SNDERR("function %s returned error: %s", func_name, snd_strerror(err)); snd_dlclose(h); if (err >= 0 && eval) { /* substitute merges compound members */ /* we don't want merging at all */ err = snd_config_delete_compound_members(src); if (err >= 0) err = snd_config_substitute(src, eval); } } _errbuf: free(buf); if (err < 0) return err; return 0; } return 1; } /** * \brief Evaluates a configuration node at runtime. * \param[in,out] config Handle to the source configuration node. * \param[in] root Handle to the root of the source configuration. * \param[in] private_data Handle to the private data node for runtime evaluation. * \param result Must be \c NULL. * \return A non-negative value if successful, otherwise a negative error code. * * This function evaluates any functions (\c \@func) in \a config and * replaces those nodes with the respective function results. */ int snd_config_evaluate(snd_config_t *config, snd_config_t *root, snd_config_t *private_data, snd_config_t **result) { /* FIXME: Only in place evaluation is currently implemented */ assert(result == NULL); return snd_config_walk(config, root, result, _snd_config_evaluate, private_data); } static int load_defaults(snd_config_t *subs, snd_config_t *defs) { snd_config_iterator_t d, dnext; snd_config_for_each(d, dnext, defs) { snd_config_t *def = snd_config_iterator_entry(d); snd_config_iterator_t f, fnext; if (snd_config_get_type(def) != SND_CONFIG_TYPE_COMPOUND) continue; snd_config_for_each(f, fnext, def) { snd_config_t *fld = snd_config_iterator_entry(f); const char *id = fld->id; if (strcmp(id, "type") == 0) continue; if (strcmp(id, "default") == 0) { snd_config_t *deflt; int err; err = snd_config_copy(&deflt, fld); if (err < 0) return err; err = snd_config_set_id(deflt, def->id); if (err < 0) { snd_config_delete(deflt); return err; } err = snd_config_add(subs, deflt); if (err < 0) { snd_config_delete(deflt); return err; } continue; } SNDERR("Unknown field %s", id); return -EINVAL; } } return 0; } static void skip_blank(const char **ptr) { while (1) { switch (**ptr) { case ' ': case '\f': case '\t': case '\n': case '\r': break; default: return; } (*ptr)++; } } static int parse_char(const char **ptr) { int c; assert(**ptr == '\\'); (*ptr)++; c = **ptr; switch (c) { case 'n': c = '\n'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case 'b': c = '\b'; break; case 'r': c = '\r'; break; case 'f': c = '\f'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { int num = c - '0'; int i = 1; (*ptr)++; do { c = **ptr; if (c < '0' || c > '7') break; num = num * 8 + c - '0'; i++; (*ptr)++; } while (i < 3); return num; } default: break; } (*ptr)++; return c; } static int parse_id(const char **ptr) { if (!**ptr) return -EINVAL; while (1) { switch (**ptr) { case '\f': case '\t': case '\n': case '\r': case ',': case '=': case '\0': return 0; default: break; } (*ptr)++; } } static int parse_string(const char **ptr, char **val) { const size_t bufsize = 256; char _buf[bufsize]; char *buf = _buf; size_t alloc = bufsize; char delim = **ptr; size_t idx = 0; (*ptr)++; while (1) { int c = **ptr; switch (c) { case '\0': SNDERR("Unterminated string"); return -EINVAL; case '\\': c = parse_char(ptr); if (c < 0) { if (alloc > bufsize) free(buf); return c; } break; default: (*ptr)++; if (c == delim) { *val = malloc(idx + 1); if (!*val) return -ENOMEM; memcpy(*val, buf, idx); (*val)[idx] = 0; if (alloc > bufsize) free(buf); return 0; } } if (idx >= alloc) { size_t old_alloc = alloc; alloc *= 2; if (old_alloc == bufsize) { buf = malloc(alloc); if (!buf) return -ENOMEM; memcpy(buf, _buf, old_alloc); } else { char *buf2 = realloc(buf, alloc); if (!buf2) { free(buf); return -ENOMEM; } buf = buf2; } } buf[idx++] = c; } } /* Parse var=val or val */ static int parse_arg(const char **ptr, unsigned int *varlen, char **val) { const char *str; int err, vallen; skip_blank(ptr); str = *ptr; if (*str == '"' || *str == '\'') { err = parse_string(ptr, val); if (err < 0) return err; *varlen = 0; return 0; } err = parse_id(ptr); if (err < 0) return err; vallen = *ptr - str; skip_blank(ptr); if (**ptr != '=') { *varlen = 0; goto _value; } *varlen = vallen; (*ptr)++; skip_blank(ptr); str = *ptr; if (*str == '"' || *str == '\'') { err = parse_string(ptr, val); if (err < 0) return err; return 0; } err = parse_id(ptr); if (err < 0) return err; vallen = *ptr - str; _value: *val = malloc(vallen + 1); if (!*val) return -ENOMEM; memcpy(*val, str, vallen); (*val)[vallen] = 0; return 0; } /* val1, val2, ... * var1=val1,var2=val2,... * { conf syntax } */ static int parse_args(snd_config_t *subs, const char *str, snd_config_t *defs) { int err; int arg = 0; if (str == NULL) return 0; skip_blank(&str); if (!*str) return 0; if (*str == '{') { int len = strlen(str); snd_input_t *input; snd_config_iterator_t i, next; while (1) { switch (str[--len]) { case ' ': case '\f': case '\t': case '\n': case '\r': continue; default: break; } break; } if (str[len] != '}') return -EINVAL; err = snd_input_buffer_open(&input, str + 1, len - 1); if (err < 0) return err; err = snd_config_load_override(subs, input); snd_input_close(input); if (err < 0) return err; snd_config_for_each(i, next, subs) { snd_config_t *n = snd_config_iterator_entry(i); snd_config_t *d; const char *id = n->id; err = snd_config_search(defs, id, &d); if (err < 0) { SNDERR("Unknown parameter %s", id); return err; } } return 0; } while (1) { char buf[256]; const char *var = buf; unsigned int varlen; snd_config_t *def, *sub, *typ; const char *new = str; const char *tmp; char *val = NULL; err = parse_arg(&new, &varlen, &val); if (err < 0) goto _err; if (varlen > 0) { assert(varlen < sizeof(buf)); memcpy(buf, str, varlen); buf[varlen] = 0; } else { sprintf(buf, "%d", arg); } err = snd_config_search_alias(defs, NULL, var, &def); if (err < 0) { SNDERR("Unknown parameter %s", var); goto _err; } if (snd_config_get_type(def) != SND_CONFIG_TYPE_COMPOUND) { SNDERR("Parameter %s definition is not correct", var); err = -EINVAL; goto _err; } var = def->id; err = snd_config_search(subs, var, &sub); if (err >= 0) snd_config_delete(sub); err = snd_config_search(def, "type", &typ); if (err < 0) { _invalid_type: SNDERR("Parameter %s definition is missing a valid type info", var); goto _err; } err = snd_config_get_string(typ, &tmp); if (err < 0 || !tmp) goto _invalid_type; if (strcmp(tmp, "integer") == 0) { long v; err = snd_config_make(&sub, var, SND_CONFIG_TYPE_INTEGER); if (err < 0) goto _err; err = safe_strtol(val, &v); if (err < 0) { SNDERR("Parameter %s must be an integer", var); goto _err; } err = snd_config_set_integer(sub, v); if (err < 0) goto _err; } else if (strcmp(tmp, "integer64") == 0) { long long v; err = snd_config_make(&sub, var, SND_CONFIG_TYPE_INTEGER64); if (err < 0) goto _err; err = safe_strtoll(val, &v); if (err < 0) { SNDERR("Parameter %s must be an integer", var); goto _err; } err = snd_config_set_integer64(sub, v); if (err < 0) goto _err; } else if (strcmp(tmp, "real") == 0) { double v; err = snd_config_make(&sub, var, SND_CONFIG_TYPE_REAL); if (err < 0) goto _err; err = safe_strtod(val, &v); if (err < 0) { SNDERR("Parameter %s must be a real", var); goto _err; } err = snd_config_set_real(sub, v); if (err < 0) goto _err; } else if (strcmp(tmp, "string") == 0) { err = snd_config_make(&sub, var, SND_CONFIG_TYPE_STRING); if (err < 0) goto _err; err = snd_config_set_string(sub, val); if (err < 0) goto _err; } else { err = -EINVAL; goto _invalid_type; } err = snd_config_set_id(sub, var); if (err < 0) goto _err; err = snd_config_add(subs, sub); if (err < 0) { _err: free(val); return err; } free(val); if (!*new) break; if (*new != ',') return -EINVAL; str = new + 1; arg++; } return 0; } /** * \brief Expands a configuration node, applying arguments and functions. * \param[in] config Handle to the configuration node. * \param[in] root Handle to the root configuration node. * \param[in] args Arguments string, can be \c NULL. * \param[in] private_data Handle to the private data node for functions. * \param[out] result The function puts the handle to the result * configuration node at the address specified by * \a result. * \return A non-negative value if successful, otherwise a negative error code. * * If \a config has arguments (defined by a child with id \c \@args), * this function replaces any string node beginning with $ with the * respective argument value, or the default argument value, or nothing. * Furthermore, any functions are evaluated (see #snd_config_evaluate). * The resulting copy of \a config is returned in \a result. */ int snd_config_expand(snd_config_t *config, snd_config_t *root, const char *args, snd_config_t *private_data, snd_config_t **result) { int err; snd_config_t *defs, *subs = NULL, *res; err = snd_config_search(config, "@args", &defs); if (err < 0) { if (args != NULL) { SNDERR("Unknown parameters %s", args); return -EINVAL; } err = snd_config_copy(&res, config); if (err < 0) return err; } else { err = snd_config_top(&subs); if (err < 0) return err; err = load_defaults(subs, defs); if (err < 0) { SNDERR("Load defaults error: %s", snd_strerror(err)); goto _end; } err = parse_args(subs, args, defs); if (err < 0) { SNDERR("Parse arguments error: %s", snd_strerror(err)); goto _end; } err = snd_config_evaluate(subs, root, private_data, NULL); if (err < 0) { SNDERR("Args evaluate error: %s", snd_strerror(err)); goto _end; } err = snd_config_walk(config, root, &res, _snd_config_expand, subs); if (err < 0) { SNDERR("Expand error (walk): %s", snd_strerror(err)); goto _end; } } err = snd_config_evaluate(res, root, private_data, NULL); if (err < 0) { SNDERR("Evaluate error: %s", snd_strerror(err)); snd_config_delete(res); goto _end; } *result = res; err = 1; _end: if (subs) snd_config_delete(subs); return err; } /** * \brief Searches for a definition in a configuration tree, using * aliases and expanding hooks and arguments. * \param[in] config Handle to the configuration (sub)tree to search. * \param[in] base Implicit key base, or \c NULL for none. * \param[in] name Key suffix, optionally with arguments. * \param[out] result The function puts the handle to the expanded found * node at the address specified by \a result. * \return A non-negative value if successful, otherwise a negative error code. * * This functions searches for a child node of \a config, allowing * aliases and expanding hooks, like #snd_config_search_alias_hooks. * * If \a name contains a colon (:), the rest of the string after the * colon contains arguments that are expanded as with * #snd_config_expand. * * In any case, \a result is a new node that must be freed by the * caller. * * \par Errors: *
*
-ENOENT
An id in \a key or an alias id does not exist. *
-ENOENT
\a config or one of its child nodes to be searched is * not a compound node. *
* Additionally, any errors encountered when parsing the hook * definitions or arguments, or returned by (hook) functions. */ int snd_config_search_definition(snd_config_t *config, const char *base, const char *name, snd_config_t **result) { snd_config_t *conf; char *key; const char *args = strchr(name, ':'); int err; if (args) { args++; key = alloca(args - name); memcpy(key, name, args - name - 1); key[args - name - 1] = '\0'; } else { key = (char *) name; } /* * if key contains dot (.), the implicit base is ignored * and the key starts from root given by the 'config' parameter */ snd_config_lock(); err = snd_config_search_alias_hooks(config, strchr(key, '.') ? NULL : base, key, &conf); if (err < 0) { snd_config_unlock(); return err; } err = snd_config_expand(conf, config, args, NULL, result); snd_config_unlock(); return err; } #ifndef DOC_HIDDEN void snd_config_set_hop(snd_config_t *conf, int hop) { conf->hop = hop; } int snd_config_check_hop(snd_config_t *conf) { if (conf) { if (conf->hop >= SND_CONF_MAX_HOPS) { SYSERR("Too many definition levels (looped?)\n"); return -EINVAL; } return conf->hop; } return 0; } #endif #if 0 /* Not strictly needed, but useful to check for memory leaks */ void _snd_config_end(void) __attribute__ ((destructor)); static void _snd_config_end(void) { int k; if (snd_config) snd_config_delete(snd_config); snd_config = 0; for (k = 0; k < files_info_count; ++k) free(files_info[k].name); free(files_info); files_info = NULL; files_info_count = 0; } #endif size_t page_size(void) { long s = sysconf(_SC_PAGE_SIZE); assert(s > 0); return s; } size_t page_align(size_t size) { size_t r; long psz = page_size(); r = size % psz; if (r) return size + psz - r; return size; } size_t page_ptr(size_t object_offset, size_t object_size, size_t *offset, size_t *mmap_offset) { size_t r; long psz = page_size(); assert(offset); assert(mmap_offset); *mmap_offset = object_offset; object_offset %= psz; *mmap_offset -= object_offset; object_size += object_offset; r = object_size % psz; if (r) r = object_size + psz - r; else r = object_size; *offset = object_offset; return r; }