OSDN Git Service

First version of configuration helpers
authorAbramo Bagnara <abramo@alsa-project.org>
Thu, 24 Aug 2000 12:49:51 +0000 (12:49 +0000)
committerAbramo Bagnara <abramo@alsa-project.org>
Thu, 24 Aug 2000 12:49:51 +0000 (12:49 +0000)
configure.in
include/Makefile.am
include/conf.h [new file with mode: 0644]
include/header.h
src/Makefile.am
src/conf/Makefile.am [new file with mode: 0644]
src/conf/conf.c [new file with mode: 0644]

index 0453ea6..a723459 100644 (file)
@@ -43,6 +43,6 @@ AC_OUTPUT(Makefile doc/Makefile include/Makefile src/Makefile \
           src/control/Makefile src/mixer/Makefile src/pcm/Makefile \
          src/pcm/plugin/Makefile src/rawmidi/Makefile src/timer/Makefile \
           src/hwdep/Makefile src/seq/Makefile src/instr/Makefile \
-          src/compat/Makefile \
+          src/compat/Makefile src/conf/Makefile \
           test/Makefile utils/Makefile \
           utils/alsa-lib.spec)
index e43434c..2761c59 100644 (file)
@@ -4,7 +4,7 @@ sysinclude_HEADERS = asoundlib.h
 # This is the order they will be concatenated into asoundlib.h!
 #
 header_files=header.h version.h error.h control.h mixer.h pcm.h rawmidi.h \
-             timer.h hwdep.h seq.h seqmid.h conv.h instr.h footer.h
+             timer.h hwdep.h seq.h seqmid.h conv.h instr.h conf.h footer.h
 
 noinst_HEADERS=$(header_files) search.h
 
diff --git a/include/conf.h b/include/conf.h
new file mode 100644 (file)
index 0000000..be686a1
--- /dev/null
@@ -0,0 +1,57 @@
+
+typedef enum {
+        SND_CONFIG_TYPE_INTEGER,
+        SND_CONFIG_TYPE_REAL,
+        SND_CONFIG_TYPE_STRING,
+       SND_CONFIG_TYPE_COMPOUND,
+} snd_config_type_t;
+
+typedef struct snd_config snd_config_t;
+
+struct snd_config {
+       char *id;
+       snd_config_type_t type;
+       union {
+               long integer;
+               char *string;
+               double real;
+               struct {
+                       struct list_head fields;
+                       int join;
+               } compound;
+       } u;
+       struct list_head list;
+       snd_config_t *father;
+};
+
+
+int snd_config_load(snd_config_t **config, FILE *fp);
+int snd_config_save(snd_config_t *config, FILE *fp);
+
+int snd_config_search(snd_config_t *config, char *key, snd_config_t **result);
+
+int snd_config_add(snd_config_t *config, snd_config_t *leaf);
+int snd_config_delete(snd_config_t *config);
+
+int snd_config_make(snd_config_t **config, char *key,
+                   snd_config_type_t type);
+
+int snd_config_integer_set(snd_config_t *config, long value);
+int snd_config_real_set(snd_config_t *config, double value);
+int snd_config_string_set(snd_config_t *config, char *value);
+int snd_config_integer_get(snd_config_t *config, long *value);
+int snd_config_real_get(snd_config_t *config, double *value);
+int snd_config_string_get(snd_config_t *config, char **value);
+
+/* One argument: long, double or char* */
+int snd_config_set(snd_config_t *config, ...);
+int snd_config_get(snd_config_t *config, void *);
+
+typedef struct list_head *snd_config_iterator_t;
+
+#define snd_config_foreach(iterator, node) \
+       assert((node)->type == SND_CONFIG_TYPE_COMPOUND); \
+       for (iterator = (node)->u.compound.fields.next; iterator != &(node)->u.compound.fields; iterator = iterator->next)
+
+#define snd_config_entry(iterator) list_entry(iterator, snd_config_t, list)
+
index ff2ceea..9353135 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  Application interface library for the ALSA driver
  *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *                   Abramo Bagnara <abramo@alsa-project.org>
  *
  *
  *   This library is free software; you can redistribute it and/or modify
index b9c1adb..c0eb002 100644 (file)
@@ -1,4 +1,4 @@
-SUBDIRS=control mixer pcm rawmidi timer hwdep seq instr compat
+SUBDIRS=control mixer pcm rawmidi timer hwdep seq instr compat conf
 COMPATNUM=@LIBTOOL_VERSION_INFO@
 
 lib_LTLIBRARIES = libasound.la
diff --git a/src/conf/Makefile.am b/src/conf/Makefile.am
new file mode 100644 (file)
index 0000000..113d747
--- /dev/null
@@ -0,0 +1,8 @@
+EXTRA_LTLIBRARIES = libconf.la
+
+libconf_la_SOURCES = conf.c
+
+all: libconf.la
+
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/src/conf/conf.c b/src/conf/conf.c
new file mode 100644 (file)
index 0000000..3d5e488
--- /dev/null
@@ -0,0 +1,863 @@
+/*
+ *  Configuration helper functions
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library General Public License as
+ *   published by the Free Software Foundation; either version 2 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 Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include "asoundlib.h"
+#include "list.h"
+
+typedef struct {
+       FILE *fp;
+       unsigned int line, column;
+       int unget;
+       int ch;
+       enum {
+               UNTERMINATED_STRING = -1,
+               UNTERMINATED_QUOTE = -2,
+               UNEXPECTED_CHAR = -3,
+               UNEXPECTED_EOF = -4,
+       } error;
+} input_t;
+
+static int get_char(input_t *input)
+{
+       int c;
+       if (input->unget) {
+               input->unget = 0;
+               return input->ch;
+       }
+       c = getc(input->fp);
+       switch (c) {
+       case '\n':
+               input->column = 0;
+               input->line++;
+               break;
+       case '\t':
+               input->column += 8 - input->column % 8;
+               break;
+       case EOF:
+               break;
+       default:
+               input->column++;
+               break;
+       }
+       return c;
+}
+
+static void unget_char(int c, input_t *input)
+{
+       assert(!input->unget);
+       input->ch = c;
+       input->unget = 1;
+}
+
+static int get_char_skip_comments(input_t *input)
+{
+       int c;
+       while (1) {
+               c = get_char(input);
+               if (c != '#')
+                       break;
+               while (1) {
+                       c = get_char(input);
+                       if (c == EOF)
+                               return c;
+                       if (c == '\n')
+                               break;
+               }
+       }
+       return c;
+}
+                       
+static int get_nonwhite(input_t *input)
+{
+       int c;
+       while (1) {
+               c = get_char_skip_comments(input);
+               switch (c) {
+               case ' ':
+               case '\f':
+               case '\t':
+               case '\n':
+               case '\r':
+                       break;
+               default:
+                       return c;
+               }
+       }
+}
+
+static int get_quotedchar(input_t *input)
+{
+       int c;
+       c = get_char(input);
+       switch (c) {
+       case 'n':
+               return '\n';
+       case 't':
+               return '\t';
+       case 'v':
+               return '\v';
+       case 'b':
+               return '\b';
+       case 'r':
+               return '\r';
+       case 'f':
+               return '\f';
+       case '0' ... '7':
+       {
+               int num = c - '0';
+               int i = 1;
+               do {
+                       c = get_char(input);
+                       if (c < '0' || c > '7') {
+                               unget_char(c, input);
+                               break;
+                       }
+                       num = num * 8 + c - '0';
+                       i++;
+               } while (i < 3);
+               return num;
+       }
+       default:
+               return c;
+       }
+}
+
+static int get_freestring(char **string, input_t *input)
+{
+       const size_t bufsize = 256;
+       char _buf[bufsize];
+       char *buf = _buf;
+       size_t alloc = bufsize;
+       size_t idx = 0;
+       int c;
+       while (1) {
+               c = get_char(input);
+               switch (c) {
+               case ' ':
+               case '\f':
+               case '\t':
+               case '\n':
+               case '\r':
+               case EOF:
+               case '.':
+               case '=':
+               case '{':
+               case '}':
+               case ',':
+               case ';':
+               case '\'':
+               case '"':
+               case '\\':
+               case '#':
+               {
+                       char *s = malloc(idx + 1);
+                       unget_char(c, input);
+                       memcpy(s, buf, idx);
+                       s[idx] = '\0';
+                       *string = s;
+                       return 0;
+               }
+               default:
+                       break;
+               }
+               if (idx >= alloc) {
+                       size_t old_alloc = alloc;
+                       alloc *= 2;
+                       if (old_alloc == bufsize) {
+                               buf = malloc(alloc);
+                               memcpy(buf, _buf, old_alloc);
+                       } else
+                               buf = realloc(buf, alloc);
+               }
+               buf[idx++] = c;
+       }
+       return 0;
+}
+                       
+static int get_delimstring(char **string, int delim, input_t *input)
+{
+       const size_t bufsize = 256;
+       char _buf[bufsize];
+       char *buf = _buf;
+       size_t alloc = bufsize;
+       size_t idx = 0;
+       int c;
+       while (1) {
+               c = get_char(input);
+               switch (c) {
+               case EOF:
+                       input->error = UNTERMINATED_STRING;
+                       return -1;
+               case '\\':
+                       c = get_quotedchar(input);
+                       if (c < 0) {
+                               input->error = UNTERMINATED_QUOTE;
+                               return -1;
+                       }
+                       break;
+               default:
+                       if (c == delim) {
+                               char *s = malloc(idx + 1);
+                               memcpy(s, buf, idx);
+                               s[idx] = '\0';
+                               *string = s;
+                               return 0;
+                       }
+               }
+               if (idx >= alloc) {
+                       size_t old_alloc = alloc;
+                       alloc *= 2;
+                       if (old_alloc == bufsize) {
+                               buf = malloc(alloc);
+                               memcpy(buf, _buf, old_alloc);
+                       } else
+                               buf = realloc(buf, alloc);
+               }
+               buf[idx++] = c;
+       }
+       return 0;
+}
+
+/* Return 1 for free string, 0 for delimited string */
+static int get_string(char **string, input_t *input)
+{
+       int c = get_nonwhite(input);
+       int err;
+       switch (c) {
+       case EOF:
+               input->error = UNEXPECTED_EOF;
+               return -1;
+       case '=':
+#if 0
+               /* I'm not sure to want unnamed fields */
+               *string = 0;
+               return 0;
+#endif
+       case '.':
+       case '{':
+       case '}':
+       case ',':
+       case ';':
+               input->error = UNEXPECTED_CHAR;
+               return -1;
+       case '\'':
+       case '"':
+               err = get_delimstring(string, c, input);
+               if (err < 0)
+                       return err;
+               return 0;
+       default:
+               unget_char(c, input);
+               err = get_freestring(string, input);
+               if (err < 0)
+                       return err;
+               return 1;
+       }
+}
+
+static int _snd_config_make(snd_config_t **config, char *id,
+                           snd_config_type_t type)
+{
+       snd_config_t *n;
+       n = calloc(1, sizeof(*n));
+       if (n == NULL) {
+               if (id)
+                       free(id);
+               return -ENOMEM;
+       }
+       n->id = id;
+       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 *father)
+{
+       snd_config_t *n;
+       int err;
+       assert(father->type == SND_CONFIG_TYPE_COMPOUND);
+       err = _snd_config_make(&n, id, type);
+       if (err < 0)
+               return err;
+       n->father = father;
+       list_add_tail(&n->list, &father->u.compound.fields);
+       *config = n;
+       return 0;
+}
+
+static int _snd_config_search(snd_config_t *config, char *id, int len, snd_config_t **result)
+{
+       snd_config_iterator_t i;
+       snd_config_foreach(i, config) {
+               snd_config_t *n = snd_config_entry(i);
+               if (len < 0) {
+                       if (strcmp(n->id, id) == 0) {
+                               *result = n;
+                               return 0;
+                       }
+               } else {
+                       if (strlen(n->id) != (size_t) len)
+                               continue;
+                       if (memcmp(n->id, id, len) == 0) {
+                               *result = n;
+                               return 0;
+                       }
+               }
+       }
+       return -ENOENT;
+}
+
+static int parse_defs(snd_config_t *father, input_t *input);
+
+static int parse_def(snd_config_t *father, input_t *input)
+{
+       char *id;
+       int c;
+       int err;
+       snd_config_t *n;
+       enum {MERGE, NOCREATE, REMOVE} mode;
+       while (1) {
+               c = get_nonwhite(input);
+               switch (c) {
+               case '?':
+                       mode = NOCREATE;
+                       break;
+               case '!':
+                       mode = REMOVE;
+                       break;
+               default:
+                       mode = MERGE;
+                       unget_char(c, input);
+               }
+               err = get_string(&id, input);
+               if (err < 0)
+                       return err;
+               c = get_nonwhite(input);
+               if (c != '.')
+                       break;
+               if (_snd_config_search(father, id, -1, &n) == 0) {
+                       if (mode != REMOVE) {
+                               if (n->type != SND_CONFIG_TYPE_COMPOUND)
+                                       return -EINVAL;
+                               n->u.compound.join = 1;
+                               father = n;
+                               free(id);
+                               continue;
+                       }
+                       snd_config_delete(n);
+               }
+               if (mode == NOCREATE) {
+                       free(id);
+                       return -ENOENT;
+               }
+               err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_COMPOUND, father);
+               if (err < 0)
+                       return err;
+               n->u.compound.join = 1;
+               father = n;
+       }
+       if (c == '=' )
+               c = get_nonwhite(input);
+       if (_snd_config_search(father, id, -1, &n) == 0) {
+               if (mode == REMOVE) {
+                       snd_config_delete(n);
+                       n = NULL;
+               }
+               else
+                       free(id);
+       } else {
+               n = NULL;
+               if (mode == NOCREATE) {
+                       free(id);
+                       return -ENOENT;
+               }
+       }
+       switch (c) {
+       case '{':
+       {
+               if (n) {
+                       if (n->type != SND_CONFIG_TYPE_COMPOUND)
+                               return -EINVAL;
+               } else {
+                       err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_COMPOUND, father);
+                       if (err < 0)
+                               return err;
+               }
+               err = parse_defs(n, input);
+               if (err < 0) {
+                       snd_config_delete(n);
+                       return err;
+               }
+               c = get_nonwhite(input);
+               if (c != '}') {
+                       snd_config_delete(n);
+                       input->error = UNEXPECTED_CHAR;
+                       return -1;
+               }
+               break;
+       }
+       default:
+       {
+               char *s;
+               unget_char(c, input);
+               err = get_string(&s, input);
+               if (err < 0)
+                       return err;
+               if (s[0] >= '0' && s[0] <= '9') {
+                       char *ptr;
+                       long i;
+                       errno = 0;
+                       i = strtol(s, &ptr, 0);
+                       if (*ptr == '.' || errno != 0) {
+                               double r;
+                               errno = 0;
+                               r = strtod(s, &ptr);
+                               if (errno == 0) {
+                                       free(s);
+                                       if (n) {
+                                               if (n->type != SND_CONFIG_TYPE_REAL)
+                                                       return -EINVAL;
+                                       } else {
+                                               err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_REAL, father);
+                                               if (err < 0)
+                                                       return err;
+                                       }
+                                       n->u.real = r;
+                                       break;
+                               }
+                       } else if (*ptr == '\0') {
+                               free(s);
+                               if (n) {
+                                       if (n->type != SND_CONFIG_TYPE_INTEGER)
+                                               return -EINVAL;
+                               } else {
+                                       err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_INTEGER, father);
+                                       if (err < 0)
+                                               return err;
+                               }
+                               n->u.integer = i;
+                               break;
+                       }
+               }
+               if (n) {
+                       if (n->type != SND_CONFIG_TYPE_STRING) {
+                               free(s);
+                               return -EINVAL;
+                       }
+               } else {
+                       err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_STRING, father);
+                       if (err < 0)
+                               return err;
+               }
+               if (n->u.string)
+                       free(n->u.string);
+               n->u.string = s;
+       }
+       }
+       c = get_nonwhite(input);
+       switch (c) {
+       case ';':
+       case ',':
+               break;
+       default:
+               unget_char(c, input);
+       }
+       return err;
+}
+               
+static int parse_defs(snd_config_t *father, input_t *input)
+{
+       while (1) {
+               int c = get_nonwhite(input);
+               int err;
+               if (c == EOF)
+                       return 0;
+               unget_char(c, input);
+               if (c == '}')
+                       return 0;
+               err = parse_def(father, input);
+               if (err < 0)
+                       return err;
+       }
+       return 0;
+}
+
+
+int snd_config_load(snd_config_t **config, FILE *fp)
+{
+       int err;
+       input_t input;
+       snd_config_t *c;
+       assert(config && fp);
+       err = _snd_config_make(&c, 0, SND_CONFIG_TYPE_COMPOUND);
+       if (err < 0)
+               return err;
+       input.fp = fp;
+       input.line = 1;
+       input.column = 0;
+       input.unget = 0;
+       err = parse_defs(c, &input);
+       if (err < 0) {
+               snd_config_delete(c);
+               return err;
+       }
+       if (get_char(&input) != EOF) {
+               snd_config_delete(c);
+               return -1;
+       }
+       *config = c;
+       return 0;
+}
+
+int snd_config_add(snd_config_t *config, snd_config_t *leaf)
+{
+       snd_config_iterator_t i;
+       assert(config && leaf);
+       snd_config_foreach(i, config) {
+               snd_config_t *n = snd_config_entry(i);
+               if (strcmp(leaf->id, n->id) == 0)
+                       return -EEXIST;
+       }
+       leaf->father = config;
+       list_add_tail(&leaf->list, &config->u.compound.fields);
+       return 0;
+}
+
+int snd_config_delete(snd_config_t *config)
+{
+       assert(config);
+       switch (config->type) {
+       case SND_CONFIG_TYPE_COMPOUND:
+       {
+               int err;
+               struct list_head *i;
+               i = config->u.compound.fields.next;
+               while (i != &config->u.compound.fields) {
+                       struct list_head *nexti = i->next;
+                       snd_config_t *leaf = snd_config_entry(i);
+                       err = snd_config_delete(leaf);
+                       if (err < 0)
+                               return err;
+                       i = nexti;
+               }
+               break;
+       }
+       case SND_CONFIG_TYPE_STRING:
+               if (config->u.string)
+                       free(config->u.string);
+               break;
+       default:
+               break;
+       }
+       if (config->father)
+               list_del(&config->list);
+       return 0;
+}
+
+int snd_config_make(snd_config_t **config, 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, id, type);
+}
+
+int snd_config_integer_set(snd_config_t *config, long value)
+{
+       assert(config->type == SND_CONFIG_TYPE_INTEGER);
+       config->u.integer = value;
+       return 0;
+}
+
+int snd_config_real_set(snd_config_t *config, double value)
+{
+       assert(config->type == SND_CONFIG_TYPE_REAL);
+       config->u.real = value;
+       return 0;
+}
+
+int snd_config_string_set(snd_config_t *config, char *value)
+{
+       assert(config);
+       assert(config->type == SND_CONFIG_TYPE_INTEGER);
+       if (config->u.string)
+               free(config->u.string);
+       config->u.string = strdup(value);
+       if (!config->u.string)
+               return -ENOMEM;
+       return 0;
+}
+
+int snd_config_set(snd_config_t *config, ...)
+{
+       va_list arg;
+       va_start(arg, config);
+       assert(config);
+       switch (config->type) {
+       case SND_CONFIG_TYPE_INTEGER:
+               config->u.integer = va_arg(arg, long);
+               break;
+       case SND_CONFIG_TYPE_REAL:
+               config->u.real = va_arg(arg, double);
+               break;
+       case SND_CONFIG_TYPE_STRING:
+               config->u.string = va_arg(arg, char *);
+               break;
+       default:
+               assert(0);
+               return -EINVAL;
+       }
+       va_end(arg);
+       return 0;
+}
+
+int snd_config_integer_get(snd_config_t *config, long *ptr)
+{
+       assert(config && ptr);
+       assert(config->type == SND_CONFIG_TYPE_INTEGER);
+       *ptr = config->u.integer;
+       return 0;
+}
+
+int snd_config_real_get(snd_config_t *config, double *ptr)
+{
+       assert(config && ptr);
+       assert(config->type == SND_CONFIG_TYPE_REAL);
+       *ptr = config->u.real;
+       return 0;
+}
+
+int snd_config_string_get(snd_config_t *config, char **ptr)
+{
+       assert(config && ptr);
+       assert(config->type == SND_CONFIG_TYPE_INTEGER);
+       *ptr = config->u.string;
+       return 0;
+}
+
+int snd_config_get(snd_config_t *config, void *ptr)
+{
+       assert(config && ptr);
+       switch (config->type) {
+       case SND_CONFIG_TYPE_INTEGER:
+               * (long*) ptr = config->u.integer;
+               break;
+       case SND_CONFIG_TYPE_REAL:
+               * (double*) ptr = config->u.real;
+               break;
+       case SND_CONFIG_TYPE_STRING:
+               * (char **) ptr = config->u.string;
+               break;
+       default:
+               assert(0);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+void quoted_print(char *str, FILE *fp)
+{
+       int quote = 0;
+       unsigned char *p = str;
+ loop:
+       switch (*p) {
+       case 0:
+               break;
+       case 1 ... 31:
+       case 127 ... 255:
+       case ' ':
+       case '=':
+       case '.':
+       case '{':
+       case '}':
+       case ';':
+       case ',':
+       case '\'':
+       case '"':
+               quote = 1;
+               break;
+       default:
+               p++;
+               goto loop;
+       }
+       if (!quote) {
+               fputs(str, fp);
+               return;
+       }
+       putc('\'', fp);
+       p = str;
+       while (*p) {
+               int c;
+               c = *p;
+               switch (c) {
+               case '\n':
+                       putc('\\', fp);
+                       putc('n', fp);
+                       break;
+               case '\t':
+                       putc('\\', fp);
+                       putc('t', fp);
+                       break;
+               case '\v':
+                       putc('\\', fp);
+                       putc('v', fp);
+                       break;
+               case '\b':
+                       putc('\\', fp);
+                       putc('b', fp);
+                       break;
+               case '\r':
+                       putc('\\', fp);
+                       putc('r', fp);
+                       break;
+               case '\f':
+                       putc('\\', fp);
+                       putc('f', fp);
+                       break;
+               case '\'':
+                       putc('\\', fp);
+                       putc(c, fp);
+                       break;
+               case 32 ... '\'' - 1:
+               case '\'' + 1 ... 126:
+                       putc(c, fp);
+                       break;
+               default:
+                       fprintf(fp, "\\%04o", c);
+                       break;
+               }
+               p++;
+       }
+       putc('\'', fp);
+}
+
+static int _snd_config_save_leaves(snd_config_t *config, FILE *fp, unsigned int level, unsigned int joins);
+
+static int _snd_config_save_leaf(snd_config_t *n, FILE *fp, 
+                                unsigned int level)
+{
+       int err;
+       unsigned int k;
+       switch (n->type) {
+       case SND_CONFIG_TYPE_INTEGER:
+               printf("%ld", n->u.integer);
+               break;
+       case SND_CONFIG_TYPE_REAL:
+               printf("%16g", n->u.real);
+               break;
+       case SND_CONFIG_TYPE_STRING:
+               quoted_print(n->u.string, fp);
+               break;
+       case SND_CONFIG_TYPE_COMPOUND:
+               putc('{', fp);
+               putc('\n', fp);
+               err = _snd_config_save_leaves(n, fp, level + 1, 0);
+               if (err < 0)
+                       return err;
+               for (k = 0; k < level; ++k) {
+                       putc('\t', fp);
+               }
+               putc('}', fp);
+               break;
+       }
+       return 0;
+}
+
+static void id_print(snd_config_t *n, FILE *fp, unsigned int joins)
+{
+       if (joins > 0) {
+               assert(n->father);
+               id_print(n->father, fp, joins - 1);
+               putc('.', fp);
+       }
+       quoted_print(n->id, fp);
+}
+
+static int _snd_config_save_leaves(snd_config_t *config, FILE *fp, unsigned int level, unsigned int joins)
+{
+       unsigned int k;
+       int err;
+       snd_config_iterator_t i;
+       assert(config && fp);
+       snd_config_foreach(i, config) {
+               snd_config_t *n = snd_config_entry(i);
+               if (n->type == SND_CONFIG_TYPE_COMPOUND &&
+                   n->u.compound.join) {
+                       err = _snd_config_save_leaves(n, fp, level, joins + 1);
+                       if (err < 0)
+                               return err;
+                       continue;
+               }
+               for (k = 0; k < level; ++k) {
+                       putc('\t', fp);
+               }
+               id_print(n, fp, joins);
+               putc(' ', fp);
+               putc('=', fp);
+               putc(' ', fp);
+               err = _snd_config_save_leaf(n, fp, level);
+               if (err < 0)
+                       return err;
+               putc(';', fp);
+               putc('\n', fp);
+       }
+       return 0;
+}
+
+int snd_config_save(snd_config_t *config, FILE *fp)
+{
+       assert(config && fp);
+       return _snd_config_save_leaves(config, fp, 0, 0);
+}
+
+int snd_config_search(snd_config_t *config, char *key, snd_config_t **result)
+{
+       assert(config && key && result);
+       while (1) {
+               snd_config_t *n;
+               int err;
+               char *p = strchr(key, '.');
+               if (p) {
+                       err = _snd_config_search(config, key, p - key, &n);
+                       if (err < 0)
+                               return err;
+                       if (n->type != SND_CONFIG_TYPE_COMPOUND)
+                               return -EINVAL;
+                       config = n;
+                       key = p + 1;
+               } else
+                       return _snd_config_search(config, key, -1, result);
+       }
+}
+