| /** |
| * \file conf.c |
| * \ingroup Configuration |
| * \brief Configuration helper functions |
| * \author Abramo Bagnara <abramo@alsa-project.org> |
| * \author Jaroslav Kysela <perex@perex.cz> |
| * \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 <abramo@alsa-project.org>, |
| * Jaroslav Kysela <perex@perex.cz> |
| * |
| * |
| * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| /*! \page conf Configuration files |
| |
| <P>Configuration files use a simple format allowing modern |
| data description like nesting and array assignments.</P> |
| |
| \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 |
| </etc/alsa1.conf> |
| <confdir:pcm/surround.conf> |
| \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 |
| <filename> |
| |
| # 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 |
| |
| <P>The ALSA library can accept runtime arguments for some configuration |
| blocks. This extension is built on top of the basic configuration file |
| syntax.<P> |
| |
| \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 |
| |
| <P>The ALSA library can modify the configuration at runtime. |
| Several built-in functions are available.</P> |
| |
| <P>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.\<function_name\> 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:</P> |
| |
| \code |
| func.remove_first_char { |
| lib "/usr/lib/libasoundextend.so" |
| func "extend_remove_first_char" |
| } |
| \endcode |
| |
| */ |
| |
| /*! \page confhooks Hooks in configuration files |
| |
| <P>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.</P> |
| |
| <P>This example defines a hook which loads two configuration files at the |
| beginning:</P> |
| |
| \code |
| @hooks [ |
| { |
| func load |
| files [ |
| "/etc/asound.conf" |
| "~/.asoundrc" |
| ] |
| errors false |
| } |
| ] |
| \endcode |
| |
| \section confhooks_ref Function reference |
| |
| <UL> |
| <LI>The function load - \c snd_config_hook_load() - loads and parses the |
| given configuration files. |
| <LI>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. |
| </UL> |
| |
| */ |
| |
| |
| #include <stdarg.h> |
| #include <limits.h> |
| #include <sys/stat.h> |
| #include <dirent.h> |
| #include <locale.h> |
| #include "local.h" |
| #ifdef HAVE_LIBPTHREAD |
| #include <pthread.h> |
| #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; |
| union { |
| long integer; |
| long long integer64; |
| char *string; |
| double real; |
| const void *ptr; |
| struct { |
| struct list_head fields; |
| int 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; |
| }; |
| |
| #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); |
| pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); |
| 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 |
| |
| 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; |
| int err = get_delimstring(&str, '>', input); |
| if (err < 0) |
| return err; |
| if (!strncmp(str, "confdir:", 8)) { |
| char *tmp = malloc(strlen(ALSA_CONFIG_DIR) + 1 + strlen(str + 8) + 1); |
| if (tmp == NULL) { |
| free(str); |
| return -ENOMEM; |
| } |
| sprintf(tmp, ALSA_CONFIG_DIR "/%s", str + 8); |
| free(str); |
| str = tmp; |
| } |
| err = snd_input_stdio_open(&in, str, "r"); |
| 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; |
| 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 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 '0' ... '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; |
| 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) { |
| char static_id[12]; |
| snprintf(static_id, sizeof(static_id), "%i", idx); |
| 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; |
| } |
| 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 = 1; |
| 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 = 1; |
| 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' ... '9': |
| case '-': |
| goto quoted; |
| } |
| } |
| loop: |
| switch (*p) { |
| case 0: |
| goto nonquoted; |
| case 1 ... 31: |
| case 127 ... 255: |
| case ' ': |
| case '=': |
| case ';': |
| case ',': |
| case '.': |
| case '{': |
| case '}': |
| case '\'': |
| case '"': |
| goto quoted; |
| default: |
| 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; |
| case 32 ... '\'' - 1: |
| case '\'' + 1 ... 126: |
| snd_output_putc(out, c); |
| break; |
| default: |
| 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: |
| * <dl> |
| * <dt>integer<dt>#SND_CONFIG_TYPE_INTEGER |
| * <dt>integer64<dt>#SND_CONFIG_TYPE_INTEGER64 |
| * <dt>real<dt>#SND_CONFIG_TYPE_REAL |
| * <dt>string<dt>#SND_CONFIG_TYPE_STRING |
| * <dt>compound<dt>#SND_CONFIG_TYPE_COMPOUND |
| * </dl> |
| * |
| * \par Errors: |
| * <dl> |
| * <dt>-EINVAL<dd>Unknown note type in \a type. |
| * </dl> |
| */ |
| 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; |
| } |
| |
| /** |
| * \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: |
| * <dl> |
| * <dt>-EEXIST<dd>One of \a config's siblings already has the id \a id. |
| * <dt>-EINVAL<dd>The id of a node with a parent cannot be set to \c NULL. |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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); |
| } |
| |
| static int snd_config_load1(snd_config_t *config, snd_input_t *in, int override) |
| { |
| 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; |
| 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(fd); |
| fd = fd_next; |
| } |
| free(fd); |
| return err; |
| } |
| |
| /** |
| * \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_load1(config, in, 0); |
| } |
| |
| /** |
| * \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_load1(config, in, 1); |
| } |
| |
| /** |
| * \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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a child does not have an id. |
| * <dt>-EINVAL<dd>\a child already has a parent. |
| * <dt>-EEXIST<dd>\a parent already contains a child node with the same |
| * id as \a child. |
| * </dl> |
| * |
| * \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 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. |
| * |
| * \par Conforming to: |
| * LSB 3.2 |
| * |
| * \sa snd_config_remove |
| */ |
| int snd_config_delete(snd_config_t *config) |
| { |
| assert(config); |
| 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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a compound node. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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; |
| } |
| |
| /** |
| * \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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not an integer node. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a 64-bit-integer node. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a real-number node. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a string node. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a pointer node. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a number or string node. |
| * <dt>-EINVAL<dd>The value in \a ascii cannot be parsed. |
| * <dt>-ERANGE<dd>The value in \a ascii is too big for the node's type. |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not an integer node. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a 64-bit-integer node. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a real-number node. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a number node. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a string node. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a string node. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-EINVAL<dd>\a config is not a (64-bit) integer or real number or |
| * string node. |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-EINVAL<dd>A node in the tree has a type that cannot be printed, |
| * i.e., #SND_CONFIG_TYPE_POINTER. |
| * </dl> |
| * |
| * \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) \ |
| 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) \ |
| 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: |
| * <dl> |
| * <dt>-ENOENT<dd>An id in \a key does not exist. |
| * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is |
| * not a compound node. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist. |
| * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is |
| * not a compound or string node. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-ENOENT<dd>An id in a search key does not exist. |
| * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is |
| * not a compound node. |
| * </dl> |
| * |
| * \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: |
| * <dl> |
| * <dt>-ENOENT<dd>An id in a search key does not exist. |
| * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is |
| * not a compound or string node. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist. |
| * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is |
| * not a compound or string node. |
| * </dl> |
| */ |
| 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: |
| * <dl> |
| * <dt>-ENOENT<dd>An id in \a key does not exist. |
| * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is |
| * not a compound node. |
| * </dl> |
| * 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: |
| * <dl> |
| * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist. |
| * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is |
| * not a compound node. |
| * </dl> |
| * 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: |
| * <dl> |
| * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist. |
| * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is |
| * not a compound node. |
| * </dl> |
| * 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: |
| * <dl> |
| * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist. |
| * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is |
| * not a compound node. |
| * </dl> |
| * 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" |
| |
| /** The name of the default files used by #snd_config_update. */ |
| #define ALSA_CONFIG_PATH_DEFAULT ALSA_CONFIG_DIR "/alsa.conf" |
| |
| /** |
| * \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; |
| 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 = snd_dlopen(lib, RTLD_NOW); |
| 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", lib); |
| 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; |
| } |
| |
| /** |
| * \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; |
| struct finfo *fi = NULL; |
| int err, idx = 0, fi_count = 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; |
| } |
| snd_config_for_each(i, next, n) { |
| snd_config_t *c = snd_config_iterator_entry(i); |
| const char *str; |
| if ((err = snd_config_get_string(c, &str)) < 0) { |
| SNDERR("Field %s is not a string", c->id); |
| goto _err; |
| } |
| fi_count++; |
| } |
| fi = calloc(fi_count, sizeof(*fi)); |
| if (fi == NULL) { |
| err = -ENOMEM; |
| 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; |
| if ((err = snd_config_get_ascii(n, &name)) < 0) |
| goto _err; |
| if ((err = snd_user_file(name, &fi[idx].name)) < 0) |
| fi[idx].name = name; |
| else |
| free(name); |
| idx++; |
| hit = 1; |
| } |
| } |
| } while (hit); |
| for (idx = 0; idx < fi_count; idx++) { |
| struct stat st; |
| if (!errors && access(fi[idx].name, R_OK) < 0) |
| continue; |
| if (stat(fi[idx].name, &st) < 0) { |
| SNDERR("cannot stat file/directory %s", fi[idx].name); |
| continue; |
| } |
| if (S_ISDIR(st.st_mode)) { |
| struct dirent **namelist; |
| int n; |
| |
| #ifndef DOC_HIDDEN |
| #ifdef _GNU_SOURCE |
| #define SORTFUNC versionsort |
| #else |
| #define SORTFUNC alphasort |
| #endif |
| #endif |
| n = scandir(fi[idx].name, &namelist, config_filename_filter, SORTFUNC); |
| if (n > 0) { |
| int j; |
| err = 0; |
| for (j = 0; j < n; ++j) { |
| if (err >= 0) { |
| int sl = strlen(fi[idx].name) + strlen(namelist[j]->d_name) + 2; |
| char *filename = malloc(sl); |
| snprintf(filename, sl, "%s/%s", fi[idx].name, namelist[j]->d_name); |
| filename[sl-1] = '\0'; |
| |
| err = config_file_open(root, filename); |
| free(filename); |
| } |
| free(namelist[j]); |
| } |
| free(namelist); |
| if (err < 0) |
| goto _err; |
| } |
| } else if ((err = config_file_open(root, fi[idx].name)) < 0) |
| goto _err; |
| } |
| *dst = NULL; |
| err = 0; |
| _err: |
| if (fi) |
| for (idx = 0; idx < fi_count; idx++) |
| free(fi[idx].name); |
| free(fi); |
| 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) |
| configs = ALSA_CONFIG_PATH_DEFAULT; |
| } |
| 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. |
| * |
| * \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 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: |
| * <dl> |
| * <dt>-ENOMEM<dd>Out of memory. |
| * </dl> |
| * |
| * \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; |
| 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 = snd_dlopen(lib, RTLD_NOW); |
| 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", lib); |
| 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' ... '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) |
| 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); |
| memcpy(buf, _buf, old_alloc); |
| } else { |
| buf = realloc(buf, alloc); |
| } |
| if (!buf) |
| return -ENOMEM; |
| } |
| 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: |
| * <dl> |
| * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist. |
| * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is |
| * not a compound node. |
| * </dl> |
| * 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 |