mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2025-10-29 05:40:25 -04:00
Closes: https://github.com/alsa-project/alsa-lib/pull/467 Signed-off-by: wyjstrong <wyjstrong@163.com> Signed-off-by: Jaroslav Kysela <perex@perex.cz>
5880 lines
141 KiB
C
5880 lines
141 KiB
C
/**
|
|
* \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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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_math simple math expressions
|
|
|
|
The simple math expressions are identified using a unix shell like expression syntax
|
|
with a dollar-sign ($) and bracket ([):
|
|
|
|
\code
|
|
card "$[$CARD + 1]"
|
|
\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 "local.h"
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <limits.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
#include <locale.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;
|
|
int refcount; /* default = 0 */
|
|
union {
|
|
long integer;
|
|
long long integer64;
|
|
char *string;
|
|
double real;
|
|
const void *ptr;
|
|
struct {
|
|
struct list_head fields;
|
|
bool join;
|
|
} compound;
|
|
} u;
|
|
struct list_head list;
|
|
snd_config_t *parent;
|
|
int hop;
|
|
};
|
|
|
|
struct filedesc {
|
|
char *name;
|
|
snd_input_t *in;
|
|
unsigned int line, column;
|
|
struct filedesc *next;
|
|
|
|
/* list of the include paths (configuration directories),
|
|
* defined by <searchdir:relative-path/to/top-alsa-conf-dir>,
|
|
* for searching its included files.
|
|
*/
|
|
struct list_head include_paths;
|
|
};
|
|
|
|
/* path to search included files */
|
|
struct include_path {
|
|
char *dir;
|
|
struct list_head list;
|
|
};
|
|
|
|
#define LOCAL_ERROR (-0x68000000)
|
|
|
|
#define LOCAL_UNTERMINATED_STRING (LOCAL_ERROR - 0)
|
|
#define LOCAL_UNTERMINATED_QUOTE (LOCAL_ERROR - 1)
|
|
#define LOCAL_UNEXPECTED_CHAR (LOCAL_ERROR - 2)
|
|
#define LOCAL_UNEXPECTED_EOF (LOCAL_ERROR - 3)
|
|
|
|
typedef struct {
|
|
struct filedesc *current;
|
|
int unget;
|
|
int ch;
|
|
} input_t;
|
|
|
|
#ifdef HAVE_LIBPTHREAD
|
|
|
|
static void snd_config_init_mutex(void)
|
|
{
|
|
pthread_mutexattr_t attr;
|
|
|
|
pthread_mutexattr_init(&attr);
|
|
#ifdef HAVE_PTHREAD_MUTEX_RECURSIVE
|
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
|
#endif
|
|
pthread_mutex_init(&snd_config_update_mutex, &attr);
|
|
pthread_mutexattr_destroy(&attr);
|
|
}
|
|
|
|
static inline void snd_config_lock(void)
|
|
{
|
|
pthread_once(&snd_config_update_mutex_once, snd_config_init_mutex);
|
|
pthread_mutex_lock(&snd_config_update_mutex);
|
|
}
|
|
|
|
static inline void snd_config_unlock(void)
|
|
{
|
|
pthread_mutex_unlock(&snd_config_update_mutex);
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void snd_config_lock(void) { }
|
|
static inline void snd_config_unlock(void) { }
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Add a directory to the paths to search included files.
|
|
* param fd - File object that owns these paths to search files included by it.
|
|
* param dir - Path of the directory to add. Allocated externally and need to
|
|
* be freed manually later.
|
|
* return - Zero if successful, otherwise a negative error code.
|
|
*
|
|
* The direcotry should be a subdiretory of top configuration directory
|
|
* "/usr/share/alsa/".
|
|
*/
|
|
static int add_include_path(struct filedesc *fd, const char *dir)
|
|
{
|
|
struct include_path *path;
|
|
struct filedesc *fd1;
|
|
struct list_head *pos;
|
|
|
|
/* check, if dir is already registered (also in parents) */
|
|
for (fd1 = fd; fd1; fd1 = fd1->next) {
|
|
list_for_each(pos, &fd1->include_paths) {
|
|
path = list_entry(pos, struct include_path, list);
|
|
if (strcmp(path->dir, dir) == 0)
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
path = calloc(1, sizeof(*path));
|
|
if (!path)
|
|
return -ENOMEM;
|
|
|
|
path->dir = strdup(dir);
|
|
if (path->dir == NULL) {
|
|
free(path);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
list_add_tail(&path->list, &fd->include_paths);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Free all include paths of a file descriptor.
|
|
* param fd - File object that owns these paths to search files included by it.
|
|
*/
|
|
static void free_include_paths(struct filedesc *fd)
|
|
{
|
|
struct list_head *pos, *npos, *base;
|
|
struct include_path *path;
|
|
|
|
base = &fd->include_paths;
|
|
list_for_each_safe(pos, npos, base) {
|
|
path = list_entry(pos, struct include_path, list);
|
|
list_del(&path->list);
|
|
if (path->dir)
|
|
free(path->dir);
|
|
free(path);
|
|
}
|
|
}
|
|
|
|
#endif /* DOC_HIDDEN */
|
|
|
|
/**
|
|
* \brief Returns the default top-level config directory
|
|
* \return The top-level config directory path string
|
|
*
|
|
* This function returns the string of the top-level config directory path.
|
|
* If the path is specified via the environment variable \c ALSA_CONFIG_DIR
|
|
* and the value is a valid path, it returns this value. If unspecified, it
|
|
* returns the default value, "/usr/share/alsa".
|
|
*/
|
|
const char *snd_config_topdir(void)
|
|
{
|
|
static char *topdir;
|
|
|
|
if (!topdir) {
|
|
topdir = getenv("ALSA_CONFIG_DIR");
|
|
if (!topdir || *topdir != '/' || strlen(topdir) >= PATH_MAX)
|
|
topdir = ALSA_CONFIG_DIR;
|
|
}
|
|
return topdir;
|
|
}
|
|
|
|
#ifndef DOC_HIDDEN
|
|
|
|
static char *_snd_config_path(const char *name)
|
|
{
|
|
const char *root = snd_config_topdir();
|
|
char *path = malloc(strlen(root) + strlen(name) + 2);
|
|
if (!path)
|
|
return NULL;
|
|
sprintf(path, "%s/%s", root, name);
|
|
return path;
|
|
}
|
|
|
|
/*
|
|
* Search and open a file, and creates a new input object reading from the file.
|
|
* param inputp - The functions puts the pointer to the new input object
|
|
* at the address specified by \p inputp.
|
|
* param file - Name of the configuration file.
|
|
* param include_paths - Optional, addtional directories to search the file.
|
|
* return - Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This function will search and open the file in the following order
|
|
* of priority:
|
|
* 1. directly open the file by its name (only if absolute)
|
|
* 2. search for the file name in in additional configuration directories
|
|
* specified by users, via alsaconf syntax
|
|
* <searchdir:relative-path/to/user/share/alsa>;
|
|
* These directories should be subdirectories of /usr/share/alsa.
|
|
*/
|
|
static int input_stdio_open(snd_input_t **inputp, const char *file,
|
|
struct filedesc *current)
|
|
{
|
|
struct list_head *pos;
|
|
struct include_path *path;
|
|
char full_path[PATH_MAX];
|
|
int err;
|
|
|
|
if (file[0] == '/')
|
|
return snd_input_stdio_open(inputp, file, "r");
|
|
|
|
/* search file in user specified include paths. These directories
|
|
* are subdirectories of /usr/share/alsa.
|
|
*/
|
|
err = -ENOENT;
|
|
while (current) {
|
|
list_for_each(pos, ¤t->include_paths) {
|
|
path = list_entry(pos, struct include_path, list);
|
|
if (!path->dir)
|
|
continue;
|
|
|
|
snprintf(full_path, PATH_MAX, "%s/%s", path->dir, file);
|
|
err = snd_input_stdio_open(inputp, full_path, "r");
|
|
if (err == 0)
|
|
return 0;
|
|
}
|
|
current = current->next;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int _snd_safe_strtoll_base(const char *str, long long *val, int base)
|
|
{
|
|
char *end;
|
|
long v;
|
|
if (!*str)
|
|
return -EINVAL;
|
|
errno = 0;
|
|
v = strtoll(str, &end, base);
|
|
if (errno)
|
|
return -errno;
|
|
if (*end)
|
|
return -EINVAL;
|
|
*val = v;
|
|
return 0;
|
|
}
|
|
|
|
int _snd_safe_strtol_base(const char *str, long *val, int base)
|
|
{
|
|
char *end;
|
|
long v;
|
|
if (!*str)
|
|
return -EINVAL;
|
|
errno = 0;
|
|
v = strtol(str, &end, base);
|
|
if (errno)
|
|
return -errno;
|
|
if (*end)
|
|
return -EINVAL;
|
|
*val = v;
|
|
return 0;
|
|
}
|
|
|
|
int _snd_safe_strtod(const char *str, double *val)
|
|
{
|
|
char *end;
|
|
double v;
|
|
#ifdef HAVE_USELOCALE
|
|
locale_t saved_locale, c_locale;
|
|
#else
|
|
char *saved_locale;
|
|
char locstr[64]; /* enough? */
|
|
#endif
|
|
int err;
|
|
|
|
if (!*str)
|
|
return -EINVAL;
|
|
#ifdef HAVE_USELOCALE
|
|
c_locale = newlocale(LC_NUMERIC_MASK, "C", 0);
|
|
saved_locale = uselocale(c_locale);
|
|
#else
|
|
saved_locale = setlocale(LC_NUMERIC, NULL);
|
|
if (saved_locale) {
|
|
snprintf(locstr, sizeof(locstr), "%s", saved_locale);
|
|
setlocale(LC_NUMERIC, "C");
|
|
}
|
|
#endif
|
|
errno = 0;
|
|
v = strtod(str, &end);
|
|
err = -errno;
|
|
#ifdef HAVE_USELOCALE
|
|
if (c_locale != (locale_t)0) {
|
|
uselocale(saved_locale);
|
|
freelocale(c_locale);
|
|
}
|
|
#else
|
|
if (saved_locale)
|
|
setlocale(LC_NUMERIC, locstr);
|
|
#endif
|
|
if (err)
|
|
return err;
|
|
if (*end)
|
|
return -EINVAL;
|
|
*val = v;
|
|
return 0;
|
|
}
|
|
|
|
static int get_char(input_t *input)
|
|
{
|
|
int c;
|
|
struct filedesc *fd;
|
|
if (input->unget) {
|
|
input->unget = 0;
|
|
return input->ch;
|
|
}
|
|
again:
|
|
fd = input->current;
|
|
c = snd_input_getc(fd->in);
|
|
switch (c) {
|
|
case '\n':
|
|
fd->column = 0;
|
|
fd->line++;
|
|
break;
|
|
case '\t':
|
|
fd->column += 8 - fd->column % 8;
|
|
break;
|
|
case EOF:
|
|
if (fd->next) {
|
|
snd_input_close(fd->in);
|
|
free(fd->name);
|
|
input->current = fd->next;
|
|
free(fd);
|
|
goto again;
|
|
}
|
|
return LOCAL_UNEXPECTED_EOF;
|
|
default:
|
|
fd->column++;
|
|
break;
|
|
}
|
|
return (unsigned char)c;
|
|
}
|
|
|
|
static void unget_char(int c, input_t *input)
|
|
{
|
|
assert(!input->unget);
|
|
input->ch = c;
|
|
input->unget = 1;
|
|
}
|
|
|
|
static int get_delimstring(char **string, int delim, input_t *input);
|
|
|
|
static int get_char_skip_comments(input_t *input)
|
|
{
|
|
int c;
|
|
while (1) {
|
|
c = get_char(input);
|
|
if (c == '<') {
|
|
char *str;
|
|
snd_input_t *in;
|
|
struct filedesc *fd;
|
|
DIR *dirp;
|
|
int err = get_delimstring(&str, '>', input);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (!strncmp(str, "searchdir:", 10)) {
|
|
/* directory to search included files */
|
|
char *tmp = _snd_config_path(str + 10);
|
|
free(str);
|
|
if (tmp == NULL)
|
|
return -ENOMEM;
|
|
str = tmp;
|
|
|
|
dirp = opendir(str);
|
|
if (!dirp) {
|
|
SNDERR("Invalid search dir %s", str);
|
|
free(str);
|
|
return -EINVAL;
|
|
}
|
|
closedir(dirp);
|
|
|
|
err = add_include_path(input->current, str);
|
|
if (err < 0) {
|
|
SNDERR("Cannot add search dir %s", str);
|
|
free(str);
|
|
return err;
|
|
}
|
|
free(str);
|
|
continue;
|
|
}
|
|
|
|
if (!strncmp(str, "confdir:", 8)) {
|
|
/* file in the specified directory */
|
|
char *tmp = _snd_config_path(str + 8);
|
|
free(str);
|
|
if (tmp == NULL)
|
|
return -ENOMEM;
|
|
str = tmp;
|
|
err = snd_input_stdio_open(&in, str, "r");
|
|
} else { /* absolute or relative file path */
|
|
err = input_stdio_open(&in, str, input->current);
|
|
}
|
|
|
|
if (err < 0) {
|
|
SNDERR("Cannot access file %s", str);
|
|
free(str);
|
|
return err;
|
|
}
|
|
fd = malloc(sizeof(*fd));
|
|
if (!fd) {
|
|
free(str);
|
|
return -ENOMEM;
|
|
}
|
|
fd->name = str;
|
|
fd->in = in;
|
|
fd->next = input->current;
|
|
fd->line = 1;
|
|
fd->column = 0;
|
|
INIT_LIST_HEAD(&fd->include_paths);
|
|
input->current = fd;
|
|
continue;
|
|
}
|
|
if (c != '#')
|
|
break;
|
|
while (1) {
|
|
c = get_char(input);
|
|
if (c < 0)
|
|
return c;
|
|
if (c == '\n')
|
|
break;
|
|
}
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
static int get_nonwhite(input_t *input)
|
|
{
|
|
int c;
|
|
while (1) {
|
|
c = get_char_skip_comments(input);
|
|
switch (c) {
|
|
case ' ':
|
|
case '\f':
|
|
case '\t':
|
|
case '\n':
|
|
case '\r':
|
|
break;
|
|
default:
|
|
return c;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline int get_hexachar(input_t *input)
|
|
{
|
|
int c, num = 0;
|
|
|
|
c = get_char(input);
|
|
if (c >= '0' && c <= '9') num |= (c - '0') << 4;
|
|
else if (c >= 'a' && c <= 'f') num |= (c - 'a') << 4;
|
|
else if (c >= 'A' && c <= 'F') num |= (c - 'A') << 4;
|
|
c = get_char(input);
|
|
if (c >= '0' && c <= '9') num |= (c - '0') << 0;
|
|
else if (c >= 'a' && c <= 'f') num |= (c - 'a') << 0;
|
|
else if (c >= 'A' && c <= 'F') num |= (c - 'A') << 0;
|
|
return num;
|
|
}
|
|
|
|
static int get_quotedchar(input_t *input)
|
|
{
|
|
int c;
|
|
c = get_char(input);
|
|
switch (c) {
|
|
case 'n':
|
|
return '\n';
|
|
case 't':
|
|
return '\t';
|
|
case 'v':
|
|
return '\v';
|
|
case 'b':
|
|
return '\b';
|
|
case 'r':
|
|
return '\r';
|
|
case 'f':
|
|
return '\f';
|
|
case 'x':
|
|
return get_hexachar(input);
|
|
case '0': case '1': case '2': case '3':
|
|
case '4': case '5': case '6': case '7':
|
|
{
|
|
int num = c - '0';
|
|
int i = 1;
|
|
do {
|
|
c = get_char(input);
|
|
if (c < '0' || c > '7') {
|
|
unget_char(c, input);
|
|
break;
|
|
}
|
|
num = num * 8 + c - '0';
|
|
i++;
|
|
} while (i < 3);
|
|
return num;
|
|
}
|
|
default:
|
|
return c;
|
|
}
|
|
}
|
|
|
|
#define LOCAL_STR_BUFSIZE 64
|
|
struct local_string {
|
|
char *buf;
|
|
size_t alloc;
|
|
size_t idx;
|
|
char tmpbuf[LOCAL_STR_BUFSIZE];
|
|
};
|
|
|
|
static void init_local_string(struct local_string *s)
|
|
{
|
|
memset(s, 0, sizeof(*s));
|
|
s->buf = s->tmpbuf;
|
|
s->alloc = LOCAL_STR_BUFSIZE;
|
|
}
|
|
|
|
static void free_local_string(struct local_string *s)
|
|
{
|
|
if (s->buf != s->tmpbuf)
|
|
free(s->buf);
|
|
}
|
|
|
|
static int add_char_local_string(struct local_string *s, int c)
|
|
{
|
|
if (s->idx >= s->alloc) {
|
|
size_t nalloc = s->alloc * 2;
|
|
if (s->buf == s->tmpbuf) {
|
|
s->buf = malloc(nalloc);
|
|
if (s->buf == NULL)
|
|
return -ENOMEM;
|
|
memcpy(s->buf, s->tmpbuf, s->alloc);
|
|
} else {
|
|
char *ptr = realloc(s->buf, nalloc);
|
|
if (ptr == NULL)
|
|
return -ENOMEM;
|
|
s->buf = ptr;
|
|
}
|
|
s->alloc = nalloc;
|
|
}
|
|
s->buf[s->idx++] = c;
|
|
return 0;
|
|
}
|
|
|
|
static char *copy_local_string(struct local_string *s)
|
|
{
|
|
char *dst = malloc(s->idx + 1);
|
|
if (dst) {
|
|
memcpy(dst, s->buf, s->idx);
|
|
dst[s->idx] = '\0';
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
static int get_freestring(char **string, int id, input_t *input)
|
|
{
|
|
struct local_string str;
|
|
int c;
|
|
|
|
init_local_string(&str);
|
|
while (1) {
|
|
c = get_char(input);
|
|
if (c < 0) {
|
|
if (c == LOCAL_UNEXPECTED_EOF) {
|
|
*string = copy_local_string(&str);
|
|
if (! *string)
|
|
c = -ENOMEM;
|
|
else
|
|
c = 0;
|
|
}
|
|
break;
|
|
}
|
|
switch (c) {
|
|
case '.':
|
|
if (!id)
|
|
break;
|
|
/* fall through */
|
|
case ' ':
|
|
case '\f':
|
|
case '\t':
|
|
case '\n':
|
|
case '\r':
|
|
case '=':
|
|
case ',':
|
|
case ';':
|
|
case '{':
|
|
case '}':
|
|
case '[':
|
|
case ']':
|
|
case '\'':
|
|
case '"':
|
|
case '\\':
|
|
case '#':
|
|
*string = copy_local_string(&str);
|
|
if (! *string)
|
|
c = -ENOMEM;
|
|
else {
|
|
unget_char(c, input);
|
|
c = 0;
|
|
}
|
|
goto _out;
|
|
default:
|
|
break;
|
|
}
|
|
if (add_char_local_string(&str, c) < 0) {
|
|
c = -ENOMEM;
|
|
break;
|
|
}
|
|
}
|
|
_out:
|
|
free_local_string(&str);
|
|
return c;
|
|
}
|
|
|
|
static int get_delimstring(char **string, int delim, input_t *input)
|
|
{
|
|
struct local_string str;
|
|
int c;
|
|
|
|
init_local_string(&str);
|
|
while (1) {
|
|
c = get_char(input);
|
|
if (c < 0)
|
|
break;
|
|
if (c == '\\') {
|
|
c = get_quotedchar(input);
|
|
if (c < 0)
|
|
break;
|
|
if (c == '\n')
|
|
continue;
|
|
} else if (c == delim) {
|
|
*string = copy_local_string(&str);
|
|
if (! *string)
|
|
c = -ENOMEM;
|
|
else
|
|
c = 0;
|
|
break;
|
|
}
|
|
if (add_char_local_string(&str, c) < 0) {
|
|
c = -ENOMEM;
|
|
break;
|
|
}
|
|
}
|
|
free_local_string(&str);
|
|
return c;
|
|
}
|
|
|
|
/* Return 0 for free string, 1 for delimited string */
|
|
static int get_string(char **string, int id, input_t *input)
|
|
{
|
|
int c = get_nonwhite(input), err;
|
|
if (c < 0)
|
|
return c;
|
|
switch (c) {
|
|
case '=':
|
|
case ',':
|
|
case ';':
|
|
case '.':
|
|
case '{':
|
|
case '}':
|
|
case '[':
|
|
case ']':
|
|
case '\\':
|
|
return LOCAL_UNEXPECTED_CHAR;
|
|
case '\'':
|
|
case '"':
|
|
err = get_delimstring(string, c, input);
|
|
if (err < 0)
|
|
return err;
|
|
return 1;
|
|
default:
|
|
unget_char(c, input);
|
|
err = get_freestring(string, id, input);
|
|
if (err < 0)
|
|
return err;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int _snd_config_make(snd_config_t **config, char **id, snd_config_type_t type)
|
|
{
|
|
snd_config_t *n;
|
|
assert(config);
|
|
n = calloc(1, sizeof(*n));
|
|
if (n == NULL) {
|
|
if (*id) {
|
|
free(*id);
|
|
*id = NULL;
|
|
}
|
|
return -ENOMEM;
|
|
}
|
|
if (id) {
|
|
n->id = *id;
|
|
*id = NULL;
|
|
}
|
|
n->type = type;
|
|
if (type == SND_CONFIG_TYPE_COMPOUND)
|
|
INIT_LIST_HEAD(&n->u.compound.fields);
|
|
*config = n;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int _snd_config_make_add(snd_config_t **config, char **id,
|
|
snd_config_type_t type, snd_config_t *parent)
|
|
{
|
|
snd_config_t *n;
|
|
int err;
|
|
assert(parent->type == SND_CONFIG_TYPE_COMPOUND);
|
|
err = _snd_config_make(&n, id, type);
|
|
if (err < 0)
|
|
return err;
|
|
n->parent = parent;
|
|
list_add_tail(&n->list, &parent->u.compound.fields);
|
|
*config = n;
|
|
return 0;
|
|
}
|
|
|
|
static int _snd_config_search(snd_config_t *config,
|
|
const char *id, int len, snd_config_t **result)
|
|
{
|
|
snd_config_iterator_t i, next;
|
|
snd_config_for_each(i, next, config) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
if (len < 0) {
|
|
if (strcmp(n->id, id) != 0)
|
|
continue;
|
|
} else if (strlen(n->id) != (size_t) len ||
|
|
memcmp(n->id, id, (size_t) len) != 0)
|
|
continue;
|
|
if (result)
|
|
*result = n;
|
|
return 0;
|
|
}
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int parse_value(snd_config_t **_n, snd_config_t *parent, input_t *input, char **id, int skip)
|
|
{
|
|
snd_config_t *n = *_n;
|
|
char *s;
|
|
int err;
|
|
|
|
err = get_string(&s, 0, input);
|
|
if (err < 0)
|
|
return err;
|
|
if (skip) {
|
|
free(s);
|
|
return 0;
|
|
}
|
|
if (err == 0 && ((s[0] >= '0' && s[0] <= '9') || s[0] == '-')) {
|
|
long long i;
|
|
errno = 0;
|
|
err = safe_strtoll(s, &i);
|
|
if (err < 0) {
|
|
double r;
|
|
err = safe_strtod(s, &r);
|
|
if (err >= 0) {
|
|
free(s);
|
|
if (n) {
|
|
if (n->type != SND_CONFIG_TYPE_REAL) {
|
|
SNDERR("%s is not a real", *id);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_REAL, parent);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
n->u.real = r;
|
|
*_n = n;
|
|
return 0;
|
|
}
|
|
} else {
|
|
free(s);
|
|
if (n) {
|
|
if (n->type != SND_CONFIG_TYPE_INTEGER && n->type != SND_CONFIG_TYPE_INTEGER64) {
|
|
SNDERR("%s is not an integer", *id);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
if (i <= INT_MAX)
|
|
err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_INTEGER, parent);
|
|
else
|
|
err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_INTEGER64, parent);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
if (n->type == SND_CONFIG_TYPE_INTEGER)
|
|
n->u.integer = (long) i;
|
|
else
|
|
n->u.integer64 = i;
|
|
*_n = n;
|
|
return 0;
|
|
}
|
|
}
|
|
if (n) {
|
|
if (n->type != SND_CONFIG_TYPE_STRING) {
|
|
SNDERR("%s is not a string", *id);
|
|
free(s);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_STRING, parent);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
free(n->u.string);
|
|
n->u.string = s;
|
|
*_n = n;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_defs(snd_config_t *parent, input_t *input, int skip, int override);
|
|
static int parse_array_defs(snd_config_t *farther, input_t *input, int skip, int override);
|
|
|
|
static int parse_array_def(snd_config_t *parent, input_t *input, int *idx, int skip, int override)
|
|
{
|
|
char *id = NULL;
|
|
int c;
|
|
int err;
|
|
snd_config_t *n = NULL;
|
|
|
|
if (!skip) {
|
|
snd_config_t *g;
|
|
char static_id[12];
|
|
while (1) {
|
|
snprintf(static_id, sizeof(static_id), "%i", *idx);
|
|
if (_snd_config_search(parent, static_id, -1, &g) == 0) {
|
|
if (override) {
|
|
snd_config_delete(n);
|
|
} else {
|
|
/* merge */
|
|
(*idx)++;
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
id = strdup(static_id);
|
|
if (id == NULL)
|
|
return -ENOMEM;
|
|
}
|
|
c = get_nonwhite(input);
|
|
if (c < 0) {
|
|
err = c;
|
|
goto __end;
|
|
}
|
|
switch (c) {
|
|
case '{':
|
|
case '[':
|
|
{
|
|
char endchr;
|
|
if (!skip) {
|
|
if (n) {
|
|
if (n->type != SND_CONFIG_TYPE_COMPOUND) {
|
|
SNDERR("%s is not a compound", id);
|
|
err = -EINVAL;
|
|
goto __end;
|
|
}
|
|
} else {
|
|
err = _snd_config_make_add(&n, &id, SND_CONFIG_TYPE_COMPOUND, parent);
|
|
if (err < 0)
|
|
goto __end;
|
|
}
|
|
}
|
|
if (c == '{') {
|
|
err = parse_defs(n, input, skip, override);
|
|
endchr = '}';
|
|
} else {
|
|
err = parse_array_defs(n, input, skip, override);
|
|
endchr = ']';
|
|
}
|
|
c = get_nonwhite(input);
|
|
if (c < 0) {
|
|
err = c;
|
|
goto __end;
|
|
}
|
|
if (c != endchr) {
|
|
if (n)
|
|
snd_config_delete(n);
|
|
err = LOCAL_UNEXPECTED_CHAR;
|
|
goto __end;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
unget_char(c, input);
|
|
err = parse_value(&n, parent, input, &id, skip);
|
|
if (err < 0)
|
|
goto __end;
|
|
break;
|
|
}
|
|
err = 0;
|
|
__end:
|
|
free(id);
|
|
return err;
|
|
}
|
|
|
|
static int parse_array_defs(snd_config_t *parent, input_t *input, int skip, int override)
|
|
{
|
|
int idx = 0;
|
|
while (1) {
|
|
int c = get_nonwhite(input), err;
|
|
if (c < 0)
|
|
return c;
|
|
unget_char(c, input);
|
|
if (c == ']')
|
|
return 0;
|
|
err = parse_array_def(parent, input, &idx, skip, override);
|
|
if (err < 0)
|
|
return err;
|
|
idx++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int parse_def(snd_config_t *parent, input_t *input, int skip, int override)
|
|
{
|
|
char *id = NULL;
|
|
int c;
|
|
int err;
|
|
snd_config_t *n;
|
|
enum {MERGE_CREATE, MERGE, OVERRIDE, DONT_OVERRIDE} mode;
|
|
while (1) {
|
|
c = get_nonwhite(input);
|
|
if (c < 0)
|
|
return c;
|
|
switch (c) {
|
|
case '+':
|
|
mode = MERGE_CREATE;
|
|
break;
|
|
case '-':
|
|
mode = MERGE;
|
|
break;
|
|
case '?':
|
|
mode = DONT_OVERRIDE;
|
|
break;
|
|
case '!':
|
|
mode = OVERRIDE;
|
|
break;
|
|
default:
|
|
mode = !override ? MERGE_CREATE : OVERRIDE;
|
|
unget_char(c, input);
|
|
}
|
|
err = get_string(&id, 1, input);
|
|
if (err < 0)
|
|
return err;
|
|
c = get_nonwhite(input);
|
|
if (c != '.')
|
|
break;
|
|
if (skip) {
|
|
free(id);
|
|
continue;
|
|
}
|
|
if (_snd_config_search(parent, id, -1, &n) == 0) {
|
|
if (mode == DONT_OVERRIDE) {
|
|
skip = 1;
|
|
free(id);
|
|
continue;
|
|
}
|
|
if (mode != OVERRIDE) {
|
|
if (n->type != SND_CONFIG_TYPE_COMPOUND) {
|
|
SNDERR("%s is not a compound", id);
|
|
return -EINVAL;
|
|
}
|
|
n->u.compound.join = true;
|
|
parent = n;
|
|
free(id);
|
|
continue;
|
|
}
|
|
snd_config_delete(n);
|
|
}
|
|
if (mode == MERGE) {
|
|
SNDERR("%s does not exists", id);
|
|
err = -ENOENT;
|
|
goto __end;
|
|
}
|
|
err = _snd_config_make_add(&n, &id, SND_CONFIG_TYPE_COMPOUND, parent);
|
|
if (err < 0)
|
|
goto __end;
|
|
n->u.compound.join = true;
|
|
parent = n;
|
|
}
|
|
if (c == '=') {
|
|
c = get_nonwhite(input);
|
|
if (c < 0)
|
|
return c;
|
|
}
|
|
if (!skip) {
|
|
if (_snd_config_search(parent, id, -1, &n) == 0) {
|
|
if (mode == DONT_OVERRIDE) {
|
|
skip = 1;
|
|
n = NULL;
|
|
} else if (mode == OVERRIDE) {
|
|
snd_config_delete(n);
|
|
n = NULL;
|
|
}
|
|
} else {
|
|
n = NULL;
|
|
if (mode == MERGE) {
|
|
SNDERR("%s does not exists", id);
|
|
err = -ENOENT;
|
|
goto __end;
|
|
}
|
|
}
|
|
}
|
|
switch (c) {
|
|
case '{':
|
|
case '[':
|
|
{
|
|
char endchr;
|
|
if (!skip) {
|
|
if (n) {
|
|
if (n->type != SND_CONFIG_TYPE_COMPOUND) {
|
|
SNDERR("%s is not a compound", id);
|
|
err = -EINVAL;
|
|
goto __end;
|
|
}
|
|
} else {
|
|
err = _snd_config_make_add(&n, &id, SND_CONFIG_TYPE_COMPOUND, parent);
|
|
if (err < 0)
|
|
goto __end;
|
|
}
|
|
}
|
|
if (c == '{') {
|
|
err = parse_defs(n, input, skip, override);
|
|
endchr = '}';
|
|
} else {
|
|
err = parse_array_defs(n, input, skip, override);
|
|
endchr = ']';
|
|
}
|
|
c = get_nonwhite(input);
|
|
if (c != endchr) {
|
|
if (n)
|
|
snd_config_delete(n);
|
|
err = LOCAL_UNEXPECTED_CHAR;
|
|
goto __end;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
unget_char(c, input);
|
|
err = parse_value(&n, parent, input, &id, skip);
|
|
if (err < 0)
|
|
goto __end;
|
|
break;
|
|
}
|
|
c = get_nonwhite(input);
|
|
switch (c) {
|
|
case ';':
|
|
case ',':
|
|
break;
|
|
default:
|
|
unget_char(c, input);
|
|
}
|
|
__end:
|
|
free(id);
|
|
return err;
|
|
}
|
|
|
|
static int parse_defs(snd_config_t *parent, input_t *input, int skip, int override)
|
|
{
|
|
int c, err;
|
|
while (1) {
|
|
c = get_nonwhite(input);
|
|
if (c < 0)
|
|
return c == LOCAL_UNEXPECTED_EOF ? 0 : c;
|
|
unget_char(c, input);
|
|
if (c == '}')
|
|
return 0;
|
|
err = parse_def(parent, input, skip, override);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void string_print(char *str, int id, snd_output_t *out)
|
|
{
|
|
int q;
|
|
unsigned char *p = (unsigned char *)str;
|
|
if (!p || !*p) {
|
|
snd_output_puts(out, "''");
|
|
return;
|
|
}
|
|
if (!id) {
|
|
switch (*p) {
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
case '-':
|
|
goto quoted;
|
|
}
|
|
}
|
|
loop:
|
|
switch (*p) {
|
|
case 0:
|
|
goto nonquoted;
|
|
case ' ':
|
|
case '=':
|
|
case ';':
|
|
case ',':
|
|
case '.':
|
|
case '{':
|
|
case '}':
|
|
case '[':
|
|
case ']':
|
|
case '\'':
|
|
case '"':
|
|
case '*':
|
|
case '#':
|
|
goto quoted;
|
|
default:
|
|
if (*p <= 31 || *p >= 127)
|
|
goto quoted;
|
|
p++;
|
|
goto loop;
|
|
}
|
|
nonquoted:
|
|
snd_output_puts(out, str);
|
|
return;
|
|
quoted:
|
|
q = strchr(str, '\'') ? '"' : '\'';
|
|
snd_output_putc(out, q);
|
|
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;
|
|
default:
|
|
if (c == q) {
|
|
snd_output_putc(out, '\\');
|
|
snd_output_putc(out, c);
|
|
} else {
|
|
if (c >= 32 && c <= 126)
|
|
snd_output_putc(out, c);
|
|
else
|
|
snd_output_printf(out, "\\%04o", c);
|
|
}
|
|
break;
|
|
}
|
|
p++;
|
|
}
|
|
snd_output_putc(out, q);
|
|
}
|
|
|
|
static void level_print(snd_output_t *out, unsigned int level)
|
|
{
|
|
char a[level + 1];
|
|
memset(a, '\t', level);
|
|
a[level] = '\0';
|
|
snd_output_puts(out, a);
|
|
}
|
|
|
|
static int _snd_config_save_children(snd_config_t *config, snd_output_t *out,
|
|
unsigned int level, unsigned int joins,
|
|
int array);
|
|
|
|
int _snd_config_save_node_value(snd_config_t *n, snd_output_t *out,
|
|
unsigned int level)
|
|
{
|
|
int err, array;
|
|
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:
|
|
array = snd_config_is_array(n);
|
|
snd_output_putc(out, array ? '[' : '{');
|
|
snd_output_putc(out, '\n');
|
|
err = _snd_config_save_children(n, out, level + 1, 0, array);
|
|
if (err < 0)
|
|
return err;
|
|
level_print(out, level);
|
|
snd_output_putc(out, array ? ']' : '}');
|
|
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,
|
|
int array)
|
|
{
|
|
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, 0);
|
|
if (err < 0)
|
|
return err;
|
|
continue;
|
|
}
|
|
level_print(out, level);
|
|
if (!array) {
|
|
id_print(n, out, joins);
|
|
snd_output_putc(out, ' ');
|
|
#if 0
|
|
snd_output_putc(out, '=');
|
|
#endif
|
|
}
|
|
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 /* DOC_HIDDEN */
|
|
|
|
|
|
/**
|
|
* \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 will
|
|
* be moved to the destination compound node. The original destination
|
|
* compound node members will be deleted (overwritten).
|
|
*
|
|
* 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 && src != dst);
|
|
if (dst->type == SND_CONFIG_TYPE_COMPOUND) {
|
|
int err = snd_config_delete_compound_members(dst);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
if (dst->type == SND_CONFIG_TYPE_COMPOUND &&
|
|
src->type == SND_CONFIG_TYPE_COMPOUND) { /* overwrite */
|
|
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;
|
|
}
|
|
free(dst->id);
|
|
if (dst->type == SND_CONFIG_TYPE_STRING)
|
|
free(dst->u.string);
|
|
if (src->parent) /* like snd_config_remove */
|
|
list_del(&src->list);
|
|
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;
|
|
}
|
|
|
|
static int check_array_item(const char *id, int index)
|
|
{
|
|
const char *p;
|
|
long val;
|
|
|
|
for (p = id; *p; p++) {
|
|
if (*p < '0' || *p > '9')
|
|
return 0;
|
|
}
|
|
|
|
if (safe_strtol(id, &val))
|
|
return 0;
|
|
return val == index;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns if the compound is an array (and count of items).
|
|
* \param config Handle to the configuration node.
|
|
* \return A count of items in array, zero when the compound is not an array,
|
|
* otherwise a negative error code.
|
|
*/
|
|
int snd_config_is_array(const snd_config_t *config)
|
|
{
|
|
int idx;
|
|
snd_config_iterator_t i, next;
|
|
snd_config_t *node;
|
|
|
|
assert(config);
|
|
if (config->type != SND_CONFIG_TYPE_COMPOUND)
|
|
return -EINVAL;
|
|
idx = 0;
|
|
snd_config_for_each(i, next, config) {
|
|
node = snd_config_iterator_entry(i);
|
|
if (!check_array_item(node->id, idx))
|
|
return 0;
|
|
idx++;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns if the compound has no fields (is empty).
|
|
* \param config Handle to the configuration node.
|
|
* \return A positive value when true, zero when false, otherwise a negative error code.
|
|
*/
|
|
int snd_config_is_empty(const snd_config_t *config)
|
|
{
|
|
assert(config);
|
|
if (config->type != SND_CONFIG_TYPE_COMPOUND)
|
|
return -EINVAL;
|
|
return list_empty(&config->u.compound.fields);
|
|
}
|
|
|
|
/**
|
|
* \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);
|
|
}
|
|
|
|
#ifndef DOC_HIDDEN
|
|
int _snd_config_load_with_include(snd_config_t *config, snd_input_t *in,
|
|
int override, const char * const *include_paths)
|
|
{
|
|
int err;
|
|
input_t input;
|
|
struct filedesc *fd, *fd_next;
|
|
|
|
assert(config && in);
|
|
fd = malloc(sizeof(*fd));
|
|
if (!fd)
|
|
return -ENOMEM;
|
|
fd->name = NULL;
|
|
fd->in = in;
|
|
fd->line = 1;
|
|
fd->column = 0;
|
|
fd->next = NULL;
|
|
INIT_LIST_HEAD(&fd->include_paths);
|
|
if (include_paths) {
|
|
for (; *include_paths; include_paths++) {
|
|
err = add_include_path(fd, *include_paths);
|
|
if (err < 0)
|
|
goto _end;
|
|
}
|
|
} else {
|
|
err = add_include_path(fd, snd_config_topdir());
|
|
if (err < 0)
|
|
goto _end;
|
|
}
|
|
input.current = fd;
|
|
input.unget = 0;
|
|
err = parse_defs(config, &input, 0, override);
|
|
fd = input.current;
|
|
if (err < 0) {
|
|
const char *str;
|
|
switch (err) {
|
|
case LOCAL_UNTERMINATED_STRING:
|
|
str = "Unterminated string";
|
|
err = -EINVAL;
|
|
break;
|
|
case LOCAL_UNTERMINATED_QUOTE:
|
|
str = "Unterminated quote";
|
|
err = -EINVAL;
|
|
break;
|
|
case LOCAL_UNEXPECTED_CHAR:
|
|
str = "Unexpected char";
|
|
err = -EINVAL;
|
|
break;
|
|
case LOCAL_UNEXPECTED_EOF:
|
|
str = "Unexpected end of file";
|
|
err = -EINVAL;
|
|
break;
|
|
default:
|
|
str = strerror(-err);
|
|
break;
|
|
}
|
|
SNDERR("%s:%d:%d:%s", fd->name ? fd->name : "_toplevel_", fd->line, fd->column, str);
|
|
goto _end;
|
|
}
|
|
err = get_char(&input);
|
|
fd = input.current;
|
|
if (err != LOCAL_UNEXPECTED_EOF) {
|
|
SNDERR("%s:%d:%d:Unexpected }", fd->name ? fd->name : "", fd->line, fd->column);
|
|
err = -EINVAL;
|
|
goto _end;
|
|
}
|
|
err = 0;
|
|
_end:
|
|
while (fd->next) {
|
|
fd_next = fd->next;
|
|
snd_input_close(fd->in);
|
|
free(fd->name);
|
|
free_include_paths(fd);
|
|
free(fd);
|
|
fd = fd_next;
|
|
}
|
|
|
|
free_include_paths(fd);
|
|
free(fd);
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* \brief Loads a configuration tree.
|
|
* \param config Handle to a top level configuration node.
|
|
* \param in Input handle to read the configuration from.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* The definitions loaded from the input are added to \a config, which
|
|
* must be a compound node.
|
|
*
|
|
* \par Errors:
|
|
* Any errors encountered when parsing the input or returned by hooks or
|
|
* functions.
|
|
*
|
|
* \par Conforming to:
|
|
* LSB 3.2
|
|
*/
|
|
int snd_config_load(snd_config_t *config, snd_input_t *in)
|
|
{
|
|
return _snd_config_load_with_include(config, in, 0, NULL);
|
|
}
|
|
|
|
/**
|
|
* \brief Loads a configuration tree from a string.
|
|
* \param[out] config The function puts the handle to the configuration
|
|
* node loaded from the file(s) at the address specified
|
|
* by \a config.
|
|
* \param[in] s String with the ASCII configuration
|
|
* \param[in] size String size, if zero, a C string is expected (with termination)
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* The definitions loaded from the string are put to \a config, which
|
|
* is created as a new top node.
|
|
*
|
|
* \par Errors:
|
|
* Any errors encountered when parsing the input or returned by hooks or
|
|
* functions.
|
|
*/
|
|
int snd_config_load_string(snd_config_t **config, const char *s, size_t size)
|
|
{
|
|
snd_input_t *input;
|
|
snd_config_t *dst;
|
|
int err;
|
|
|
|
assert(config && s);
|
|
if (size == 0)
|
|
size = strlen(s);
|
|
err = snd_input_buffer_open(&input, s, size);
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_config_top(&dst);
|
|
if (err < 0) {
|
|
snd_input_close(input);
|
|
return err;
|
|
}
|
|
err = snd_config_load(dst, input);
|
|
snd_input_close(input);
|
|
if (err < 0) {
|
|
snd_config_delete(dst);
|
|
return err;
|
|
}
|
|
*config = dst;
|
|
return 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_load_with_include(config, in, 1, NULL);
|
|
}
|
|
|
|
/**
|
|
* \brief Adds a child to a compound configuration node.
|
|
* \param parent Handle to a compound configuration node.
|
|
* \param child Handle to the configuration node to be added.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This function makes the node \a child a child of the node \a parent.
|
|
*
|
|
* The parent node then owns the child node, i.e., the child node gets
|
|
* deleted together with its parent.
|
|
*
|
|
* \a child must have an id.
|
|
*
|
|
* \par Errors:
|
|
* <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 Adds a child after another child configuration node.
|
|
* \param after Handle to the start configuration node.
|
|
* \param child Handle to the configuration node to be added.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This function makes the node \a child a child of the parent of
|
|
* the node \a after.
|
|
*
|
|
* The parent node then owns the child node, i.e., the child node gets
|
|
* deleted together with its parent.
|
|
*
|
|
* \a child must have an id.
|
|
*
|
|
* \par Errors:
|
|
* <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>
|
|
*/
|
|
int snd_config_add_after(snd_config_t *after, snd_config_t *child)
|
|
{
|
|
snd_config_iterator_t i, next;
|
|
snd_config_t *parent;
|
|
assert(after && child);
|
|
parent = after->parent;
|
|
assert(parent);
|
|
if (!child->id || child->parent)
|
|
return -EINVAL;
|
|
snd_config_for_each(i, next, parent) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
if (strcmp(child->id, n->id) == 0)
|
|
return -EEXIST;
|
|
}
|
|
child->parent = parent;
|
|
list_insert(&child->list, &after->list, after->list.next);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Adds a child before another child configuration node.
|
|
* \param before Handle to the start configuration node.
|
|
* \param child Handle to the configuration node to be added.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This function makes the node \a child a child of the parent of
|
|
* the node \a before.
|
|
*
|
|
* The parent node then owns the child node, i.e., the child node gets
|
|
* deleted together with its parent.
|
|
*
|
|
* \a child must have an id.
|
|
*
|
|
* \par Errors:
|
|
* <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>
|
|
*/
|
|
int snd_config_add_before(snd_config_t *before, snd_config_t *child)
|
|
{
|
|
snd_config_iterator_t i, next;
|
|
snd_config_t *parent;
|
|
assert(before && child);
|
|
parent = before->parent;
|
|
assert(parent);
|
|
if (!child->id || child->parent)
|
|
return -EINVAL;
|
|
snd_config_for_each(i, next, parent) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
if (strcmp(child->id, n->id) == 0)
|
|
return -EEXIST;
|
|
}
|
|
child->parent = parent;
|
|
list_insert(&child->list, before->list.prev, &before->list);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* append all src items to the end of dst arrray
|
|
*/
|
|
static int _snd_config_array_merge(snd_config_t *dst, snd_config_t *src, int index)
|
|
{
|
|
snd_config_iterator_t si, snext;
|
|
int err;
|
|
|
|
snd_config_for_each(si, snext, src) {
|
|
snd_config_t *sn = snd_config_iterator_entry(si);
|
|
char id[16];
|
|
snd_config_remove(sn);
|
|
snprintf(id, sizeof(id), "%d", index++);
|
|
err = snd_config_set_id(sn, id);
|
|
if (err < 0) {
|
|
snd_config_delete(sn);
|
|
return err;
|
|
}
|
|
sn->parent = dst;
|
|
list_add_tail(&sn->list, &dst->u.compound.fields);
|
|
}
|
|
snd_config_delete(src);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief In-place merge of two config handles
|
|
* \param[out] dst Config handle for the merged contents
|
|
* \param[in] src Config handle to merge into dst (may be NULL)
|
|
* \param[in] override Override flag
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This function merges all fields from the source compound to the destination compound.
|
|
* When the \a override flag is set, the related subtree in \a dst is replaced from \a src.
|
|
*
|
|
* When \a override is not set, the child compounds are traversed and merged.
|
|
*
|
|
* The configuration elements other than compounds are always substituted (overwritten)
|
|
* from the \a src config handle.
|
|
*
|
|
* The src handle is deleted.
|
|
*
|
|
* Note: On error, config handles may be modified.
|
|
*
|
|
* \par Errors:
|
|
* <dl>
|
|
* <dt>-EEXIST<dd>identifier already exists (!override)
|
|
* <dt>-ENOMEM<dd>not enough memory
|
|
* </dl>
|
|
*/
|
|
int snd_config_merge(snd_config_t *dst, snd_config_t *src, int override)
|
|
{
|
|
snd_config_iterator_t di, si, dnext, snext;
|
|
bool found;
|
|
int err, array;
|
|
|
|
assert(dst);
|
|
if (src == NULL)
|
|
return 0;
|
|
if (dst->type != SND_CONFIG_TYPE_COMPOUND || src->type != SND_CONFIG_TYPE_COMPOUND)
|
|
return snd_config_substitute(dst, src);
|
|
array = snd_config_is_array(dst);
|
|
if (array && snd_config_is_array(src))
|
|
return _snd_config_array_merge(dst, src, array);
|
|
snd_config_for_each(si, snext, src) {
|
|
snd_config_t *sn = snd_config_iterator_entry(si);
|
|
found = false;
|
|
snd_config_for_each(di, dnext, dst) {
|
|
snd_config_t *dn = snd_config_iterator_entry(di);
|
|
if (strcmp(sn->id, dn->id) == 0) {
|
|
if (override ||
|
|
sn->type != SND_CONFIG_TYPE_COMPOUND ||
|
|
dn->type != SND_CONFIG_TYPE_COMPOUND) {
|
|
err = snd_config_substitute(dn, sn);
|
|
if (err < 0)
|
|
return err;
|
|
} else {
|
|
err = snd_config_merge(dn, sn, 0);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
/* move config from src to dst */
|
|
snd_config_remove(sn);
|
|
sn->parent = dst;
|
|
list_add_tail(&sn->list, &dst->u.compound.fields);
|
|
}
|
|
}
|
|
snd_config_delete(src);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Removes a configuration node from its tree.
|
|
* \param config Handle to the configuration node to be removed.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This function makes \a config a top-level node, i.e., if \a config
|
|
* has a parent, then \a config is removed from the list of the parent's
|
|
* children.
|
|
*
|
|
* This functions does \e not free the removed node.
|
|
*
|
|
* \sa snd_config_delete
|
|
*/
|
|
int snd_config_remove(snd_config_t *config)
|
|
{
|
|
assert(config);
|
|
if (config->parent)
|
|
list_del(&config->list);
|
|
config->parent = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Frees a configuration node.
|
|
* \param config Handle to the configuration node to be deleted.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This function frees a configuration node and all its resources.
|
|
*
|
|
* If the node is a child node, it is removed from the tree before being
|
|
* deleted.
|
|
*
|
|
* If the node is a compound node, its descendants (the whole subtree)
|
|
* are deleted recursively.
|
|
*
|
|
* The function is supposed to be called only for locally copied config
|
|
* trees. For the global tree, take the reference via #snd_config_update_ref
|
|
* and free it via #snd_config_unref.
|
|
*
|
|
* \par Conforming to:
|
|
* LSB 3.2
|
|
*
|
|
* \sa snd_config_remove
|
|
*/
|
|
int snd_config_delete(snd_config_t *config)
|
|
{
|
|
assert(config);
|
|
if (config->refcount > 0) {
|
|
config->refcount--;
|
|
return 0;
|
|
}
|
|
switch (config->type) {
|
|
case SND_CONFIG_TYPE_COMPOUND:
|
|
{
|
|
int err;
|
|
struct list_head *i;
|
|
i = config->u.compound.fields.next;
|
|
while (i != &config->u.compound.fields) {
|
|
struct list_head *nexti = i->next;
|
|
snd_config_t *child = snd_config_iterator_entry(i);
|
|
err = snd_config_delete(child);
|
|
if (err < 0)
|
|
return err;
|
|
i = nexti;
|
|
}
|
|
break;
|
|
}
|
|
case SND_CONFIG_TYPE_STRING:
|
|
free(config->u.string);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (config->parent)
|
|
list_del(&config->list);
|
|
free(config->id);
|
|
free(config);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Deletes the children of a node.
|
|
* \param config Handle to the compound configuration node.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This function removes and frees all children of a configuration node.
|
|
*
|
|
* Any compound nodes among the children of \a config are deleted
|
|
* recursively.
|
|
*
|
|
* After a successful call to this function, \a config is an empty
|
|
* compound node.
|
|
*
|
|
* \par Errors:
|
|
* <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 empty compound configuration node in the path.
|
|
* \param[out] config The function puts the handle to the new or
|
|
* existing compound node at the address specified
|
|
* by \a config.
|
|
* \param[in] root The id of the new node.
|
|
* \param[in] key The id of the new node.
|
|
* \param[in] join Join flag.
|
|
* \param[in] override Override flag.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This function creates a new empty node of type
|
|
* #SND_CONFIG_TYPE_COMPOUND if the path does not exist. Otherwise,
|
|
* the node from the current configuration tree is returned without
|
|
* any modification. The \a join argument is ignored in this case.
|
|
*
|
|
* \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.
|
|
* <dt>-EACCESS<dd>Path exists, but it's not a compound (!override)
|
|
* </dl>
|
|
*/
|
|
int snd_config_make_path(snd_config_t **config, snd_config_t *root,
|
|
const char *key, int join, int override)
|
|
{
|
|
snd_config_t *n;
|
|
const char *p;
|
|
int err;
|
|
|
|
while (1) {
|
|
p = strchr(key, '.');
|
|
if (p) {
|
|
err = _snd_config_search(root, key, p - key, &n);
|
|
if (err < 0) {
|
|
size_t l = p - key;
|
|
char *s = malloc(l + 1);
|
|
if (s == NULL)
|
|
return -ENOMEM;
|
|
strncpy(s, key, l);
|
|
s[l] = '\0';
|
|
err = snd_config_make_compound(&n, s, join);
|
|
free(s);
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_config_add(root, n);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
root = n;
|
|
key = p + 1;
|
|
} else {
|
|
err = _snd_config_search(root, key, -1, config);
|
|
if (err == 0) {
|
|
if ((*config)->type != SND_CONFIG_TYPE_COMPOUND) {
|
|
if (override) {
|
|
err = snd_config_delete(*config);
|
|
if (err < 0)
|
|
return err;
|
|
goto __make;
|
|
} else {
|
|
return -EACCES;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
__make:
|
|
err = snd_config_make_compound(&n, key, join);
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_config_add(root, n);
|
|
if (err < 0)
|
|
return err;
|
|
*config = n;
|
|
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 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. The node
|
|
* contains with a copy of the string \c value, replacing any character other
|
|
* than alphanumeric, space, or '-' with the character '_'.
|
|
*
|
|
* \par Errors:
|
|
* <dl>
|
|
* <dt>-ENOMEM<dd>Out of memory.
|
|
* </dl>
|
|
*
|
|
* \par Conforming to:
|
|
* LSB 3.2
|
|
*/
|
|
int snd_config_imake_safe_string(snd_config_t **config, const char *id, const char *value)
|
|
{
|
|
int err;
|
|
snd_config_t *tmp;
|
|
char *c;
|
|
|
|
err = snd_config_make(&tmp, id, SND_CONFIG_TYPE_STRING);
|
|
if (err < 0)
|
|
return err;
|
|
if (value) {
|
|
tmp->u.string = strdup(value);
|
|
if (!tmp->u.string) {
|
|
snd_config_delete(tmp);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (c = tmp->u.string; *c; c++) {
|
|
if (*c == ' ' || *c == '-' || *c == '_' ||
|
|
(*c >= '0' && *c <= '9') ||
|
|
(*c >= 'a' && *c <= 'z') ||
|
|
(*c >= 'A' && *c <= 'Z'))
|
|
continue;
|
|
*c = '_';
|
|
}
|
|
} else {
|
|
tmp->u.string = NULL;
|
|
}
|
|
*config = tmp;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief Creates a pointer configuration node with the given initial value.
|
|
* \param[out] config The function puts the handle to the new node at
|
|
* the address specified by \a config.
|
|
* \param[in] id The id of the new node.
|
|
* \param[in] value The initial value of the new node.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This function creates a new node of type #SND_CONFIG_TYPE_POINTER and
|
|
* with value \c value.
|
|
*
|
|
* \par Errors:
|
|
* <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) {
|
|
int array = snd_config_is_array(config);
|
|
return _snd_config_save_children(config, out, 0, 0, array);
|
|
} else {
|
|
return _snd_config_save_node_value(config, out, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* *** search macros ***
|
|
*/
|
|
|
|
#ifndef DOC_HIDDEN
|
|
|
|
#define SND_CONFIG_SEARCH(config, key, result, extra_code) \
|
|
{ \
|
|
snd_config_t *n; \
|
|
int err; \
|
|
const char *p; \
|
|
assert(config && key); \
|
|
while (1) { \
|
|
if (config->type != SND_CONFIG_TYPE_COMPOUND) \
|
|
return -ENOENT; \
|
|
{ extra_code ; } \
|
|
p = strchr(key, '.'); \
|
|
if (p) { \
|
|
err = _snd_config_search(config, key, p - key, &n); \
|
|
if (err < 0) \
|
|
return err; \
|
|
config = n; \
|
|
key = p + 1; \
|
|
} else \
|
|
return _snd_config_search(config, key, -1, result); \
|
|
} \
|
|
}
|
|
|
|
#define SND_CONFIG_SEARCHA(root, config, key, result, fcn, extra_code) \
|
|
{ \
|
|
snd_config_t *n; \
|
|
int err; \
|
|
const char *p; \
|
|
assert(config && key); \
|
|
while (1) { \
|
|
if (config->type != SND_CONFIG_TYPE_COMPOUND) { \
|
|
if (snd_config_get_string(config, &p) < 0) \
|
|
return -ENOENT; \
|
|
err = fcn(root, root, p, &config); \
|
|
if (err < 0) \
|
|
return err; \
|
|
} \
|
|
{ extra_code ; } \
|
|
p = strchr(key, '.'); \
|
|
if (p) { \
|
|
err = _snd_config_search(config, key, p - key, &n); \
|
|
if (err < 0) \
|
|
return err; \
|
|
config = n; \
|
|
key = p + 1; \
|
|
} else \
|
|
return _snd_config_search(config, key, -1, result); \
|
|
} \
|
|
}
|
|
|
|
#define SND_CONFIG_SEARCHV(config, result, fcn) \
|
|
{ \
|
|
snd_config_t *n; \
|
|
va_list arg; \
|
|
assert(config); \
|
|
va_start(arg, result); \
|
|
while (1) { \
|
|
const char *k = va_arg(arg, const char *); \
|
|
int err; \
|
|
if (!k) \
|
|
break; \
|
|
err = fcn(config, k, &n); \
|
|
if (err < 0) { \
|
|
va_end(arg); \
|
|
return err; \
|
|
} \
|
|
config = n; \
|
|
} \
|
|
va_end(arg); \
|
|
if (result) \
|
|
*result = n; \
|
|
return 0; \
|
|
}
|
|
|
|
#define SND_CONFIG_SEARCHVA(root, config, result, fcn) \
|
|
{ \
|
|
snd_config_t *n; \
|
|
va_list arg; \
|
|
assert(config); \
|
|
va_start(arg, result); \
|
|
while (1) { \
|
|
const char *k = va_arg(arg, const char *); \
|
|
int err; \
|
|
if (!k) \
|
|
break; \
|
|
err = fcn(root, config, k, &n); \
|
|
if (err < 0) { \
|
|
va_end(arg); \
|
|
return err; \
|
|
} \
|
|
config = n; \
|
|
} \
|
|
va_end(arg); \
|
|
if (result) \
|
|
*result = n; \
|
|
return 0; \
|
|
}
|
|
|
|
#define SND_CONFIG_SEARCH_ALIAS(config, base, key, result, fcn1, fcn2) \
|
|
{ \
|
|
snd_config_t *res = NULL; \
|
|
char *old_key; \
|
|
int err, first = 1, maxloop = 1000; \
|
|
assert(config && key); \
|
|
while (1) { \
|
|
old_key = strdup(key); \
|
|
if (old_key == NULL) { \
|
|
err = -ENOMEM; \
|
|
res = NULL; \
|
|
break; \
|
|
} \
|
|
err = first && base ? -EIO : fcn1(config, config, key, &res); \
|
|
if (err < 0) { \
|
|
if (!base) \
|
|
break; \
|
|
err = fcn2(config, config, &res, base, key, NULL); \
|
|
if (err < 0) \
|
|
break; \
|
|
} \
|
|
if (snd_config_get_string(res, &key) < 0) \
|
|
break; \
|
|
assert(key); \
|
|
if (!first && (strcmp(key, old_key) == 0 || maxloop <= 0)) { \
|
|
if (maxloop == 0) \
|
|
SNDERR("maximum loop count reached (circular configuration?)"); \
|
|
else \
|
|
SNDERR("key %s refers to itself", key); \
|
|
err = -EINVAL; \
|
|
res = NULL; \
|
|
break; \
|
|
} \
|
|
free(old_key); \
|
|
first = 0; \
|
|
maxloop--; \
|
|
} \
|
|
free(old_key); \
|
|
if (!res) \
|
|
return err; \
|
|
if (result) \
|
|
*result = res; \
|
|
return 0; \
|
|
}
|
|
|
|
#endif /* DOC_HIDDEN */
|
|
|
|
/**
|
|
* \brief Searches for a node in a configuration tree.
|
|
* \param[in] config Handle to the root of the configuration (sub)tree to search.
|
|
* \param[in] key Search key: one or more node ids, separated with dots.
|
|
* \param[out] result When \a result != \c NULL, the function puts the
|
|
* handle to the node found at the address specified
|
|
* by \a result.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This function searches for a child node of \a config that is
|
|
* identified by \a key, which contains either the id of a direct child
|
|
* node of \a config, or a series of ids, separated with dots, where
|
|
* each id specifies a node that is contained in the previous compound
|
|
* node.
|
|
*
|
|
* In the following example, the comment after each node shows the
|
|
* search key to find that node, assuming that \a config is a handle to
|
|
* the compound node with id \c config:
|
|
* \code
|
|
* config {
|
|
* a 42 # "a"
|
|
* b { # "b"
|
|
* c "cee" # "b.c"
|
|
* d { # "b.d"
|
|
* e 2.71828 # "b.d.e"
|
|
* }
|
|
* }
|
|
* }
|
|
* \endcode
|
|
*
|
|
* \par Errors:
|
|
* <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"
|
|
|
|
/**
|
|
* \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;
|
|
ino64_t ino;
|
|
time_t mtime;
|
|
};
|
|
|
|
struct _snd_config_update {
|
|
unsigned int count;
|
|
struct finfo *finfo;
|
|
};
|
|
#endif /* DOC_HIDDEN */
|
|
|
|
static snd_config_update_t *snd_config_global_update = NULL;
|
|
|
|
static int snd_config_hooks_call(snd_config_t *root, snd_config_t *config, snd_config_t *private_data)
|
|
{
|
|
void *h = NULL;
|
|
snd_config_t *c, *func_conf = NULL;
|
|
char *buf = NULL, errbuf[256];
|
|
const char *lib = NULL, *func_name = NULL;
|
|
const char *str;
|
|
int (*func)(snd_config_t *root, snd_config_t *config, snd_config_t **dst, snd_config_t *private_data) = NULL;
|
|
int err;
|
|
|
|
err = snd_config_search(config, "func", &c);
|
|
if (err < 0) {
|
|
SNDERR("Field func is missing");
|
|
return err;
|
|
}
|
|
err = snd_config_get_string(c, &str);
|
|
if (err < 0) {
|
|
SNDERR("Invalid type for field func");
|
|
return err;
|
|
}
|
|
assert(str);
|
|
err = snd_config_search_definition(root, "hook_func", str, &func_conf);
|
|
if (err >= 0) {
|
|
snd_config_iterator_t i, next;
|
|
if (snd_config_get_type(func_conf) != SND_CONFIG_TYPE_COMPOUND) {
|
|
SNDERR("Invalid type for func %s definition", str);
|
|
err = -EINVAL;
|
|
goto _err;
|
|
}
|
|
snd_config_for_each(i, next, func_conf) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
const char *id = n->id;
|
|
if (strcmp(id, "comment") == 0)
|
|
continue;
|
|
if (strcmp(id, "lib") == 0) {
|
|
err = snd_config_get_string(n, &lib);
|
|
if (err < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
goto _err;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "func") == 0) {
|
|
err = snd_config_get_string(n, &func_name);
|
|
if (err < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
goto _err;
|
|
}
|
|
continue;
|
|
}
|
|
SNDERR("Unknown field %s", id);
|
|
}
|
|
}
|
|
if (!func_name) {
|
|
int len = 16 + strlen(str) + 1;
|
|
buf = malloc(len);
|
|
if (! buf) {
|
|
err = -ENOMEM;
|
|
goto _err;
|
|
}
|
|
snprintf(buf, len, "snd_config_hook_%s", str);
|
|
buf[len-1] = '\0';
|
|
func_name = buf;
|
|
}
|
|
h = INTERNAL(snd_dlopen)(lib, RTLD_NOW, errbuf, sizeof(errbuf));
|
|
func = h ? snd_dlsym(h, func_name, SND_DLSYM_VERSION(SND_CONFIG_DLSYM_VERSION_HOOK)) : NULL;
|
|
err = 0;
|
|
if (!h) {
|
|
SNDERR("Cannot open shared library %s (%s)", lib, errbuf);
|
|
err = -ENOENT;
|
|
} else if (!func) {
|
|
SNDERR("symbol %s is not defined inside %s", func_name, lib);
|
|
snd_dlclose(h);
|
|
err = -ENXIO;
|
|
}
|
|
_err:
|
|
if (func_conf)
|
|
snd_config_delete(func_conf);
|
|
if (err >= 0) {
|
|
snd_config_t *nroot;
|
|
err = func(root, config, &nroot, private_data);
|
|
if (err < 0)
|
|
SNDERR("function %s returned error: %s", func_name, snd_strerror(err));
|
|
snd_dlclose(h);
|
|
if (err >= 0 && nroot)
|
|
err = snd_config_substitute(root, nroot);
|
|
}
|
|
free(buf);
|
|
if (err < 0)
|
|
return err;
|
|
return 0;
|
|
}
|
|
|
|
static int snd_config_hooks(snd_config_t *config, snd_config_t *private_data)
|
|
{
|
|
snd_config_t *n;
|
|
snd_config_iterator_t i, next;
|
|
int err, hit, idx = 0;
|
|
|
|
if ((err = snd_config_search(config, "@hooks", &n)) < 0)
|
|
return 0;
|
|
snd_config_lock();
|
|
snd_config_remove(n);
|
|
do {
|
|
hit = 0;
|
|
snd_config_for_each(i, next, n) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
const char *id = n->id;
|
|
long i;
|
|
err = safe_strtol(id, &i);
|
|
if (err < 0) {
|
|
SNDERR("id of field %s is not and integer", id);
|
|
err = -EINVAL;
|
|
goto _err;
|
|
}
|
|
if (i == idx) {
|
|
err = snd_config_hooks_call(config, n, private_data);
|
|
if (err < 0)
|
|
goto _err;
|
|
idx++;
|
|
hit = 1;
|
|
}
|
|
}
|
|
} while (hit);
|
|
err = 0;
|
|
_err:
|
|
snd_config_delete(n);
|
|
snd_config_unlock();
|
|
return err;
|
|
}
|
|
|
|
static int config_filename_filter(const struct dirent64 *dirent)
|
|
{
|
|
size_t flen;
|
|
|
|
if (dirent == NULL)
|
|
return 0;
|
|
if (dirent->d_type == DT_DIR)
|
|
return 0;
|
|
|
|
flen = strlen(dirent->d_name);
|
|
if (flen <= 5)
|
|
return 0;
|
|
|
|
if (strncmp(&dirent->d_name[flen-5], ".conf", 5) == 0)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int config_file_open(snd_config_t *root, const char *filename)
|
|
{
|
|
snd_input_t *in;
|
|
int err;
|
|
|
|
err = snd_input_stdio_open(&in, filename, "r");
|
|
if (err >= 0) {
|
|
err = snd_config_load(root, in);
|
|
snd_input_close(in);
|
|
if (err < 0)
|
|
SNDERR("%s may be old or corrupted: consider to remove or fix it", filename);
|
|
} else
|
|
SNDERR("cannot access file %s", filename);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int config_file_load(snd_config_t *root, const char *fn, int errors)
|
|
{
|
|
struct stat64 st;
|
|
struct dirent64 **namelist;
|
|
int err, n;
|
|
|
|
if (!errors && access(fn, R_OK) < 0)
|
|
return 1;
|
|
if (stat64(fn, &st) < 0) {
|
|
SNDERR("cannot stat file/directory %s", fn);
|
|
return 1;
|
|
}
|
|
if (!S_ISDIR(st.st_mode))
|
|
return config_file_open(root, fn);
|
|
#ifndef DOC_HIDDEN
|
|
#if defined(_GNU_SOURCE) && \
|
|
!defined(__NetBSD__) && \
|
|
!defined(__FreeBSD__) && \
|
|
!defined(__OpenBSD__) && \
|
|
!defined(__DragonFly__) && \
|
|
!defined(__sun) && \
|
|
!defined(__ANDROID__) && \
|
|
!defined(__OHOS__)
|
|
#define SORTFUNC versionsort64
|
|
#else
|
|
#define SORTFUNC alphasort64
|
|
#endif
|
|
#endif
|
|
n = scandir64(fn, &namelist, config_filename_filter, SORTFUNC);
|
|
if (n > 0) {
|
|
int j;
|
|
err = 0;
|
|
for (j = 0; j < n; ++j) {
|
|
if (err >= 0) {
|
|
int sl = strlen(fn) + strlen(namelist[j]->d_name) + 2;
|
|
char *filename = malloc(sl);
|
|
snprintf(filename, sl, "%s/%s", fn, namelist[j]->d_name);
|
|
filename[sl-1] = '\0';
|
|
|
|
err = config_file_open(root, filename);
|
|
free(filename);
|
|
}
|
|
free(namelist[j]);
|
|
}
|
|
free(namelist);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int config_file_load_user(snd_config_t *root, const char *fn, int errors)
|
|
{
|
|
char *fn2;
|
|
int err;
|
|
|
|
err = snd_user_file(fn, &fn2);
|
|
if (err < 0)
|
|
return config_file_load(root, fn, errors);
|
|
err = config_file_load(root, fn2, errors);
|
|
free(fn2);
|
|
return err;
|
|
}
|
|
|
|
static int config_file_load_user_all(snd_config_t *_root, snd_config_t *_file, int errors)
|
|
{
|
|
snd_config_t *file = _file, *root = _root, *n;
|
|
char *name, *name2, *remain, *rname = NULL;
|
|
int err;
|
|
|
|
if (snd_config_get_type(_file) == SND_CONFIG_TYPE_COMPOUND) {
|
|
if ((err = snd_config_search(_file, "file", &file)) < 0) {
|
|
SNDERR("Field file not found");
|
|
return err;
|
|
}
|
|
if ((err = snd_config_search(_file, "root", &root)) >= 0) {
|
|
err = snd_config_get_ascii(root, &rname);
|
|
if (err < 0) {
|
|
SNDERR("Field root is bad");
|
|
return err;
|
|
}
|
|
err = snd_config_make_compound(&root, rname, 0);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
}
|
|
if ((err = snd_config_get_ascii(file, &name)) < 0)
|
|
goto _err;
|
|
name2 = name;
|
|
remain = strstr(name, "|||");
|
|
while (1) {
|
|
if (remain) {
|
|
*remain = '\0';
|
|
remain += 3;
|
|
}
|
|
err = config_file_load_user(root, name2, errors);
|
|
if (err < 0)
|
|
goto _err;
|
|
if (err == 0) /* first hit wins */
|
|
break;
|
|
if (!remain)
|
|
break;
|
|
name2 = remain;
|
|
remain = strstr(remain, "|||");
|
|
}
|
|
_err:
|
|
if (root != _root) {
|
|
if (err == 0) {
|
|
if (snd_config_get_type(root) == SND_CONFIG_TYPE_COMPOUND) {
|
|
if (snd_config_is_empty(root))
|
|
goto _del;
|
|
}
|
|
err = snd_config_make_path(&n, _root, rname, 0, 1);
|
|
if (err < 0)
|
|
goto _del;
|
|
err = snd_config_substitute(n, root);
|
|
if (err == 0)
|
|
goto _fin;
|
|
}
|
|
_del:
|
|
snd_config_delete(root);
|
|
}
|
|
_fin:
|
|
free(name);
|
|
free(rname);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* \brief Loads and parses the given configurations files.
|
|
* \param[in] root Handle to the root configuration node.
|
|
* \param[in] config Handle to the configuration node for this hook.
|
|
* \param[out] dst The function puts the handle to the configuration
|
|
* node loaded from the file(s) at the address specified
|
|
* by \a dst.
|
|
* \param[in] private_data Handle to the private data configuration node.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* See \ref confhooks for an example.
|
|
*/
|
|
int snd_config_hook_load(snd_config_t *root, snd_config_t *config, snd_config_t **dst, snd_config_t *private_data)
|
|
{
|
|
snd_config_t *n;
|
|
snd_config_iterator_t i, next;
|
|
int err, idx = 0, errors = 1, hit;
|
|
|
|
assert(root && dst);
|
|
if ((err = snd_config_search(config, "errors", &n)) >= 0) {
|
|
errors = snd_config_get_bool(n);
|
|
if (errors < 0) {
|
|
SNDERR("Invalid bool value in field errors");
|
|
return errors;
|
|
}
|
|
}
|
|
if ((err = snd_config_search(config, "files", &n)) < 0) {
|
|
SNDERR("Unable to find field files in the pre-load section");
|
|
return -EINVAL;
|
|
}
|
|
if ((err = snd_config_expand(n, root, NULL, private_data, &n)) < 0) {
|
|
SNDERR("Unable to expand filenames in the pre-load section");
|
|
return err;
|
|
}
|
|
if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
|
|
SNDERR("Invalid type for field filenames");
|
|
goto _err;
|
|
}
|
|
do {
|
|
hit = 0;
|
|
snd_config_for_each(i, next, n) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
const char *id = n->id;
|
|
long i;
|
|
err = safe_strtol(id, &i);
|
|
if (err < 0) {
|
|
SNDERR("id of field %s is not and integer", id);
|
|
err = -EINVAL;
|
|
goto _err;
|
|
}
|
|
if (i == idx) {
|
|
err = config_file_load_user_all(root, n, errors);
|
|
if (err < 0)
|
|
goto _err;
|
|
idx++;
|
|
hit = 1;
|
|
}
|
|
}
|
|
} while (hit);
|
|
*dst = NULL;
|
|
err = 0;
|
|
_err:
|
|
snd_config_delete(n);
|
|
return err;
|
|
}
|
|
#ifndef DOC_HIDDEN
|
|
SND_DLSYM_BUILD_VERSION(snd_config_hook_load, SND_CONFIG_DLSYM_VERSION_HOOK);
|
|
#endif
|
|
|
|
#ifndef DOC_HIDDEN
|
|
int snd_determine_driver(int card, char **driver);
|
|
#endif
|
|
|
|
static snd_config_t *_snd_config_hook_private_data(int card, const char *driver)
|
|
{
|
|
snd_config_t *private_data, *v;
|
|
int err;
|
|
|
|
err = snd_config_make_compound(&private_data, NULL, 0);
|
|
if (err < 0)
|
|
goto __err;
|
|
err = snd_config_imake_integer(&v, "integer", card);
|
|
if (err < 0)
|
|
goto __err;
|
|
err = snd_config_add(private_data, v);
|
|
if (err < 0) {
|
|
snd_config_delete(v);
|
|
goto __err;
|
|
}
|
|
err = snd_config_imake_string(&v, "string", driver);
|
|
if (err < 0)
|
|
goto __err;
|
|
err = snd_config_add(private_data, v);
|
|
if (err < 0) {
|
|
snd_config_delete(v);
|
|
goto __err;
|
|
}
|
|
return private_data;
|
|
|
|
__err:
|
|
snd_config_delete(private_data);
|
|
return NULL;
|
|
}
|
|
|
|
static int _snd_config_hook_table(snd_config_t *root, snd_config_t *config, snd_config_t *private_data)
|
|
{
|
|
snd_config_t *n, *tn;
|
|
const char *id;
|
|
int err;
|
|
|
|
if (snd_config_search(config, "table", &n) < 0)
|
|
return 0;
|
|
if ((err = snd_config_expand(n, root, NULL, private_data, &n)) < 0) {
|
|
SNDERR("Unable to expand table compound");
|
|
return err;
|
|
}
|
|
if (snd_config_search(n, "id", &tn) < 0 ||
|
|
snd_config_get_string(tn, &id) < 0) {
|
|
SNDERR("Unable to find field table.id");
|
|
snd_config_delete(n);
|
|
return -EINVAL;
|
|
}
|
|
if (snd_config_search(n, "value", &tn) < 0 ||
|
|
snd_config_get_type(tn) != SND_CONFIG_TYPE_STRING) {
|
|
SNDERR("Unable to find field table.value");
|
|
snd_config_delete(n);
|
|
return -EINVAL;
|
|
}
|
|
snd_config_remove(tn);
|
|
if ((err = snd_config_set_id(tn, id)) < 0) {
|
|
snd_config_delete(tn);
|
|
snd_config_delete(n);
|
|
return err;
|
|
}
|
|
snd_config_delete(n);
|
|
if ((err = snd_config_add(root, tn)) < 0) {
|
|
snd_config_delete(tn);
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \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;
|
|
snd_config_t *loaded; // trace loaded cards
|
|
|
|
err = snd_config_top(&loaded);
|
|
if (err < 0)
|
|
return err;
|
|
do {
|
|
err = snd_card_next(&card);
|
|
if (err < 0)
|
|
goto __fin_err;
|
|
if (card >= 0) {
|
|
snd_config_t *n, *m, *private_data = NULL;
|
|
const char *driver;
|
|
char *fdriver = NULL;
|
|
bool load;
|
|
err = snd_determine_driver(card, &fdriver);
|
|
if (err < 0)
|
|
goto __fin_err;
|
|
if (snd_config_search(root, fdriver, &n) >= 0) {
|
|
if (snd_config_get_string(n, &driver) < 0) {
|
|
if (snd_config_get_type(n) == SND_CONFIG_TYPE_COMPOUND) {
|
|
snd_config_get_id(n, &driver);
|
|
goto __std;
|
|
}
|
|
goto __err;
|
|
}
|
|
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;
|
|
}
|
|
__std:
|
|
load = true;
|
|
err = snd_config_imake_integer(&m, driver, 1);
|
|
if (err < 0)
|
|
goto __err;
|
|
err = snd_config_add(loaded, m);
|
|
if (err < 0) {
|
|
if (err == -EEXIST) {
|
|
snd_config_delete(m);
|
|
load = false;
|
|
} else {
|
|
goto __err;
|
|
}
|
|
}
|
|
private_data = _snd_config_hook_private_data(card, driver);
|
|
if (!private_data) {
|
|
err = -ENOMEM;
|
|
goto __err;
|
|
}
|
|
err = _snd_config_hook_table(root, config, private_data);
|
|
if (err < 0)
|
|
goto __err;
|
|
if (load)
|
|
err = snd_config_hook_load(root, config, &n, private_data);
|
|
__err:
|
|
if (private_data)
|
|
snd_config_delete(private_data);
|
|
free(fdriver);
|
|
if (err < 0)
|
|
goto __fin_err;
|
|
}
|
|
} while (card >= 0);
|
|
snd_config_delete(loaded);
|
|
*dst = NULL;
|
|
return 0;
|
|
__fin_err:
|
|
snd_config_delete(loaded);
|
|
return err;
|
|
}
|
|
#ifndef DOC_HIDDEN
|
|
SND_DLSYM_BUILD_VERSION(snd_config_hook_load_for_all_cards, SND_CONFIG_DLSYM_VERSION_HOOK);
|
|
#endif
|
|
|
|
/**
|
|
* \brief Updates a configuration tree by rereading the configuration files (if needed).
|
|
* \param[in,out] _top Address of the handle to the top-level node.
|
|
* \param[in,out] _update Address of a pointer to private update information.
|
|
* \param[in] cfgs A list of configuration file names, delimited with ':'.
|
|
* If \p cfgs is \c NULL, the default global
|
|
* configuration file is used.
|
|
* \return 0 if \a _top was up to date, 1 if the configuration files
|
|
* have been reread, otherwise a negative error code.
|
|
*
|
|
* The variables pointed to by \a _top and \a _update can be initialized
|
|
* to \c NULL before the first call to this function. The private
|
|
* update information holds information about all used configuration
|
|
* files that allows this function to detects changes to them; this data
|
|
* can be freed with #snd_config_update_free.
|
|
*
|
|
* The global configuration files are specified in the environment variable
|
|
* \c ALSA_CONFIG_PATH.
|
|
*
|
|
* \warning If the configuration tree is reread, all string pointers and
|
|
* configuration node handles previously obtained from this tree become
|
|
* invalid.
|
|
*
|
|
* \par Errors:
|
|
* Any errors encountered when parsing the input or returned by hooks or
|
|
* functions.
|
|
*/
|
|
int snd_config_update_r(snd_config_t **_top, snd_config_update_t **_update, const char *cfgs)
|
|
{
|
|
int err;
|
|
const char *configs, *c;
|
|
unsigned int k;
|
|
size_t l;
|
|
snd_config_update_t *local;
|
|
snd_config_update_t *update;
|
|
snd_config_t *top;
|
|
|
|
assert(_top && _update);
|
|
top = *_top;
|
|
update = *_update;
|
|
configs = cfgs;
|
|
if (!configs) {
|
|
configs = getenv(ALSA_CONFIG_PATH_VAR);
|
|
if (!configs || !*configs) {
|
|
const char *topdir = snd_config_topdir();
|
|
char *s = alloca(strlen(topdir) +
|
|
strlen("alsa.conf") + 2);
|
|
sprintf(s, "%s/alsa.conf", topdir);
|
|
configs = s;
|
|
}
|
|
}
|
|
for (k = 0, c = configs; (l = strcspn(c, ": ")) > 0; ) {
|
|
c += l;
|
|
k++;
|
|
if (!*c)
|
|
break;
|
|
c++;
|
|
}
|
|
if (k == 0) {
|
|
local = NULL;
|
|
goto _reread;
|
|
}
|
|
local = (snd_config_update_t *)calloc(1, sizeof(snd_config_update_t));
|
|
if (!local)
|
|
return -ENOMEM;
|
|
local->count = k;
|
|
local->finfo = calloc(local->count, sizeof(struct finfo));
|
|
if (!local->finfo) {
|
|
free(local);
|
|
return -ENOMEM;
|
|
}
|
|
for (k = 0, c = configs; (l = strcspn(c, ": ")) > 0; ) {
|
|
char name[l + 1];
|
|
memcpy(name, c, l);
|
|
name[l] = 0;
|
|
err = snd_user_file(name, &local->finfo[k].name);
|
|
if (err < 0)
|
|
goto _end;
|
|
c += l;
|
|
k++;
|
|
if (!*c)
|
|
break;
|
|
c++;
|
|
}
|
|
for (k = 0; k < local->count; ++k) {
|
|
struct stat64 st;
|
|
struct finfo *lf = &local->finfo[k];
|
|
if (stat64(lf->name, &st) >= 0) {
|
|
lf->dev = st.st_dev;
|
|
lf->ino = st.st_ino;
|
|
lf->mtime = st.st_mtime;
|
|
} else {
|
|
SNDERR("Cannot access file %s", lf->name);
|
|
free(lf->name);
|
|
memmove(&local->finfo[k], &local->finfo[k+1], sizeof(struct finfo) * (local->count - k - 1));
|
|
k--;
|
|
local->count--;
|
|
}
|
|
}
|
|
if (!update)
|
|
goto _reread;
|
|
if (local->count != update->count)
|
|
goto _reread;
|
|
for (k = 0; k < local->count; ++k) {
|
|
struct finfo *lf = &local->finfo[k];
|
|
struct finfo *uf = &update->finfo[k];
|
|
if (strcmp(lf->name, uf->name) != 0 ||
|
|
lf->dev != uf->dev ||
|
|
lf->ino != uf->ino ||
|
|
lf->mtime != uf->mtime)
|
|
goto _reread;
|
|
}
|
|
err = 0;
|
|
|
|
_end:
|
|
if (err < 0) {
|
|
if (top) {
|
|
snd_config_delete(top);
|
|
*_top = NULL;
|
|
}
|
|
if (update) {
|
|
snd_config_update_free(update);
|
|
*_update = NULL;
|
|
}
|
|
}
|
|
if (local)
|
|
snd_config_update_free(local);
|
|
return err;
|
|
|
|
_reread:
|
|
*_top = NULL;
|
|
*_update = NULL;
|
|
if (update) {
|
|
snd_config_update_free(update);
|
|
update = NULL;
|
|
}
|
|
if (top) {
|
|
snd_config_delete(top);
|
|
top = NULL;
|
|
}
|
|
err = snd_config_top(&top);
|
|
if (err < 0)
|
|
goto _end;
|
|
if (!local)
|
|
goto _skip;
|
|
for (k = 0; k < local->count; ++k) {
|
|
snd_input_t *in;
|
|
err = snd_input_stdio_open(&in, local->finfo[k].name, "r");
|
|
if (err >= 0) {
|
|
err = snd_config_load(top, in);
|
|
snd_input_close(in);
|
|
if (err < 0) {
|
|
SNDERR("%s may be old or corrupted: consider to remove or fix it", local->finfo[k].name);
|
|
goto _end;
|
|
}
|
|
} else {
|
|
SNDERR("cannot access file %s", local->finfo[k].name);
|
|
}
|
|
}
|
|
_skip:
|
|
err = snd_config_hooks(top, NULL);
|
|
if (err < 0) {
|
|
SNDERR("hooks failed, removing configuration");
|
|
goto _end;
|
|
}
|
|
*_top = top;
|
|
*_update = local;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* \brief Updates #snd_config by rereading the global configuration files (if needed).
|
|
* \return 0 if #snd_config was up to date, 1 if #snd_config was
|
|
* updated, otherwise a negative error code.
|
|
*
|
|
* \warning Whenever #snd_config is updated, all string pointers and
|
|
* configuration node handles previously obtained from it may become
|
|
* invalid.
|
|
* For safer operations, use #snd_config_update_ref and release the config
|
|
* via #snd_config_unref.
|
|
*
|
|
* \par Errors:
|
|
* Any errors encountered when parsing the input or returned by hooks or
|
|
* functions.
|
|
*
|
|
* \par Conforming to:
|
|
* LSB 3.2
|
|
*/
|
|
int snd_config_update(void)
|
|
{
|
|
int err;
|
|
|
|
snd_config_lock();
|
|
err = snd_config_update_r(&snd_config, &snd_config_global_update, NULL);
|
|
snd_config_unlock();
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* \brief Updates #snd_config and takes its reference.
|
|
* \return 0 if #snd_config was up to date, 1 if #snd_config was
|
|
* updated, otherwise a negative error code.
|
|
*
|
|
* Unlike #snd_config_update, this function increases a reference counter
|
|
* so that the obtained tree won't be deleted until unreferenced by
|
|
* #snd_config_unref.
|
|
*
|
|
* This function is supposed to be thread-safe.
|
|
*/
|
|
int snd_config_update_ref(snd_config_t **top)
|
|
{
|
|
int err;
|
|
|
|
if (top)
|
|
*top = NULL;
|
|
snd_config_lock();
|
|
err = snd_config_update_r(&snd_config, &snd_config_global_update, NULL);
|
|
if (err >= 0) {
|
|
if (snd_config) {
|
|
if (top) {
|
|
snd_config->refcount++;
|
|
*top = snd_config;
|
|
}
|
|
} else {
|
|
err = -ENODEV;
|
|
}
|
|
}
|
|
snd_config_unlock();
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* \brief Take the reference of the config tree.
|
|
*
|
|
* Increases a reference counter of the given config tree.
|
|
*
|
|
* This function is supposed to be thread-safe.
|
|
*/
|
|
void snd_config_ref(snd_config_t *cfg)
|
|
{
|
|
snd_config_lock();
|
|
if (cfg)
|
|
cfg->refcount++;
|
|
snd_config_unlock();
|
|
}
|
|
|
|
/**
|
|
* \brief Unreference the config tree.
|
|
*
|
|
* Decreases a reference counter of the given config tree, and eventually
|
|
* deletes the tree if all references are gone. This is the counterpart of
|
|
* #snd_config_unref.
|
|
*
|
|
* Also, the config taken via #snd_config_update_ref must be unreferenced
|
|
* by this function, too.
|
|
*
|
|
* This function is supposed to be thread-safe.
|
|
*/
|
|
void snd_config_unref(snd_config_t *cfg)
|
|
{
|
|
snd_config_lock();
|
|
if (cfg)
|
|
snd_config_delete(cfg);
|
|
snd_config_unlock();
|
|
}
|
|
|
|
/**
|
|
* \brief Frees a private update structure.
|
|
* \param[in] update The private update structure to free.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*/
|
|
int snd_config_update_free(snd_config_update_t *update)
|
|
{
|
|
unsigned int k;
|
|
|
|
assert(update);
|
|
for (k = 0; k < update->count; k++)
|
|
free(update->finfo[k].name);
|
|
free(update->finfo);
|
|
free(update);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Frees the global configuration tree in #snd_config.
|
|
* \return Zero if successful, otherwise a negative error code.
|
|
*
|
|
* This functions releases all resources of the global configuration
|
|
* tree, and sets #snd_config to \c NULL.
|
|
*
|
|
* \par Conforming to:
|
|
* LSB 3.2
|
|
*/
|
|
int snd_config_update_free_global(void)
|
|
{
|
|
snd_config_lock();
|
|
if (snd_config)
|
|
snd_config_delete(snd_config);
|
|
snd_config = NULL;
|
|
if (snd_config_global_update)
|
|
snd_config_update_free(snd_config_global_update);
|
|
snd_config_global_update = NULL;
|
|
snd_config_unlock();
|
|
/* FIXME: better to place this in another place... */
|
|
snd_dlobj_cache_cleanup();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns an iterator pointing to a node's first child.
|
|
* \param[in] config Handle to a configuration node.
|
|
* \return An iterator pointing to \a config's first child.
|
|
*
|
|
* \a config must be a compound node.
|
|
*
|
|
* The returned iterator is valid if it is not equal to the return value
|
|
* of #snd_config_iterator_end on \a config.
|
|
*
|
|
* Use #snd_config_iterator_entry to get the handle of the node pointed
|
|
* to.
|
|
*
|
|
* \par Conforming to:
|
|
* LSB 3.2
|
|
*/
|
|
snd_config_iterator_t snd_config_iterator_first(const snd_config_t *config)
|
|
{
|
|
assert(config->type == SND_CONFIG_TYPE_COMPOUND);
|
|
return config->u.compound.fields.next;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns an iterator pointing to the next sibling.
|
|
* \param[in] iterator An iterator pointing to a child configuration node.
|
|
* \return An iterator pointing to the next sibling of \a iterator.
|
|
*
|
|
* The returned iterator is valid if it is not equal to the return value
|
|
* of #snd_config_iterator_end on the node's parent.
|
|
*
|
|
* Use #snd_config_iterator_entry to get the handle of the node pointed
|
|
* to.
|
|
*
|
|
* \par Conforming to:
|
|
* LSB 3.2
|
|
*/
|
|
snd_config_iterator_t snd_config_iterator_next(const snd_config_iterator_t iterator)
|
|
{
|
|
return iterator->next;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns an iterator that ends a node's children list.
|
|
* \param[in] config Handle to a configuration node.
|
|
* \return An iterator that indicates the end of \a config's children list.
|
|
*
|
|
* \a config must be a compound node.
|
|
*
|
|
* The return value can be understood as pointing past the last child of
|
|
* \a config.
|
|
*
|
|
* \par Conforming to:
|
|
* LSB 3.2
|
|
*/
|
|
snd_config_iterator_t snd_config_iterator_end(const snd_config_t *config)
|
|
{
|
|
assert(config->type == SND_CONFIG_TYPE_COMPOUND);
|
|
return (const snd_config_iterator_t)&config->u.compound.fields;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the configuration node handle pointed to by an iterator.
|
|
* \param[in] iterator A configuration node iterator.
|
|
* \return The configuration node handle pointed to by \a iterator.
|
|
*
|
|
* \par Conforming to:
|
|
* LSB 3.2
|
|
*/
|
|
snd_config_t *snd_config_iterator_entry(const snd_config_iterator_t iterator)
|
|
{
|
|
return list_entry(iterator, snd_config_t, list);
|
|
}
|
|
|
|
#ifndef DOC_HIDDEN
|
|
typedef enum _snd_config_walk_pass {
|
|
SND_CONFIG_WALK_PASS_PRE,
|
|
SND_CONFIG_WALK_PASS_POST,
|
|
SND_CONFIG_WALK_PASS_LEAF,
|
|
} snd_config_walk_pass_t;
|
|
#endif
|
|
|
|
/* Return 1 if node needs to be attached to parent */
|
|
/* Return 2 if compound is replaced with standard node */
|
|
#ifndef DOC_HIDDEN
|
|
typedef int (*snd_config_walk_callback_t)(snd_config_t *src,
|
|
snd_config_t *root,
|
|
snd_config_t **dst,
|
|
snd_config_walk_pass_t pass,
|
|
snd_config_expand_fcn_t fcn,
|
|
void *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_expand_fcn_t fcn,
|
|
void *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, fcn, 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, fcn, 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, fcn, 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, fcn, 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_expand_fcn_t fcn ATTRIBUTE_UNUSED,
|
|
void *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, NULL);
|
|
}
|
|
|
|
static int _snd_config_expand_vars(snd_config_t **dst, const char *s, void *private_data)
|
|
{
|
|
snd_config_t *val, *vars = private_data;
|
|
if (snd_config_search(vars, s, &val) < 0) {
|
|
*dst = NULL;
|
|
return 0;
|
|
}
|
|
return snd_config_copy(dst, val);
|
|
}
|
|
|
|
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_expand_fcn_t fcn,
|
|
void *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 *vars = private_data;
|
|
snd_config_get_string(src, &s);
|
|
if (s && *s == '$') {
|
|
err = snd_config_evaluate_string(dst, s, fcn, vars);
|
|
if (err < 0)
|
|
return err;
|
|
if (*dst == NULL)
|
|
return 0;
|
|
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_expand_fcn_t fcn ATTRIBUTE_UNUSED,
|
|
void *private_data)
|
|
{
|
|
int err;
|
|
if (pass == SND_CONFIG_WALK_PASS_PRE) {
|
|
char *buf = NULL, errbuf[256];
|
|
const char *lib = NULL, *func_name = NULL;
|
|
const char *str;
|
|
int (*func)(snd_config_t **dst, snd_config_t *root,
|
|
snd_config_t *src, snd_config_t *private_data) = NULL;
|
|
void *h = NULL;
|
|
snd_config_t *c, *func_conf = NULL;
|
|
err = snd_config_search(src, "@func", &c);
|
|
if (err < 0)
|
|
return 1;
|
|
err = snd_config_get_string(c, &str);
|
|
if (err < 0) {
|
|
SNDERR("Invalid type for @func");
|
|
return err;
|
|
}
|
|
assert(str);
|
|
err = snd_config_search_definition(root, "func", str, &func_conf);
|
|
if (err >= 0) {
|
|
snd_config_iterator_t i, next;
|
|
if (snd_config_get_type(func_conf) != SND_CONFIG_TYPE_COMPOUND) {
|
|
SNDERR("Invalid type for func %s definition", str);
|
|
err = -EINVAL;
|
|
goto _err;
|
|
}
|
|
snd_config_for_each(i, next, func_conf) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
const char *id = n->id;
|
|
if (strcmp(id, "comment") == 0)
|
|
continue;
|
|
if (strcmp(id, "lib") == 0) {
|
|
err = snd_config_get_string(n, &lib);
|
|
if (err < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
goto _err;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "func") == 0) {
|
|
err = snd_config_get_string(n, &func_name);
|
|
if (err < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
goto _err;
|
|
}
|
|
continue;
|
|
}
|
|
SNDERR("Unknown field %s", id);
|
|
}
|
|
}
|
|
if (!func_name) {
|
|
int len = 9 + strlen(str) + 1;
|
|
buf = malloc(len);
|
|
if (! buf) {
|
|
err = -ENOMEM;
|
|
goto _err;
|
|
}
|
|
snprintf(buf, len, "snd_func_%s", str);
|
|
buf[len-1] = '\0';
|
|
func_name = buf;
|
|
}
|
|
h = INTERNAL(snd_dlopen)(lib, RTLD_NOW, errbuf, sizeof(errbuf));
|
|
if (h)
|
|
func = snd_dlsym(h, func_name, SND_DLSYM_VERSION(SND_CONFIG_DLSYM_VERSION_EVALUATE));
|
|
err = 0;
|
|
if (!h) {
|
|
SNDERR("Cannot open shared library %s (%s)", lib, errbuf);
|
|
err = -ENOENT;
|
|
goto _errbuf;
|
|
} else if (!func) {
|
|
SNDERR("symbol %s is not defined inside %s", func_name, lib);
|
|
snd_dlclose(h);
|
|
err = -ENXIO;
|
|
goto _errbuf;
|
|
}
|
|
_err:
|
|
if (func_conf)
|
|
snd_config_delete(func_conf);
|
|
if (err >= 0) {
|
|
snd_config_t *eval;
|
|
err = func(&eval, root, src, private_data);
|
|
if (err < 0)
|
|
SNDERR("function %s returned error: %s", func_name, snd_strerror(err));
|
|
snd_dlclose(h);
|
|
if (err >= 0 && eval)
|
|
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, NULL, private_data);
|
|
}
|
|
|
|
static int load_defaults(snd_config_t *subs, snd_config_t *defs)
|
|
{
|
|
snd_config_iterator_t d, dnext;
|
|
snd_config_for_each(d, dnext, defs) {
|
|
snd_config_t *def = snd_config_iterator_entry(d);
|
|
snd_config_iterator_t f, fnext;
|
|
if (snd_config_get_type(def) != SND_CONFIG_TYPE_COMPOUND)
|
|
continue;
|
|
snd_config_for_each(f, fnext, def) {
|
|
snd_config_t *fld = snd_config_iterator_entry(f);
|
|
const char *id = fld->id;
|
|
if (strcmp(id, "type") == 0)
|
|
continue;
|
|
if (strcmp(id, "default") == 0) {
|
|
snd_config_t *deflt;
|
|
int err;
|
|
err = snd_config_copy(&deflt, fld);
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_config_set_id(deflt, def->id);
|
|
if (err < 0) {
|
|
snd_config_delete(deflt);
|
|
return err;
|
|
}
|
|
err = snd_config_add(subs, deflt);
|
|
if (err < 0) {
|
|
snd_config_delete(deflt);
|
|
return err;
|
|
}
|
|
continue;
|
|
}
|
|
SNDERR("Unknown field %s", id);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void skip_blank(const char **ptr)
|
|
{
|
|
while (1) {
|
|
switch (**ptr) {
|
|
case ' ':
|
|
case '\f':
|
|
case '\t':
|
|
case '\n':
|
|
case '\r':
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
(*ptr)++;
|
|
}
|
|
}
|
|
|
|
static int parse_char(const char **ptr)
|
|
{
|
|
int c;
|
|
assert(**ptr == '\\');
|
|
(*ptr)++;
|
|
c = **ptr;
|
|
switch (c) {
|
|
case 'n':
|
|
c = '\n';
|
|
break;
|
|
case 't':
|
|
c = '\t';
|
|
break;
|
|
case 'v':
|
|
c = '\v';
|
|
break;
|
|
case 'b':
|
|
c = '\b';
|
|
break;
|
|
case 'r':
|
|
c = '\r';
|
|
break;
|
|
case 'f':
|
|
c = '\f';
|
|
break;
|
|
case '0': case '1': case '2': case '3':
|
|
case '4': case '5': case '6': case '7':
|
|
{
|
|
int num = c - '0';
|
|
int i = 1;
|
|
(*ptr)++;
|
|
do {
|
|
c = **ptr;
|
|
if (c < '0' || c > '7')
|
|
break;
|
|
num = num * 8 + c - '0';
|
|
i++;
|
|
(*ptr)++;
|
|
} while (i < 3);
|
|
return num;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
(*ptr)++;
|
|
return c;
|
|
}
|
|
|
|
static int parse_id(const char **ptr)
|
|
{
|
|
if (!**ptr)
|
|
return -EINVAL;
|
|
while (1) {
|
|
switch (**ptr) {
|
|
case '\f':
|
|
case '\t':
|
|
case '\n':
|
|
case '\r':
|
|
case ',':
|
|
case '=':
|
|
case '\0':
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
(*ptr)++;
|
|
}
|
|
}
|
|
|
|
static int parse_string(const char **ptr, char **val)
|
|
{
|
|
const size_t bufsize = 256;
|
|
char _buf[bufsize];
|
|
char *buf = _buf;
|
|
size_t alloc = bufsize;
|
|
char delim = **ptr;
|
|
size_t idx = 0;
|
|
(*ptr)++;
|
|
while (1) {
|
|
int c = **ptr;
|
|
switch (c) {
|
|
case '\0':
|
|
SNDERR("Unterminated string");
|
|
return -EINVAL;
|
|
case '\\':
|
|
c = parse_char(ptr);
|
|
if (c < 0) {
|
|
if (alloc > bufsize)
|
|
free(buf);
|
|
return c;
|
|
}
|
|
break;
|
|
default:
|
|
(*ptr)++;
|
|
if (c == delim) {
|
|
*val = malloc(idx + 1);
|
|
if (!*val)
|
|
return -ENOMEM;
|
|
memcpy(*val, buf, idx);
|
|
(*val)[idx] = 0;
|
|
if (alloc > bufsize)
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
}
|
|
if (idx >= alloc) {
|
|
size_t old_alloc = alloc;
|
|
alloc *= 2;
|
|
if (old_alloc == bufsize) {
|
|
buf = malloc(alloc);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
memcpy(buf, _buf, old_alloc);
|
|
} else {
|
|
char *buf2 = realloc(buf, alloc);
|
|
if (!buf2) {
|
|
free(buf);
|
|
return -ENOMEM;
|
|
}
|
|
buf = buf2;
|
|
}
|
|
}
|
|
buf[idx++] = c;
|
|
}
|
|
}
|
|
|
|
|
|
/* Parse var=val or val */
|
|
static int parse_arg(const char **ptr, unsigned int *varlen, char **val)
|
|
{
|
|
const char *str;
|
|
int err, vallen;
|
|
skip_blank(ptr);
|
|
str = *ptr;
|
|
if (*str == '"' || *str == '\'') {
|
|
err = parse_string(ptr, val);
|
|
if (err < 0)
|
|
return err;
|
|
*varlen = 0;
|
|
return 0;
|
|
}
|
|
err = parse_id(ptr);
|
|
if (err < 0)
|
|
return err;
|
|
vallen = *ptr - str;
|
|
skip_blank(ptr);
|
|
if (**ptr != '=') {
|
|
*varlen = 0;
|
|
goto _value;
|
|
}
|
|
*varlen = vallen;
|
|
(*ptr)++;
|
|
skip_blank(ptr);
|
|
str = *ptr;
|
|
if (*str == '"' || *str == '\'') {
|
|
err = parse_string(ptr, val);
|
|
if (err < 0)
|
|
return err;
|
|
return 0;
|
|
}
|
|
err = parse_id(ptr);
|
|
if (err < 0)
|
|
return err;
|
|
vallen = *ptr - str;
|
|
_value:
|
|
*val = malloc(vallen + 1);
|
|
if (!*val)
|
|
return -ENOMEM;
|
|
memcpy(*val, str, vallen);
|
|
(*val)[vallen] = 0;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* val1, val2, ...
|
|
* var1=val1,var2=val2,...
|
|
* { conf syntax }
|
|
*/
|
|
static int parse_args(snd_config_t *subs, const char *str, snd_config_t *defs)
|
|
{
|
|
int err;
|
|
int arg = 0;
|
|
if (str == NULL)
|
|
return 0;
|
|
skip_blank(&str);
|
|
if (!*str)
|
|
return 0;
|
|
if (*str == '{') {
|
|
int len = strlen(str);
|
|
snd_input_t *input;
|
|
snd_config_iterator_t i, next;
|
|
while (1) {
|
|
switch (str[--len]) {
|
|
case ' ':
|
|
case '\f':
|
|
case '\t':
|
|
case '\n':
|
|
case '\r':
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if (str[len] != '}')
|
|
return -EINVAL;
|
|
err = snd_input_buffer_open(&input, str + 1, len - 1);
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_config_load_override(subs, input);
|
|
snd_input_close(input);
|
|
if (err < 0)
|
|
return err;
|
|
snd_config_for_each(i, next, subs) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
snd_config_t *d;
|
|
const char *id = n->id;
|
|
err = snd_config_search(defs, id, &d);
|
|
if (err < 0) {
|
|
SNDERR("Unknown parameter %s", id);
|
|
return err;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
while (1) {
|
|
char buf[256];
|
|
const char *var = buf;
|
|
unsigned int varlen;
|
|
snd_config_t *def, *sub, *typ;
|
|
const char *new = str;
|
|
const char *tmp;
|
|
char *val = NULL;
|
|
|
|
sub = 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);
|
|
sub = NULL;
|
|
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:
|
|
if (sub)
|
|
snd_config_delete(sub);
|
|
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] fcn Custom function to obtain the referred variable name
|
|
* \param[in] private_data Private data node for the custom function
|
|
* \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.
|
|
*
|
|
* The new tree is not evaluated (\ref snd_config_evaluate).
|
|
*/
|
|
int snd_config_expand_custom(snd_config_t *config, snd_config_t *root,
|
|
snd_config_expand_fcn_t fcn, void *private_data,
|
|
snd_config_t **result)
|
|
{
|
|
snd_config_t *res;
|
|
int err;
|
|
|
|
err = snd_config_walk(config, root, &res, _snd_config_expand, fcn, private_data);
|
|
if (err < 0) {
|
|
SNDERR("Expand error (walk): %s", snd_strerror(err));
|
|
return err;
|
|
}
|
|
*result = res;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* \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, _snd_config_expand_vars, 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
|
|
|
|
#ifndef DOC_HIDDEN
|
|
size_t page_size(void)
|
|
{
|
|
long s = sysconf(_SC_PAGE_SIZE);
|
|
assert(s > 0);
|
|
return s;
|
|
}
|
|
|
|
size_t page_align(size_t size)
|
|
{
|
|
size_t r;
|
|
long psz = page_size();
|
|
r = size % psz;
|
|
if (r)
|
|
return size + psz - r;
|
|
return size;
|
|
}
|
|
|
|
size_t page_ptr(size_t object_offset, size_t object_size, size_t *offset, size_t *mmap_offset)
|
|
{
|
|
size_t r;
|
|
long psz = page_size();
|
|
assert(offset);
|
|
assert(mmap_offset);
|
|
*mmap_offset = object_offset;
|
|
object_offset %= psz;
|
|
*mmap_offset -= object_offset;
|
|
object_size += object_offset;
|
|
r = object_size % psz;
|
|
if (r)
|
|
r = object_size + psz - r;
|
|
else
|
|
r = object_size;
|
|
*offset = object_offset;
|
|
return r;
|
|
}
|
|
#endif /* DOC_HIDDEN */
|