]> git.alsa-project.org Git - alsa-utils.git/commitdiff
Initial 'alsactl init' implementation
authorJaroslav Kysela <perex@perex.cz>
Thu, 31 Jul 2008 13:45:08 +0000 (15:45 +0200)
committerJaroslav Kysela <perex@perex.cz>
Thu, 31 Jul 2008 13:45:08 +0000 (15:45 +0200)
See 'man 7 alsactl_init' for more details.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
18 files changed:
.gitignore
alsactl/Makefile.am
alsactl/alsactl.c
alsactl/alsactl.h
alsactl/alsactl_init.xml [new file with mode: 0644]
alsactl/init/00main [new file with mode: 0644]
alsactl/init/hda [new file with mode: 0644]
alsactl/init/help [new file with mode: 0644]
alsactl/init/info [new file with mode: 0644]
alsactl/init/test [new file with mode: 0644]
alsactl/init_parse.c [new file with mode: 0644]
alsactl/init_sysdeps.c [new file with mode: 0644]
alsactl/init_sysfs.c [new file with mode: 0644]
alsactl/init_utils_run.c [new file with mode: 0644]
alsactl/init_utils_string.c [new file with mode: 0644]
alsactl/list.h [new file with mode: 0644]
alsactl/state.c
alsactl/utils.c [new file with mode: 0644]

index 1128fbece489f0edb947151b023dc79b587a5804..8b72d6ff5657349bf3f408b94390c47619addc83 100644 (file)
@@ -20,14 +20,17 @@ ABOUT-NLS
 *.ok
 *.gmo
 *.o
+*~
 .deps
 
 alsactl/alsactl
+alsactl/alsactl_init.7
 alsaconf/alsaconf
 alsamixer/alsamixer
 amidi/amidi
 amixer/amixer
 aplay/aplay
+aplay/arecord.1
 iecset/iecset
 seq/aconnect/aconnect
 seq/aplaymidi/aplaymidi
index d21a496e63ae4dcb96ea63151dd4a35615467bf7..93b3d79a9f7f1a4b1dc8fd51386afc09d05e6b32 100644 (file)
@@ -1,6 +1,9 @@
 sbin_PROGRAMS=alsactl
-man_MANS=alsactl.1
-EXTRA_DIST=alsactl.1
+man_MANS=alsactl.1 alsactl_init.7
+EXTRA_DIST=alsactl.1 alsactl_init.xml
 
-alsactl_SOURCES=alsactl.c state.c names.c
+alsactl_SOURCES=alsactl.c state.c names.c utils.c init_parse.c
 noinst_HEADERS=alsactl.h
+
+%.7: %.xml
+       xmlto man $?
index 78c6cb100902b6c16f4beaa61a1bc820e76f4f83..8d3987aad7fd8e15acfeffe1285214a9d81ba62b 100644 (file)
@@ -40,19 +40,25 @@ char *command;
 static void help(void)
 {
        printf("Usage: alsactl <options> command\n");
-       printf("\nAvailable options:\n");
+       printf("\nAvailable global options:\n");
        printf("  -h,--help        this help\n");
+       printf("  -d,--debug       debug mode\n");
+       printf("  -v,--version     print version of this program\n");
+       printf("\nAvailable state options:\n");
        printf("  -f,--file #      configuration file (default " SYS_ASOUNDRC " or " SYS_ASOUNDNAMES ")\n");
        printf("  -F,--force       try to restore the matching controls as much as possible\n");
        printf("                   (default mode)\n");
        printf("  -P,--pedantic    don't restore mismatching controls (old default)\n");
-       printf("  -d,--debug       debug mode\n");
-       printf("  -v,--version     print version of this program\n");
+       printf("\nAvailable init options:\n");
+       printf("  -E,--env #=#     set environment variable for init phase (NAME=VALUE)\n");
+       printf("  -i,--initfile #  main configuation file for init phase (default " DATADIR "/init/00main)\n");
+       printf("\n");
        printf("\nAvailable commands:\n");
        printf("  store   <card #> save current driver setup for one or each soundcards\n");
        printf("                   to configuration file\n");
        printf("  restore <card #> load current driver setup for one or each soundcards\n");
        printf("                   from configuration file\n");
+       printf("  init    <card #> initialize driver to a default state\n");
        printf("  names   <card #> dump information about all the known present (sub-)devices\n");
        printf("                   into configuration file (DEPRECATED)\n");
 }
@@ -63,6 +69,8 @@ int main(int argc, char *argv[])
        {
                {"help", 0, NULL, 'h'},
                {"file", 1, NULL, 'f'},
+               {"env", 1, NULL, 'E'},
+               {"initfile", 1, NULL, 'i'},
                {"force", 0, NULL, 'F'},
                {"pedantic", 0, NULL, 'P'},
                {"debug", 0, NULL, 'd'},
@@ -70,13 +78,14 @@ int main(int argc, char *argv[])
                {NULL, 0, NULL, 0},
        };
        char *cfgfile = SYS_ASOUNDRC;
+       char *initfile = DATADIR "/init/00main";
        int res;
 
        command = argv[0];
        while (1) {
                int c;
 
-               if ((c = getopt_long(argc, argv, "hf:Fdv", long_option, NULL)) < 0)
+               if ((c = getopt_long(argc, argv, "hdvf:FE:i:", long_option, NULL)) < 0)
                        break;
                switch (c) {
                case 'h':
@@ -88,6 +97,15 @@ int main(int argc, char *argv[])
                case 'F':
                        force_restore = 1;
                        break;
+               case 'E':
+                       if (putenv(optarg)) {
+                               fprintf(stderr, "environment string '%s' is wrong\n", optarg);
+                               return EXIT_FAILURE;
+                       }
+                       break;
+               case 'i':
+                       initfile = optarg;
+                       break;
                case 'P':
                        force_restore = 0;
                        break;
@@ -111,8 +129,11 @@ int main(int argc, char *argv[])
                return 0;
        }
 
-       if (!strcmp(argv[optind], "store")) {
-               res =  save_state(cfgfile,
+       if (!strcmp(argv[optind], "init")) {
+               res = init(initfile,
+                       argc - optind > 1 ? argv[optind + 1] : NULL);
+       } else if (!strcmp(argv[optind], "store")) {
+               res = save_state(cfgfile,
                   argc - optind > 1 ? argv[optind + 1] : NULL);
        } else if (!strcmp(argv[optind], "restore")) {
                res = load_state(cfgfile, 
index f1e2b41c1a7fd339aaa2a275beaae330382da0ec..51396dab6504d6df8f98d9dc4bcaea00dff116a0 100644 (file)
@@ -2,6 +2,20 @@ extern int debugflag;
 extern int force_restore;
 extern char *command;
 
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define info(...) do {\
+       fprintf(stdout, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+       fprintf(stdout, __VA_ARGS__); \
+       putc('\n', stdout); \
+} while (0)
+#else
+#define info(args...) do {\
+       fprintf(stdout, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+       fprintf(stdout, ##args); \
+       putc('\n', stdout); \
+} while (0)
+#endif 
+
 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
 #define error(...) do {\
        fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
@@ -16,7 +30,43 @@ extern char *command;
 } while (0)
 #endif 
 
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define dbg(...) do {\
+       if (!debugflag) break; \
+       fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+       fprintf(stderr, __VA_ARGS__); \
+       putc('\n', stderr); \
+} while (0)
+#else
+#define dbg(args...) do {\
+       if (!debugflag) break; \
+       fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+       fprintf(stderr, ##args); \
+       putc('\n', stderr); \
+} while (0)
+#endif 
+
+int init(const char *file, const char *cardname);
 int save_state(const char *file, const char *cardname);
 int load_state(const char *file, const char *cardname);
 int power(const char *argv[], int argc);
 int generate_names(const char *cfgfile);
+
+/* utils */
+
+int file_map(const char *filename, char **buf, size_t *bufsize);
+void file_unmap(void *buf, size_t bufsize);
+size_t line_width(const char *buf, size_t bufsize, size_t pos);
+
+static inline int hextodigit(int c)
+{
+        if (c >= '0' && c <= '9')
+                c -= '0';
+        else if (c >= 'a' && c <= 'f')
+                c = c - 'a' + 10;
+        else if (c >= 'A' && c <= 'F')
+                c = c - 'A' + 10;
+        else
+                return -1;
+        return c;
+}
diff --git a/alsactl/alsactl_init.xml b/alsactl/alsactl_init.xml
new file mode 100644 (file)
index 0000000..d4b65fd
--- /dev/null
@@ -0,0 +1,534 @@
+<?xml version='1.0'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<article>
+  <section>
+    <title>alsactl init</title>
+    <refentry>
+      <refentryinfo>
+        <title>alsactl init</title>
+        <date>July 2008</date>
+        <productname>alsactl</productname>
+      </refentryinfo>
+
+      <refmeta>
+        <refentrytitle>alsactl_init</refentrytitle>
+        <manvolnum>7</manvolnum>
+        <refmiscinfo class="version"></refmiscinfo>
+      </refmeta>
+
+      <refnamediv>
+        <refname>alsactl init</refname>
+        <refpurpose>alsa control management - initialization</refpurpose>
+      </refnamediv>
+
+      <refsect1><title>DESCRIPTION</title>
+        <para>"alsactl init" provides soundcard specific initialization.</para>
+      </refsect1>
+
+      <refsect1><title>CONFIGURATION</title>
+        <para>All "alsactl init" configuration files are placed in
+        <filename>/usr/share/alsa/init/</filename> directory. The top level
+        configuration file is <filename>/usr/share/alsa/init/00main</filename>.
+       The default top-level file can be also specified using -i or
+       --initfile parameter for the alsactl tool.
+       Every file consists of a set of lines of text. All empty lines or
+        lines beginning with '#' will be ignored.</para>
+
+        <refsect2><title>Rules files</title>
+          <para>The "alsactl init" rules are read from the files located
+          in the <filename>/usr/share/alsa/init/*</filename>. The top
+          level configuration file is <filename>/usr/share/alsa/init/00main</filename>.
+          Every line in the rules file contains at least one key value pair.
+          There are two kind of keys, match and assignment keys. If all match
+          keys are matching against its value, the rule gets applied and the
+          assign keys get the specified value assigned.</para>
+          
+          <para>A rule may consists of a list of one or more key value pairs
+          separated by a comma. Each key has a distinct operation, depending
+          on the used operator. Valid operators are:</para>
+          <variablelist>
+            <varlistentry>
+              <term><option>==</option></term>
+              <listitem>
+                <para>Compare for equality.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>!=</option></term>
+              <listitem>
+                <para>Compare for non-equality.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>=</option></term>
+              <listitem>
+                <para>Assign a value to a key. Keys that represent a list,
+                are reset and only this single value is assigned.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>+=</option></term>
+              <listitem>
+                <para>Add the value to a key that holds a list
+                of entries.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>:=</option></term>
+              <listitem>
+                <para>Assign  a  value  to  a key finally; disallow any
+                later changes, which may be used to prevent changes by
+                any later rules.</para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+
+          <para>The following key names can be used to match against device
+          properties:</para>
+          <variablelist>
+            <varlistentry>
+              <term><option>CARDINDEX</option></term>
+              <listitem>
+                <para>Match the card index of the ALSA driver.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>CTL{<replaceable>attribute</replaceable>}</option></term>
+              <listitem>
+                <para>Set or test universal control attribute. Possible
+               attributes:</para>
+                <variablelist>
+                  <varlistentry>
+                    <term><option>numid</option></term>
+                    <listitem>
+                      <para>Numeric control identification.</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>iface</option>, <option>interface</option></term>
+                    <listitem>
+                      <para>Control interface name (CARD, HWEDEP, MIXER, PCM, RAWMIDI, TIMER, SEQUENCER)</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>subdev</option>, <option>subdevice</option></term>
+                    <listitem>
+                      <para>Subdevice number.</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>name</option></term>
+                    <listitem>
+                      <para>Control name</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>index</option></term>
+                    <listitem>
+                      <para>Control index</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>type</option></term>
+                    <listitem>
+                      <para>Control type (BOOLEAN, INTEGER, INTEGER64, ENUMERATED, BYTES, IEC958)</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>attr</option>, <option>attribute</option></term>
+                    <listitem>
+                      <para>Attributes (stored in a string - use match characters * and ?):</para>
+                     <variablelist>
+                       <varlistentry>
+                         <term><option>r</option></term>
+                         <listitem>
+                           <para>control is readable</para>
+                         </listitem>
+                       </varlistentry>
+                       <varlistentry>
+                         <term><option>w</option></term>
+                         <listitem>
+                           <para>control is writable</para>
+                         </listitem>
+                       </varlistentry>
+                       <varlistentry>
+                         <term><option>v</option></term>
+                         <listitem>
+                           <para>control is volatile</para>
+                         </listitem>
+                       </varlistentry>
+                       <varlistentry>
+                         <term><option>i</option></term>
+                         <listitem>
+                           <para>control is inactive</para>
+                         </listitem>
+                       </varlistentry>
+                       <varlistentry>
+                         <term><option>l</option></term>
+                         <listitem>
+                           <para>control is locked</para>
+                         </listitem>
+                       </varlistentry>
+                       <varlistentry>
+                         <term><option>R</option></term>
+                         <listitem>
+                           <para>control is TLV readable</para>
+                         </listitem>
+                       </varlistentry>
+                       <varlistentry>
+                         <term><option>W</option></term>
+                         <listitem>
+                           <para>control is TLV writable</para>
+                         </listitem>
+                       </varlistentry>
+                       <varlistentry>
+                         <term><option>C</option></term>
+                         <listitem>
+                           <para>control is TLV commandable</para>
+                         </listitem>
+                       </varlistentry>
+                       <varlistentry>
+                         <term><option>o</option></term>
+                         <listitem>
+                           <para>process is owner of this control</para>
+                         </listitem>
+                       </varlistentry>
+                       <varlistentry>
+                         <term><option>u</option></term>
+                         <listitem>
+                           <para>control created in user space</para>
+                         </listitem>
+                       </varlistentry>
+                     </variablelist>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>owner</option></term>
+                    <listitem>
+                      <para>Control owner process PID number</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>count</option></term>
+                    <listitem>
+                      <para>Control count of values</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>min</option></term>
+                    <listitem>
+                      <para>Value range - minimum value</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>max</option></term>
+                    <listitem>
+                      <para>Value range - maximum value</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>step</option></term>
+                    <listitem>
+                      <para>Value range - step value</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>items</option></term>
+                    <listitem>
+                      <para>Enumerated value - number of text items</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>value</option></term>
+                    <listitem>
+                      <para>Value of control stored to a string delimited by
+                      comma (,).</para>
+                    </listitem>
+                  </varlistentry>
+                </variablelist>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>SYSFS_DEVICE</option></term>
+              <listitem>
+                <para>The relative path to sysfs subsystem specifying
+                the root directory of a soundcard device. Usually,
+                it should be set to "/class/sound/controlC$cardinfo{card}/device".
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>ATTR{<replaceable>filename</replaceable>}</option></term>
+              <listitem>
+                <para>Match sysfs attribute values of the soundcard device.
+                The relative path to sysfs tree must be defined by
+               SYSFS_DEVICE key. Trailing whitespace in the attribute
+               values is ignored, if the specified match value does
+               not contain trailing whitespace itself. Depending on
+               the type of operator, this key is also used to set
+               the value of a sysfs attribute.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>ENV{<replaceable>key</replaceable>}</option></term>
+              <listitem>
+                <para>Match against the value of an environment variable. Up
+                to five <option>ENV</option> keys can be specified per rule.
+                Depending on the type of operator, this key is also used
+                to export a variable to the environment.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>PROGRAM</option></term>
+              <listitem>
+                <para>Execute external program. The key is true, if
+                the program returns without exit code zero. The whole event
+                environment is available to the executed program. The
+                program's output printed to stdout is available for
+                the RESULT key.</para>
+                <para>Several buildin commands are available:</para>
+                <variablelist>
+                  <varlistentry>
+                    <term><option>__ctl_search</option></term>
+                    <listitem>
+                      <para>Search for a control. The CTL{name} key might
+                     contain match characters * and ?. An control index
+                     might be specified as first argument starting from
+                     zero (e.g. PROGRAM="__ctl_search 2").</para>
+                     </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>__ctl_count</option></term>
+                    <listitem>
+                      <para>Search for a controls and return total count
+                      of matched ones. The CTL{name} key might contain match
+                      characters * and ?.</para>
+                     </listitem>
+                  </varlistentry>
+               </variablelist>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>RESULT</option></term>
+              <listitem>
+                <para>Match the returned string of the last PROGRAM call.
+                This key can be used in the same or in any later rule
+                after a PROGRAM call.</para>
+              </listitem>
+            </varlistentry>
+
+          </variablelist>
+
+          <para>Most of the fields support a shell style pattern matching.
+          The following pattern characters are supported:</para>
+          <variablelist>
+            <varlistentry>
+              <term><option>*</option></term>
+              <listitem>
+                <para>Matches zero, or any number of characters.</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><option>?</option></term>
+              <listitem>
+                <para>Matches any single character.</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><option>[]</option></term>
+              <listitem>
+                <para>Matches any single character specified within
+                the brackets. For example, the pattern string 'tty[SR]'
+                would match either 'ttyS' or 'ttyR'. Ranges are also
+                supported within this match with the '-' character.
+               For example, to match on the range of all digits,
+               the pattern [0-9] would be used. If the first character
+               following the '[' is a '!', any characters
+                not enclosed are matched.</para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+
+          <para>The following keys can get values assigned:</para>
+          <variablelist>
+            <varlistentry>
+              <term><option>CTL{numid}</option>, <option>CTL{iface}</option>,
+                   <option>CTL{device}</option>, <option>CTL{subdev}</option>, 
+                   <option>CTL{name}</option>,  <option>CTL{index}</option>, 
+             </term>
+              <listitem>
+                <para>Select universal control element.</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><option>CTL{value}</option></term>
+              <listitem>
+                <para>Value is set (written) also to soundcard's control
+                device and RESULT key is set to errno code. The result of
+                 set operation is always true (it means continue with
+                 next key on line).</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>ENV{<replaceable>key</replaceable>}</option></term>
+              <listitem>
+                <para>Export a variable to the environment. Depending on the type of operator,
+                this key is also to match against an environment variable.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>LABEL</option></term>
+              <listitem>
+                <para>Named label where a GOTO can jump to.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>GOTO</option></term>
+              <listitem>
+                <para>Jumps to the next LABEL with a matching name</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>SYSFS_DEVICE</option></term>
+              <listitem>
+                <para>The relative path to sysfs subsystem specifying
+                the root directory of a soundcard device. Usually,
+                it should be set to "/class/sound/controlC$cardinfo{card}/device".
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>PRINT</option></term>
+              <listitem>
+                <para>PRINT value to stdout.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>ERROR</option></term>
+              <listitem>
+                <para>PRINT value to stderr.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>EXIT</option></term>
+              <listitem>
+                <para>Exit immediately and set program exit code to value (should be integer).</para>
+              </listitem>
+            </varlistentry>
+
+          </variablelist>
+
+          <para>The <option>PROGRAM</option>, <option>CTL{value}</option>,
+         <option>PRINT</option>, <option>ERROR</option>,
+         <option>EXIT</option>, <option>SYSFS_DEVICE</option>
+         fields support simple printf-like string substitutions.
+          It allows the use of the complete environment set by earlier matching
+          rules. For all other fields, substitutions are applied while the individual rule is
+          being processed. The available substitutions are:</para>
+          <variablelist>
+            <varlistentry>
+             <term><option>$cardinfo{<replaceable>attribute</replaceable>}</option>, <option>%i{<replaceable>attribute</replaceable>}</option></term>
+              <listitem>
+                <para>See CARDINFO{} for more details.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$ctl{<replaceable>attribute</replaceable>}</option>, <option>%C{<replaceable>attribute</replaceable>}</option></term>
+              <listitem>
+                <para>See CTL{} for more details.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$attr{<replaceable>file</replaceable>}</option>, <option>%s{<replaceable>file</replaceable>}</option></term>
+              <listitem>
+                <para>The value of a sysfs attribute found at the device, where
+                all keys of the rule have matched.
+                If the attribute is a symlink, the last element of the symlink target is
+                returned as the value.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$env{<replaceable>key</replaceable>}</option>, <option>%E{<replaceable>key</replaceable>}</option></term>
+              <listitem>
+                <para>The value of an environment variable.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$result</option>, <option>%c</option></term>
+              <listitem>
+                <para>The string returned by the external program requested with PROGRAM.
+                A single part of the string, separated by a space character may be selected
+                by specifying the part number as an attribute: <option>%c{N}</option>.
+                If the number is followed by the '+' char this part plus all remaining parts
+                of the result string are substituted: <option>%c{N+}</option></para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$sysfsroot</option>, <option>%r</option></term>
+              <listitem>
+                <para>Root directory where sysfs file-system is mounted.
+                Ususally, this value is just "/sys".</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>%%</option></term>
+              <listitem>
+              <para>The '%' character itself.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$$</option></term>
+              <listitem>
+              <para>The '$' character itself.</para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+          <para>The count of characters to be substituted may be limited
+          by specifying the format length value. For example, '%3s{file}'
+          will only insert the first three characters of the sysfs
+          attribute</para>
+        </refsect2>
+      </refsect1>
+
+      <refsect1><title>AUTHOR</title>
+        <para>Written by Jaroslav Kysela <email>perex@perex.cz</email></para>
+        <para>Some portions are written by Greg Kroah-Hartman <email>greg@kroah.com</email> and
+        Kay Sievers <email>kay.sievers@vrfy.org</email>.</para>
+      </refsect1>
+
+      <refsect1>
+        <title>SEE ALSO</title>
+        <para><citerefentry>
+            <refentrytitle>alsactl</refentrytitle><manvolnum>1</manvolnum>
+          </citerefentry></para>
+      </refsect1>
+    </refentry>
+  </section>
+</article>
diff --git a/alsactl/init/00main b/alsactl/init/00main
new file mode 100644 (file)
index 0000000..daac952
--- /dev/null
@@ -0,0 +1,16 @@
+# This is toplevel configuration for for 'alsactl init'.
+# See 'man alsactl_init' for syntax.
+
+# set root device directory in sysfs for soundcard for ATTR{} command
+SYSFS_DEVICE="/class/sound/controlC$cardinfo{card}/device"
+
+# test for extra commands
+ENV{CMD}=="help", INCLUDE="help", GOTO="00main_end"
+ENV{CMD}=="info", INCLUDE="info", GOTO="00main_end"
+ENV{CMD}=="test", INCLUDE="test", GOTO="00main_end"
+ENV{CMD}=="*", ERROR="Unknown command '$env{CMD}'\n", GOTO="00main_end"
+
+# include files with real configuration
+CARDINFO{driver}=="HDA-Intel", INCLUDE="hda", GOTO="00main_end"
+CARDINFO{driver}=="Test", INCLUDE="test", GOTO="00main_end"
+LABEL="00main_end"
diff --git a/alsactl/init/hda b/alsactl/init/hda
new file mode 100644 (file)
index 0000000..7621c67
--- /dev/null
@@ -0,0 +1,21 @@
+# Configuration for HDA Intel driver (High Definition Audio - Azalia)
+
+CARDINFO{mixername}=="Realtek ALC880", \
+  CARDINFO{components}=="*HDA:10ec0880 HDA:11c13026*", \
+  GOTO="Acer Travelmate 8100"
+ERROR="Unknown hardware: \"$cardinfo{mixername}\" \"$cardinfo{components}\"\n"
+ERROR="Hardware is left uninitialized"
+EXIT="99"
+
+LABEL="Acer Travelmate 8100"
+# playback
+CTL{reset}="mixer"
+CTL{name}="Headphone Playback Switch", CTL{value}="on,on"
+CTL{name}="Front Playback Volume", CTL{value}="35,35"
+CTL{name}="Front Playback Switch", CTL{value}="on,on"
+CTL{name}="PCM Playback Volume", CTL{value}="150,150"
+# capture
+CTL{name}="Input Source", CTL{value}="0"
+CTL{name}="Capture Volume", CTL{value}="65,65"
+CTL{name}="Capture Switch", CTL{value}="on,on"
+EXIT="0"
diff --git a/alsactl/init/help b/alsactl/init/help
new file mode 100644 (file)
index 0000000..60d9b1c
--- /dev/null
@@ -0,0 +1,7 @@
+# help page
+
+PRINT="Available commands (identified by the environment variable CMD):\n\n"
+PRINT="  (not set)     Do a soundcard initialization\n"
+PRINT="  help          Show this information\n"
+PRINT="  info          Print all available hardware identification\n"
+PRINT="  test          Do alsactl utility parser tests\n"
diff --git a/alsactl/init/info b/alsactl/init/info
new file mode 100644 (file)
index 0000000..a4fea19
--- /dev/null
@@ -0,0 +1,22 @@
+# show information about card
+
+PRINT="CARDINFO:\n"
+PRINT="  CARDINFO{id}=\"$CARDINFO{id}\"\n"
+PRINT="  CARDINFO{card}=\"$CARDINFO{card}\"\n"
+PRINT="  CARDINFO{driver}=\"$CARDINFO{driver}\"\n"
+PRINT="  CARDINFO{name}=\"$CARDINFO{name}\"\n"
+PRINT="  CARDINFO{longname}=\"$CARDINFO{longname}\"\n"
+PRINT="  CARDINFO{mixername}=\"$CARDINFO{mixername}\"\n"
+PRINT="  CARDINFO{components}=\"$CARDINFO{components}\"\n"
+
+# sysfs stuff
+PRINT="sysfs:\n"
+ATTR{bus}=="*", PRINT="  ATTR{bus}=\"$ATTR{bus}\"\n"
+ATTR{class}=="*", PRINT="  ATTR{class}=\"$ATTR{class}\"\n"
+ATTR{driver}=="*", PRINT="  ATTR{driver}=\"$ATTR{driver}\"\n"
+ATTR{vendor}=="*", PRINT="  ATTR{vendor}=\"$ATTR{vendor}\"\n"
+ATTR{device}=="*", PRINT="  ATTR{device}=\"$ATTR{device}\"\n"
+ATTR{subsystem_vendor}=="*", \
+  PRINT="  ATTR{subsystem_vendor}=\"$ATTR{subsystem_vendor}\"\n"
+ATTR{subsystem_device}=="*", \
+  PRINT="  ATTR{subsystem_device}=\"$ATTR{subsystem_device}\"\n"
diff --git a/alsactl/init/test b/alsactl/init/test
new file mode 100644 (file)
index 0000000..d1f4f11
--- /dev/null
@@ -0,0 +1,256 @@
+# Test code
+# Just for debugging purposes
+
+PRINT="Default CTL:\n"
+PRINT="  CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="  CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="  CTL{device}=\"$ctl{device}\"\n"
+PRINT="  CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="  CTL{name}=\"$ctl{name}\"\n"
+PRINT="  CTL{index}=\"$ctl{index}\"\n"
+
+CTL{reset}="mixer"
+
+PRINT="After CTL{reset}=\"mixer\":\n"
+PRINT="  CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="  CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="  CTL{device}=\"$ctl{device}\"\n"
+PRINT="  CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="  CTL{name}=\"$ctl{name}\"\n"
+PRINT="  CTL{index}=\"$ctl{index}\"\n"
+
+CTL{numid}="987"
+CTL{iface}="sequencer"
+CTL{device}="10"
+CTL{subdevice}="20"
+CTL{name}="Just Test"
+CTL{index}="999"
+
+PRINT="After test sequence:\n"
+PRINT="  CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="  CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="  CTL{device}=\"$ctl{device}\"\n"
+PRINT="  CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="  CTL{name}=\"$ctl{name}\"\n"
+PRINT="  CTL{index}=\"$ctl{index}\"\n"
+
+ERROR="Ignore following error:\n  "
+PROGRAM="__just_test"
+
+PRINT="__ctl_count test:\n"
+CTL{search}="mixer", CTL{name}="*Switch*", PROGRAM="__ctl_count", \
+  PRINT="  *Switch* count result: $result\n"
+
+PRINT="__ctl_search test:\n"
+CTL{search}="mixer", CTL{name}="*Switch*", PROGRAM!="__ctl_search", GOTO="skip_switch_search"
+PRINT="  *Switch 0* search result: $result\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+CTL{search}="mixer", CTL{name}="*Switch*", PROGRAM!="__ctl_search 1", GOTO="skip_switch_search"
+PRINT="  *Switch 1* search result: $result\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+LABEL="skip_switch_search"
+
+PRINT="First ten elements:\n"
+CTL{search}="mixer", CTL{name}="*", PROGRAM!="__ctl_search 0", GOTO="skip_first_ten_search"
+PRINT="  Element #0:\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+PRINT="    CTL{type}=\"$ctl{type}\"\n"
+PRINT="    CTL{attr}=\"$ctl{attr}\"\n"
+PRINT="    CTL{owner}=\"$ctl{owner}\"\n"
+PRINT="    CTL{count}=\"$ctl{count}\"\n"
+PRINT="    CTL{min}=\"$ctl{min}\"\n"
+PRINT="    CTL{max}=\"$ctl{max}\"\n"
+PRINT="    CTL{step}=\"$ctl{step}\"\n"
+PRINT="    CTL{items}=\"$ctl{items}\"\n"
+PRINT="    CTL{value}=\"$ctl{value}\"\n"
+CTL{search}="mixer", CTL{name}="*", PROGRAM!="__ctl_search 1", GOTO="skip_first_ten_search"
+PRINT="  Element #1:\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+PRINT="    CTL{type}=\"$ctl{type}\"\n"
+PRINT="    CTL{attr}=\"$ctl{attr}\"\n"
+PRINT="    CTL{owner}=\"$ctl{owner}\"\n"
+PRINT="    CTL{count}=\"$ctl{count}\"\n"
+PRINT="    CTL{min}=\"$ctl{min}\"\n"
+PRINT="    CTL{max}=\"$ctl{max}\"\n"
+PRINT="    CTL{step}=\"$ctl{step}\"\n"
+PRINT="    CTL{items}=\"$ctl{items}\"\n"
+PRINT="    CTL{value}=\"$ctl{value}\"\n"
+CTL{search}="mixer", CTL{name}="*", PROGRAM!="__ctl_search 2", GOTO="skip_first_ten_search"
+PRINT="  Element #2:\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+PRINT="    CTL{type}=\"$ctl{type}\"\n"
+PRINT="    CTL{attr}=\"$ctl{attr}\"\n"
+PRINT="    CTL{owner}=\"$ctl{owner}\"\n"
+PRINT="    CTL{count}=\"$ctl{count}\"\n"
+PRINT="    CTL{min}=\"$ctl{min}\"\n"
+PRINT="    CTL{max}=\"$ctl{max}\"\n"
+PRINT="    CTL{step}=\"$ctl{step}\"\n"
+PRINT="    CTL{items}=\"$ctl{items}\"\n"
+PRINT="    CTL{value}=\"$ctl{value}\"\n"
+LABEL="skip_first_ten_search"
+CTL{search}="mixer", CTL{name}="*", PROGRAM!="__ctl_search 3", GOTO="skip_first_ten_search"
+PRINT="  Element #3:\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+PRINT="    CTL{type}=\"$ctl{type}\"\n"
+PRINT="    CTL{attr}=\"$ctl{attr}\"\n"
+PRINT="    CTL{owner}=\"$ctl{owner}\"\n"
+PRINT="    CTL{count}=\"$ctl{count}\"\n"
+PRINT="    CTL{min}=\"$ctl{min}\"\n"
+PRINT="    CTL{max}=\"$ctl{max}\"\n"
+PRINT="    CTL{step}=\"$ctl{step}\"\n"
+PRINT="    CTL{items}=\"$ctl{items}\"\n"
+PRINT="    CTL{value}=\"$ctl{value}\"\n"
+LABEL="skip_first_ten_search"
+CTL{search}="mixer", CTL{name}="*", PROGRAM!="__ctl_search 4", GOTO="skip_first_ten_search"
+PRINT="  Element #4:\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+PRINT="    CTL{type}=\"$ctl{type}\"\n"
+PRINT="    CTL{attr}=\"$ctl{attr}\"\n"
+PRINT="    CTL{owner}=\"$ctl{owner}\"\n"
+PRINT="    CTL{count}=\"$ctl{count}\"\n"
+PRINT="    CTL{min}=\"$ctl{min}\"\n"
+PRINT="    CTL{max}=\"$ctl{max}\"\n"
+PRINT="    CTL{step}=\"$ctl{step}\"\n"
+PRINT="    CTL{items}=\"$ctl{items}\"\n"
+PRINT="    CTL{value}=\"$ctl{value}\"\n"
+LABEL="skip_first_ten_search"
+CTL{search}="mixer", CTL{name}="*", PROGRAM!="__ctl_search 5", GOTO="skip_first_ten_search"
+PRINT="  Element #5:\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+PRINT="    CTL{type}=\"$ctl{type}\"\n"
+PRINT="    CTL{attr}=\"$ctl{attr}\"\n"
+PRINT="    CTL{owner}=\"$ctl{owner}\"\n"
+PRINT="    CTL{count}=\"$ctl{count}\"\n"
+PRINT="    CTL{min}=\"$ctl{min}\"\n"
+PRINT="    CTL{max}=\"$ctl{max}\"\n"
+PRINT="    CTL{step}=\"$ctl{step}\"\n"
+PRINT="    CTL{items}=\"$ctl{items}\"\n"
+PRINT="    CTL{value}=\"$ctl{value}\"\n"
+LABEL="skip_first_ten_search"
+CTL{search}="mixer", CTL{name}="*", PROGRAM!="__ctl_search 6", GOTO="skip_first_ten_search"
+PRINT="  Element #6:\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+PRINT="    CTL{type}=\"$ctl{type}\"\n"
+PRINT="    CTL{attr}=\"$ctl{attr}\"\n"
+PRINT="    CTL{owner}=\"$ctl{owner}\"\n"
+PRINT="    CTL{count}=\"$ctl{count}\"\n"
+PRINT="    CTL{min}=\"$ctl{min}\"\n"
+PRINT="    CTL{max}=\"$ctl{max}\"\n"
+PRINT="    CTL{step}=\"$ctl{step}\"\n"
+PRINT="    CTL{items}=\"$ctl{items}\"\n"
+PRINT="    CTL{value}=\"$ctl{value}\"\n"
+LABEL="skip_first_ten_search"
+CTL{search}="mixer", CTL{name}="*", PROGRAM!="__ctl_search 7", GOTO="skip_first_ten_search"
+PRINT="  Element #7:\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+PRINT="    CTL{type}=\"$ctl{type}\"\n"
+PRINT="    CTL{attr}=\"$ctl{attr}\"\n"
+PRINT="    CTL{owner}=\"$ctl{owner}\"\n"
+PRINT="    CTL{count}=\"$ctl{count}\"\n"
+PRINT="    CTL{min}=\"$ctl{min}\"\n"
+PRINT="    CTL{max}=\"$ctl{max}\"\n"
+PRINT="    CTL{step}=\"$ctl{step}\"\n"
+PRINT="    CTL{items}=\"$ctl{items}\"\n"
+PRINT="    CTL{value}=\"$ctl{value}\"\n"
+LABEL="skip_first_ten_search"
+CTL{search}="mixer", CTL{name}="*", PROGRAM!="__ctl_search 8", GOTO="skip_first_ten_search"
+PRINT="  Element #8:\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+PRINT="    CTL{type}=\"$ctl{type}\"\n"
+PRINT="    CTL{attr}=\"$ctl{attr}\"\n"
+PRINT="    CTL{owner}=\"$ctl{owner}\"\n"
+PRINT="    CTL{count}=\"$ctl{count}\"\n"
+PRINT="    CTL{min}=\"$ctl{min}\"\n"
+PRINT="    CTL{max}=\"$ctl{max}\"\n"
+PRINT="    CTL{step}=\"$ctl{step}\"\n"
+PRINT="    CTL{items}=\"$ctl{items}\"\n"
+PRINT="    CTL{value}=\"$ctl{value}\"\n"
+LABEL="skip_first_ten_search"
+CTL{search}="mixer", CTL{name}="*", PROGRAM!="__ctl_search 9", GOTO="skip_first_ten_search"
+PRINT="  Element #9:\n"
+PRINT="    CTL{numid}=\"$ctl{numid}\"\n"
+PRINT="    CTL{iface}=\"$ctl{iface}\"\n"
+PRINT="    CTL{device}=\"$ctl{device}\"\n"
+PRINT="    CTL{subdevice}=\"$ctl{subdevice}\"\n"
+PRINT="    CTL{name}=\"$ctl{name}\"\n"
+PRINT="    CTL{index}=\"$ctl{index}\"\n"
+PRINT="    CTL{type}=\"$ctl{type}\"\n"
+PRINT="    CTL{attr}=\"$ctl{attr}\"\n"
+PRINT="    CTL{owner}=\"$ctl{owner}\"\n"
+PRINT="    CTL{count}=\"$ctl{count}\"\n"
+PRINT="    CTL{min}=\"$ctl{min}\"\n"
+PRINT="    CTL{max}=\"$ctl{max}\"\n"
+PRINT="    CTL{step}=\"$ctl{step}\"\n"
+PRINT="    CTL{items}=\"$ctl{items}\"\n"
+PRINT="    CTL{value}=\"$ctl{value}\"\n"
+LABEL="skip_first_ten_search"
+
+PRINT="Elements write test #1:\n", \
+  CTL{search}="mixer", CTL{name}="Front Playback Switch", \
+  PROGRAM="__ctl_search", CTL{value}="on,on", \
+  PRINT="  result=$result\n"
+PRINT="Elements write test #2:\n", \
+  CTL{search}="mixer", CTL{name}="Front Playback Volume", \
+  PROGRAM="__ctl_search", CTL{value}="32,32", \
+  PRINT="  result=$result\n"
+PRINT="Elements write test #3:\n", \
+  CTL{search}="mixer", CTL{name}="Front Playback Volume Error", \
+  PROGRAM="__ctl_search", CTL{value}="32,32", \
+  PRINT="  result=$result\n"
+
+PRINT="\nAll tests done..\n"
diff --git a/alsactl/init_parse.c b/alsactl/init_parse.c
new file mode 100644 (file)
index 0000000..542ee74
--- /dev/null
@@ -0,0 +1,1512 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program - Parse initialization files
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ *                  Greg Kroah-Hartman <greg@kroah.com>,
+ *                  Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU 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 General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/select.h>
+#include <alsa/asoundlib.h>
+#include "aconfig.h"
+#include "alsactl.h"
+#include "list.h"
+
+#define PATH_SIZE      512
+#define NAME_SIZE      128
+
+enum key_op {
+       KEY_OP_UNSET,
+       KEY_OP_MATCH,
+       KEY_OP_NOMATCH,
+       KEY_OP_ADD,
+       KEY_OP_ASSIGN,
+       KEY_OP_ASSIGN_FINAL
+};
+
+struct pair {
+       char *key;
+       char *value;
+       struct pair *next;
+};
+
+struct space {
+       struct pair *pairs;
+       char *rootdir;
+       char *go_to;
+       char *program_result;
+       const char *filename;
+       int linenum;
+       int log_run;
+       int exit_code;
+       int quit;
+       unsigned int ctl_id_changed;
+       snd_hctl_t *ctl_handle;
+       snd_ctl_card_info_t *ctl_card_info;
+       snd_ctl_elem_id_t *ctl_id;
+       snd_ctl_elem_info_t *ctl_info;
+       snd_ctl_elem_value_t *ctl_value;
+};
+
+static void Perror(struct space *space, const char *fmt, ...)
+{
+       va_list arg;
+       va_start(arg, fmt);
+       fprintf(stderr, "%s:%i: ", space->filename, space->linenum);
+       vfprintf(stderr, fmt, arg);
+       putc('\n', stderr);
+       va_end(arg);
+}
+
+#include "init_sysdeps.c"
+#include "init_utils_string.c"
+#include "init_utils_run.c"
+#include "init_sysfs.c"
+
+static void free_space(struct space *space)
+{
+       struct pair *pair = space->pairs;
+       struct pair *next = pair;
+
+       while (next) {
+               pair = next;
+               next = pair->next;
+               free(pair->value);
+               free(pair->key);
+               free(pair);
+       }
+       space->pairs = NULL;
+       if (space->ctl_value) {
+               snd_ctl_elem_value_free(space->ctl_value);
+               space->ctl_value = NULL;
+       }
+       if (space->ctl_info) {
+               snd_ctl_elem_info_free(space->ctl_info);
+               space->ctl_info = NULL;
+       }
+       if (space->ctl_id) {
+               snd_ctl_elem_id_free(space->ctl_id);
+               space->ctl_id = NULL;
+       }
+       if (space->ctl_card_info) {
+               snd_ctl_card_info_free(space->ctl_card_info);
+               space->ctl_card_info = NULL;
+       }
+       if (space->ctl_handle) {
+               free(space->ctl_handle);
+               space->ctl_handle = NULL;
+       }
+       if (space->rootdir)
+               free(space->rootdir);
+       if (space->program_result)
+               free(space->program_result);
+}
+
+struct pair *value_find(struct space *space, const char *key)
+{
+       struct pair *pair = space->pairs;
+       
+       while (pair && strcmp(pair->key, key) != 0)
+               pair = pair->next;
+       return pair;
+}
+
+static int value_set(struct space *space, const char *key, const char *value)
+{
+       struct pair *pair;
+       
+       pair = value_find(space, key);
+       if (pair) {
+               free(pair->value);
+               pair->value = strdup(value);
+               if (pair->value == NULL)
+                       return -ENOMEM;
+       } else {
+               pair = malloc(sizeof(struct pair));
+               if (pair == NULL)
+                       return -ENOMEM;
+               pair->key = strdup(key);
+               if (pair->key == NULL) {
+                       free(pair);
+                       return -ENOMEM;
+               }
+               pair->value = strdup(value);
+               if (pair->value == NULL) {
+                       free(pair->key);
+                       free(pair);
+                       return -ENOMEM;
+               }
+               pair->next = space->pairs;
+               space->pairs = pair;
+       }
+       return 0;
+}
+
+static int init_space(struct space **space, int card)
+{
+       struct space *res;
+       char device[16];
+       int err;
+
+       res = calloc(1, sizeof(struct space));
+       if (res == NULL)
+               return -ENOMEM;
+       res->ctl_id_changed = ~0;
+       res->linenum = -1;
+       sprintf(device, "hw:%u", card);
+       err = snd_hctl_open(&res->ctl_handle, device, 0);
+       if (err < 0)
+               goto error;
+       err = snd_hctl_load(res->ctl_handle);
+       if (err < 0)
+               goto error;
+       err = snd_ctl_card_info_malloc(&res->ctl_card_info);
+       if (err < 0)
+               goto error;
+       err = snd_ctl_card_info(snd_hctl_ctl(res->ctl_handle), res->ctl_card_info);
+       if (err < 0)
+               goto error;
+       err = snd_ctl_elem_id_malloc(&res->ctl_id);
+       if (err < 0)
+               goto error;
+       err = snd_ctl_elem_info_malloc(&res->ctl_info);
+       if (err < 0)
+               goto error;
+       err = snd_ctl_elem_value_malloc(&res->ctl_value);
+       if (err < 0)
+               goto error;
+       *space = res;
+       return 0;
+ error:
+       free_space(res);
+       return err;
+}
+
+static const char *cardinfo_get(struct space *space, const char *attr)
+{
+       if (strncasecmp(attr, "CARD", 4) == 0) {
+               static char res[16];
+               sprintf(res, "%u", snd_ctl_card_info_get_card(space->ctl_card_info));
+               return res;
+       }
+       if (strncasecmp(attr, "ID", 2) == 0)
+               return snd_ctl_card_info_get_id(space->ctl_card_info);
+       if (strncasecmp(attr, "DRIVER", 6) == 0)
+               return snd_ctl_card_info_get_driver(space->ctl_card_info);
+       if (strncasecmp(attr, "NAME", 4) == 0)
+               return snd_ctl_card_info_get_name(space->ctl_card_info);
+       if (strncasecmp(attr, "LONGNAME", 8) == 0)
+               return snd_ctl_card_info_get_longname(space->ctl_card_info);
+       if (strncasecmp(attr, "MIXERNAME", 9) == 0)
+               return snd_ctl_card_info_get_mixername(space->ctl_card_info);
+       if (strncasecmp(attr, "COMPONENTS", 10) == 0)
+               return snd_ctl_card_info_get_components(space->ctl_card_info);
+       Perror(space, "unknown cardinfo{} attribute '%s'", attr);
+       return NULL;
+}
+
+static int check_id_changed(struct space *space, unsigned int what)
+{
+       snd_hctl_elem_t *elem;
+       int err;
+
+       if ((space->ctl_id_changed & what & 1) != 0) {
+               snd_ctl_elem_id_set_numid(space->ctl_id, 0);
+               elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
+               if (!elem)
+                       return -ENOENT;
+               err = snd_hctl_elem_info(elem, space->ctl_info);
+               if (err == 0)
+                       space->ctl_id_changed &= ~1;
+               return err;
+       }
+       if ((space->ctl_id_changed & what & 2) != 0) {
+               snd_ctl_elem_id_set_numid(space->ctl_id, 0);
+               elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
+               if (!elem)
+                       return -ENOENT;
+               err = snd_hctl_elem_read(elem, space->ctl_value);
+               if (err == 0)
+                       space->ctl_id_changed &= ~2;
+               return err;
+       }
+       return 0;
+}
+
+static const char *get_ctl_value(struct space *space)
+{
+       snd_ctl_elem_type_t type;
+       unsigned int idx, count;
+       static char res[1024], tmp[16];
+       static const char *hex = "0123456789abcdef";
+       char *pos;
+       const char *pos1;
+
+       type = snd_ctl_elem_info_get_type(space->ctl_info);
+       count = snd_ctl_elem_info_get_count(space->ctl_info);
+       res[0] = '\0';
+       switch (type) {
+       case SND_CTL_ELEM_TYPE_BOOLEAN:
+               for (idx = 0; idx < count; idx++) {
+                       if (idx > 0)
+                               strlcat(res, ",", sizeof(res));
+                       strlcat(res, snd_ctl_elem_value_get_boolean(space->ctl_value, idx) ? "on" : "off", sizeof(res));
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER:
+               for (idx = 0; idx < count; idx++) {
+                       if (idx > 0)
+                               strlcat(res, ",", sizeof(res));
+                       snprintf(tmp, sizeof(tmp), "%li", snd_ctl_elem_value_get_integer(space->ctl_value, idx));
+                       strlcat(res, tmp, sizeof(res));
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER64:
+               for (idx = 0; idx < count; idx++) {
+                       if (idx > 0)
+                               strlcat(res, ",", sizeof(res));
+                       snprintf(tmp, sizeof(tmp), "%lli", snd_ctl_elem_value_get_integer64(space->ctl_value, idx));
+                       strlcat(res, tmp, sizeof(res));
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_ENUMERATED:
+               for (idx = 0; idx < count; idx++) {
+                       if (idx > 0)
+                               strlcat(res, ",", sizeof(res));
+                       snprintf(tmp, sizeof(tmp), "%u", snd_ctl_elem_value_get_enumerated(space->ctl_value, idx));
+                       strlcat(res, tmp, sizeof(res));
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_BYTES:
+       case SND_CTL_ELEM_TYPE_IEC958:
+               if (type == SND_CTL_ELEM_TYPE_IEC958)
+                       count = sizeof(snd_aes_iec958_t);
+               if (count > (sizeof(res)-1)/2)
+                       count = (sizeof(res)-1/2);
+               pos = res;
+               pos1 = snd_ctl_elem_value_get_bytes(space->ctl_value);
+               while (count > 0) {
+                       idx = *pos1++;
+                       *pos++ = hex[idx >> 4];
+                       *pos++ = hex[idx & 0x0f];
+                       count++;
+               }
+               *pos++ = '\0';
+               break;
+       default:
+               Perror(space, "unknown element type '%i'", type);
+               return NULL;
+       }
+       return res;
+}
+
+static int set_ctl_value(struct space *space, const char *value)
+{
+       snd_ctl_elem_type_t type;
+       unsigned int idx, count;
+       const char *pos;
+       int val;
+
+       type = snd_ctl_elem_info_get_type(space->ctl_info);
+       count = snd_ctl_elem_info_get_count(space->ctl_info);
+       switch (type) {
+       case SND_CTL_ELEM_TYPE_BOOLEAN:
+               for (idx = 0; idx < count; idx++) {
+                       while (*value == ' ')
+                               value++;
+                       if (*value == '\0')
+                               goto missing;
+                       val = strncasecmp(value, "true", 4) == 0 ||
+                               strncasecmp(value, "yes", 3) == 0 ||
+                               strncasecmp(value, "on", 2) == 0 ||
+                               strncasecmp(value, "1", 1) == 0;
+                       snd_ctl_elem_value_set_boolean(space->ctl_value, idx, val);
+                       pos = strchr(value, ',');
+                       value = pos ? pos + 1 : value + strlen(value) - 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER:
+               for (idx = 0; idx < count; idx++) {
+                       while (*value == ' ')
+                               value++;
+                       snd_ctl_elem_value_set_integer(space->ctl_value, idx, strtol(value, NULL, 0));
+                       pos = strchr(value, ',');
+                       value = pos ? pos + 1 : value + strlen(value) - 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_INTEGER64:
+               for (idx = 0; idx < count; idx++) {
+                       while (*value == ' ')
+                               value++;
+                       snd_ctl_elem_value_set_integer64(space->ctl_value, idx, strtoll(value, NULL, 0));
+                       pos = strchr(value, ',');
+                       value = pos ? pos + 1 : value + strlen(value) - 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_ENUMERATED:
+               for (idx = 0; idx < count; idx++) {
+                       while (*value == ' ')
+                               value++;
+                       snd_ctl_elem_value_set_enumerated(space->ctl_value, idx, strtol(value, NULL, 0));
+                       pos = strchr(value, ',');
+                       value = pos ? pos + 1 : value + strlen(value) - 1;
+               }
+               break;
+       case SND_CTL_ELEM_TYPE_BYTES:
+       case SND_CTL_ELEM_TYPE_IEC958:
+               if (type == SND_CTL_ELEM_TYPE_IEC958)
+                       count = sizeof(snd_aes_iec958_t);
+               while (*value == ' ')
+                       value++;
+               if (strlen(value) != count * 2) {
+                       Perror(space, "bad ctl value hexa length (should be %u bytes, line %i)", count, space->linenum);
+                       return -EINVAL;
+               }
+               for (idx = 0; idx < count; idx += 2) {
+                       val = hextodigit(*(value++)) << 4;
+                       val |= hextodigit(*(value++));
+                       if (val > 255) {
+                               Perror(space, "bad ctl hexa value (line %i)", space->linenum);
+                               return -EINVAL;
+                       }
+                       snd_ctl_elem_value_set_byte(space->ctl_value, idx, val);
+               }
+               break;
+       default:
+               Perror(space, "unknown element type '%i'", type);
+               return -EINVAL;
+       }
+       return 0;
+  missing:
+       printf("%i %i\n", type, count);
+       Perror(space, "missing some ctl values (line %i)", space->linenum);
+       return -EINVAL;
+}
+
+static const char *elemid_get(struct space *space, const char *attr)
+{
+       long long val;
+       snd_ctl_elem_type_t type;
+       static char res[16];
+
+       if (strncasecmp(attr, "numid", 5) == 0) {
+               val = snd_ctl_elem_id_get_numid(space->ctl_id);
+               goto value;
+       }
+       if (strncasecmp(attr, "iface", 5) == 0 ||
+           strncasecmp(attr, "interface", 9) == 0)
+               return snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(space->ctl_id));
+       if (strncasecmp(attr, "device", 6) == 0) {
+               val = snd_ctl_elem_id_get_device(space->ctl_id);
+               goto value;
+       }
+       if (strncasecmp(attr, "subdev", 6) == 0) {
+               val = snd_ctl_elem_id_get_subdevice(space->ctl_id);
+               goto value;
+       }
+       if (strncasecmp(attr, "name", 4) == 0)
+               return snd_ctl_elem_id_get_name(space->ctl_id);
+       if (strncasecmp(attr, "index", 5) == 0) {
+               val = snd_ctl_elem_id_get_index(space->ctl_id);
+               goto value;
+       }
+       if (strncasecmp(attr, "type", 4) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               return snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(space->ctl_info));
+       }
+       if (strncasecmp(attr, "attr", 4) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               res[0] = '\0';
+               if (snd_ctl_elem_info_is_readable(space->ctl_info))
+                       strcat(res, "r");
+               if (snd_ctl_elem_info_is_writable(space->ctl_info))
+                       strcat(res, "w");
+               if (snd_ctl_elem_info_is_volatile(space->ctl_info))
+                       strcat(res, "v");
+               if (snd_ctl_elem_info_is_inactive(space->ctl_info))
+                       strcat(res, "i");
+               if (snd_ctl_elem_info_is_locked(space->ctl_info))
+                       strcat(res, "l");
+               if (snd_ctl_elem_info_is_tlv_readable(space->ctl_info))
+                       strcat(res, "R");
+               if (snd_ctl_elem_info_is_tlv_writable(space->ctl_info))
+                       strcat(res, "W");
+               if (snd_ctl_elem_info_is_tlv_commandable(space->ctl_info))
+                       strcat(res, "C");
+               if (snd_ctl_elem_info_is_owner(space->ctl_info))
+                       strcat(res, "o");
+               if (snd_ctl_elem_info_is_user(space->ctl_info))
+                       strcat(res, "u");
+               return res;
+       }
+       if (strncasecmp(attr, "owner", 5) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               val = snd_ctl_elem_info_get_owner(space->ctl_info);
+               goto value;
+       }
+       if (strncasecmp(attr, "count", 5) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               val = snd_ctl_elem_info_get_count(space->ctl_info);
+               goto value;
+       }
+       if (strncasecmp(attr, "min", 3) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               type = snd_ctl_elem_info_get_type(space->ctl_info);
+               if (type == SND_CTL_ELEM_TYPE_INTEGER64)
+                       val = snd_ctl_elem_info_get_min64(space->ctl_info);
+               else if (type == SND_CTL_ELEM_TYPE_INTEGER)
+                       val = snd_ctl_elem_info_get_min(space->ctl_info);
+               else
+                       goto empty;
+               goto value;
+       }
+       if (strncasecmp(attr, "max", 3) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               type = snd_ctl_elem_info_get_type(space->ctl_info);
+               if (type == SND_CTL_ELEM_TYPE_INTEGER64)
+                       val = snd_ctl_elem_info_get_max64(space->ctl_info);
+               else if (type == SND_CTL_ELEM_TYPE_INTEGER)
+                       val = snd_ctl_elem_info_get_max(space->ctl_info);
+               else
+                       goto empty;
+               goto value;
+       }
+       if (strncasecmp(attr, "step", 3) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               type = snd_ctl_elem_info_get_type(space->ctl_info);
+               if (type == SND_CTL_ELEM_TYPE_INTEGER64)
+                       val = snd_ctl_elem_info_get_step64(space->ctl_info);
+               else if (type == SND_CTL_ELEM_TYPE_INTEGER)
+                       val = snd_ctl_elem_info_get_step(space->ctl_info);
+               else
+                       goto empty;
+               goto value;
+       }
+       if (strncasecmp(attr, "items", 5) == 0) {
+               if (check_id_changed(space, 1))
+                       return NULL;
+               if (snd_ctl_elem_info_get_type(space->ctl_info) == SND_CTL_ELEM_TYPE_ENUMERATED)
+                       val = snd_ctl_elem_info_get_items(space->ctl_info);
+               else {
+                 empty:
+                       res[0] = '\0';
+                       return res;
+               }
+               goto value;
+       }
+       if (strncasecmp(attr, "value", 5) == 0) {
+               if (check_id_changed(space, 3))
+                       return NULL;
+               return get_ctl_value(space);
+       }
+       Perror(space, "unknown ctl{} attribute '%s'", attr);
+       return NULL;
+  value:
+       sprintf(res, "%lli", val);
+       return res;
+}
+
+static int elemid_set(struct space *space, const char *attr, const char *value)
+{
+       unsigned int val;
+       void (*fcn)(snd_ctl_elem_id_t *, unsigned int);
+       snd_ctl_elem_iface_t iface;
+       int err;
+
+       if (strncasecmp(attr, "numid", 5) == 0) {
+               fcn = snd_ctl_elem_id_set_numid;
+               goto value;
+       }
+       if (strncasecmp(attr, "iface", 5) == 0 ||
+           strncasecmp(attr, "interface", 9) == 0 ||
+           strncasecmp(attr, "reset", 5) == 0 ||
+           strncasecmp(attr, "search", 6) == 0) {
+               if (strlen(value) == 0 && strncasecmp(attr, "search", 6) == 0) {
+                       iface = 0;
+                       goto search;
+               }
+               for (iface = 0; iface <= SND_CTL_ELEM_IFACE_LAST; iface++) {
+                       if (strcasecmp(value, snd_ctl_elem_iface_name(iface)) == 0) {
+                               if (strncasecmp(attr, "reset", 5) == 0)
+                                       snd_ctl_elem_id_clear(space->ctl_id);
+                               if (strncasecmp(attr, "search", 5) == 0) {
+                                 search:
+                                       snd_ctl_elem_id_clear(space->ctl_id);
+                                       /* -1 means all */
+                                       snd_ctl_elem_id_set_interface(space->ctl_id, -1);
+                                       snd_ctl_elem_id_set_device(space->ctl_id, -1);
+                                       snd_ctl_elem_id_set_subdevice(space->ctl_id, -1);
+                                       snd_ctl_elem_id_set_name(space->ctl_id, "*");
+                                       snd_ctl_elem_id_set_index(space->ctl_id, -1);
+                                       if (strlen(value) == 0)
+                                               return 0;
+                               }
+                               snd_ctl_elem_id_set_interface(space->ctl_id, iface);
+                               space->ctl_id_changed = ~0;
+                               return 0;
+                       }
+               }
+               Perror(space, "unknown control interface name '%s'", value);
+               return -EINVAL;
+       }
+       if (strncasecmp(attr, "device", 6) == 0) {
+               fcn = snd_ctl_elem_id_set_device;
+               goto value;
+       }
+       if (strncasecmp(attr, "subdev", 6) == 0) {
+               fcn = snd_ctl_elem_id_set_subdevice;
+               goto value;
+       }
+       if (strncasecmp(attr, "name", 4) == 0) {
+               snd_ctl_elem_id_set_name(space->ctl_id, value);
+               space->ctl_id_changed = ~0;
+               return 0;
+       }
+       if (strncasecmp(attr, "index", 5) == 0) {
+               fcn = snd_ctl_elem_id_set_index;
+               goto value;
+       }
+       if (strncasecmp(attr, "value", 5) == 0) {
+               err = check_id_changed(space, 1);
+               if (err < 0)
+                       return err;
+               err = set_ctl_value(space, value);
+               if (err < 0) {
+                       space->ctl_id_changed |= 2;
+               } else {
+                       space->ctl_id_changed &= ~2;
+                       snd_ctl_elem_value_set_id(space->ctl_value, space->ctl_id);
+                       err = snd_ctl_elem_write(snd_hctl_ctl(space->ctl_handle), space->ctl_value);
+                       if (err < 0) {
+                               Perror(space, "value write error: %s", snd_strerror(err));
+                               return err;
+                       }
+               }
+               return err;
+       }
+       Perror(space, "unknown CTL{} attribute '%s'", attr);
+       return -EINVAL;
+  value:
+       val = (unsigned int)strtol(value, NULL, 0);
+       fcn(space->ctl_id, val);
+       space->ctl_id_changed = ~0;
+       return 0;
+}
+
+static int get_key(char **line, char **key, enum key_op *op, char **value)
+{
+       char *linepos;
+       char *temp;
+
+       linepos = *line;
+       if (linepos == NULL && linepos[0] == '\0')
+               return -EINVAL;
+
+       /* skip whitespace */
+       while (isspace(linepos[0]) || linepos[0] == ',')
+               linepos++;
+
+       /* get the key */
+       if (linepos[0] == '\0')
+               return -EINVAL;
+       *key = linepos;
+
+       while (1) {
+               linepos++;
+               if (linepos[0] == '\0')
+                       return -1;
+               if (isspace(linepos[0]))
+                       break;
+               if (linepos[0] == '=')
+                       break;
+               if (linepos[0] == '+')
+                       break;
+               if (linepos[0] == '!')
+                       break;
+               if (linepos[0] == ':')
+                       break;
+       }
+
+       /* remember end of key */
+       temp = linepos;
+
+       /* skip whitespace after key */
+       while (isspace(linepos[0]))
+               linepos++;
+       if (linepos[0] == '\0')
+               return -EINVAL;
+
+       /* get operation type */
+       if (linepos[0] == '=' && linepos[1] == '=') {
+               *op = KEY_OP_MATCH;
+               linepos += 2;
+               dbg("operator=match");
+       } else if (linepos[0] == '!' && linepos[1] == '=') {
+               *op = KEY_OP_NOMATCH;
+               linepos += 2;
+               dbg("operator=nomatch");
+       } else if (linepos[0] == '+' && linepos[1] == '=') {
+               *op = KEY_OP_ADD;
+               linepos += 2;
+               dbg("operator=add");
+       } else if (linepos[0] == '=') {
+               *op = KEY_OP_ASSIGN;
+               linepos++;
+               dbg("operator=assign");
+       } else if (linepos[0] == ':' && linepos[1] == '=') {
+               *op = KEY_OP_ASSIGN_FINAL;
+               linepos += 2;
+               dbg("operator=assign_final");
+       } else
+               return -EINVAL;
+
+       /* terminate key */
+       temp[0] = '\0';
+       dbg("key='%s'", *key);
+
+       /* skip whitespace after operator */
+       while (isspace(linepos[0]))
+               linepos++;
+       if (linepos[0] == '\0')
+               return -EINVAL;
+
+       /* get the value*/
+       if (linepos[0] != '"')
+               return -EINVAL;
+       linepos++;
+       *value = linepos;
+
+       while (1) {
+               temp = strchr(linepos, '"');
+               if (temp && temp[-1] == '\\') {
+                       linepos = temp + 1;
+                       continue;
+               }
+               break;
+       }
+       if (!temp)
+               return -EINVAL;
+       temp[0] = '\0';
+       temp++;
+       dbg("value='%s'", *value);
+
+       /* move line to next key */
+       *line = temp;
+
+       return 0;
+}
+
+/* extract possible KEY{attr} */
+static char *get_key_attribute(struct space *space, char *str, char *res, size_t ressize)
+{
+       char *pos;
+       char *attr;
+
+       attr = strchr(str, '{');
+       if (attr != NULL) {
+               attr++;
+               pos = strchr(attr, '}');
+               if (pos == NULL) {
+                       Perror(space, "missing closing brace for format");
+                       return NULL;
+               }
+               pos[0] = '\0';
+               strlcpy(res, attr, ressize);
+               pos[0] = '}';
+               dbg("attribute='%s'", res);
+               return res;
+       }
+
+       return NULL;
+}
+
+/* extract possible {attr} and move str behind it */
+static char *get_format_attribute(struct space *space, char **str)
+{
+       char *pos;
+       char *attr = NULL;
+
+       if (*str[0] == '{') {
+               pos = strchr(*str, '}');
+               if (pos == NULL) {
+                       Perror(space, "missing closing brace for format");
+                       return NULL;
+               }
+               pos[0] = '\0';
+               attr = *str+1;
+               *str = pos+1;
+               dbg("attribute='%s', str='%s'", attr, *str);
+       }
+       return attr;
+}
+
+/* extract possible format length and move str behind it*/
+static int get_format_len(struct space *space, char **str)
+{
+       int num;
+       char *tail;
+
+       if (isdigit(*str[0])) {
+               num = (int) strtoul(*str, &tail, 10);
+               if (num > 0) {
+                       *str = tail;
+                       dbg("format length=%i", num);
+                       return num;
+               } else {
+                       Perror(space, "format parsing error '%s'", *str);
+               }
+       }
+       return -1;
+}
+
+static void apply_format(struct space *space, char *string, size_t maxsize)
+{
+       char temp[PATH_SIZE];
+       char temp2[PATH_SIZE];
+       char *head, *tail, *pos, *cpos, *attr, *rest;
+       struct pair *pair;
+       int len;
+       int i;
+       int count;
+       enum subst_type {
+               SUBST_UNKNOWN,
+               SUBST_CARDINFO,
+               SUBST_CTL,
+               SUBST_RESULT,
+               SUBST_ATTR,
+               SUBST_SYSFSROOT,
+               SUBST_ENV,
+       };
+       static const struct subst_map {
+               char *name;
+               char fmt;
+               enum subst_type type;
+       } map[] = {
+               { .name = "cardinfo",   .fmt = 'i',     .type = SUBST_CARDINFO },
+               { .name = "ctl",        .fmt = 'C',     .type = SUBST_CTL },
+               { .name = "result",     .fmt = 'c',     .type = SUBST_RESULT },
+               { .name = "attr",       .fmt = 's',     .type = SUBST_ATTR },
+               { .name = "sysfsroot",  .fmt = 'r',     .type = SUBST_SYSFSROOT },
+               { .name = "env",        .fmt = 'E',     .type = SUBST_ENV },
+               { NULL, '\0', 0 }
+       };
+       enum subst_type type;
+       const struct subst_map *subst;
+
+       head = string;
+       while (1) {
+               len = -1;
+               while (head[0] != '\0') {
+                       if (head[0] == '$') {
+                               /* substitute named variable */
+                               if (head[1] == '\0')
+                                       break;
+                               if (head[1] == '$') {
+                                       strlcpy(temp, head+2, sizeof(temp));
+                                       strlcpy(head+1, temp, maxsize);
+                                       head++;
+                                       continue;
+                               }
+                               head[0] = '\0';
+                               for (subst = map; subst->name; subst++) {
+                                       if (strncasecmp(&head[1], subst->name, strlen(subst->name)) == 0) {
+                                               type = subst->type;
+                                               tail = head + strlen(subst->name)+1;
+                                               dbg("will substitute format name '%s'", subst->name);
+                                               goto found;
+                                       }
+                               }
+                       } else if (head[0] == '%') {
+                               /* substitute format char */
+                               if (head[1] == '\0')
+                                       break;
+                               if (head[1] == '%') {
+                                       strlcpy(temp, head+2, sizeof(temp));
+                                       strlcpy(head+1, temp, maxsize);
+                                       head++;
+                                       continue;
+                               }
+                               head[0] = '\0';
+                               tail = head+1;
+                               len = get_format_len(space, &tail);
+                               for (subst = map; subst->name; subst++) {
+                                       if (tail[0] == subst->fmt) {
+                                               type = subst->type;
+                                               tail++;
+                                               dbg("will substitute format char '%c'", subst->fmt);
+                                               goto found;
+                                       }
+                               }
+                       }
+                       head++;
+               }
+               break;
+found:
+               attr = get_format_attribute(space, &tail);
+               strlcpy(temp, tail, sizeof(temp));
+               dbg("format=%i, string='%s', tail='%s'", type ,string, tail);
+
+               switch (type) {
+               case SUBST_CARDINFO:
+                       if (attr == NULL)
+                               Perror(space, "missing identification parametr for cardinfo");
+                       else {
+                               const char *value = cardinfo_get(space, attr);
+                               if (value == NULL)
+                                       break;
+                               strlcat(string, value, maxsize);
+                               dbg("substitute cardinfo{%s} '%s'", attr, value);
+                       }
+                       break;
+               case SUBST_CTL:
+                       if (attr == NULL)
+                               Perror(space, "missing identification parametr for ctl");
+                       else {
+                               const char *value = elemid_get(space, attr);
+                               if (value == NULL)
+                                       break;
+                               strlcat(string, value, maxsize);
+                               dbg("substitute ctl{%s} '%s'", attr, value);
+                       }
+                       break;
+               case SUBST_RESULT:
+                       if (space->program_result == NULL)
+                               break;
+                       /* get part part of the result string */
+                       i = 0;
+                       if (attr != NULL)
+                               i = strtoul(attr, &rest, 10);
+                       if (i > 0) {
+                               dbg("request part #%d of result string", i);
+                               cpos = space->program_result;
+                               while (--i) {
+                                       while (cpos[0] != '\0' && !isspace(cpos[0]))
+                                               cpos++;
+                                       while (isspace(cpos[0]))
+                                               cpos++;
+                               }
+                               if (i > 0) {
+                                       Perror(space, "requested part of result string not found");
+                                       break;
+                               }
+                               strlcpy(temp2, cpos, sizeof(temp2));
+                               /* %{2+}c copies the whole string from the second part on */
+                               if (rest[0] != '+') {
+                                       cpos = strchr(temp2, ' ');
+                                       if (cpos)
+                                               cpos[0] = '\0';
+                               }
+                               strlcat(string, temp2, maxsize);
+                               dbg("substitute part of result string '%s'", temp2);
+                       } else {
+                               strlcat(string, space->program_result, maxsize);
+                               dbg("substitute result string '%s'", space->program_result);
+                       }
+                       break;
+               case SUBST_ATTR:
+                       if (attr == NULL)
+                               Perror(space, "missing file parameter for attr");
+                       else {
+                               const char *value = NULL;
+                               size_t size;
+
+                               pair = value_find(space, "SYSFS_DEVICE");
+                               if (pair == NULL)
+                                       break;
+                               value = sysfs_attr_get_value(pair->value, attr);
+
+                               if (value == NULL)
+                                       break;
+
+                               /* strip trailing whitespace and replace untrusted characters of sysfs value */
+                               size = strlcpy(temp2, value, sizeof(temp2));
+                               if (size >= sizeof(temp2))
+                                       size = sizeof(temp2)-1;
+                               while (size > 0 && isspace(temp2[size-1]))
+                                       temp2[--size] = '\0';
+                               count = replace_untrusted_chars(temp2);
+                               if (count > 0)
+                                       Perror(space, "%i untrusted character(s) replaced" , count);
+                               strlcat(string, temp2, maxsize);
+                               dbg("substitute sysfs value '%s'", temp2);
+                       }
+                       break;
+               case SUBST_SYSFSROOT:
+                       strlcat(string, sysfs_path, maxsize);
+                       dbg("substitute sysfs_path '%s'", sysfs_path);
+                       break;
+               case SUBST_ENV:
+                       if (attr == NULL) {
+                               dbg("missing attribute");
+                               break;
+                       }
+                       pos = getenv(attr);
+                       if (pos == NULL) {
+                               dbg("env '%s' not available", attr);
+                               break;
+                       }
+                       dbg("substitute env '%s=%s'", attr, pos);
+                       strlcat(string, pos, maxsize);
+                       break;
+               default:
+                       Perror(space, "unknown substitution type=%i", type);
+                       break;
+               }
+               /* possibly truncate to format-char specified length */
+               if (len != -1) {
+                       head[len] = '\0';
+                       dbg("truncate to %i chars, subtitution string becomes '%s'", len, head);
+               }
+               strlcat(string, temp, maxsize);
+       }
+       /* unescape strings */
+       head = tail = string;
+       while (*head != '\0') {
+               if (*head == '\\') {
+                       head++;
+                       if (*head == '\0')
+                               break;
+                       switch (*head) {
+                       case 'a': *tail++ = '\a'; break;
+                       case 'b': *tail++ = '\b'; break;
+                       case 'n': *tail++ = '\n'; break;
+                       case 'r': *tail++ = '\r'; break;
+                       case 't': *tail++ = '\t'; break;
+                       case 'v': *tail++ = '\v'; break;
+                       case '\\': *tail++ = '\\'; break;
+                       default: *tail++ = *head; break;
+                       }
+                       head++;
+                       continue;
+               }
+               if (*head)
+                       *tail++ = *head++;
+       }
+       *tail = 0;
+}
+
+static int do_match(const char *key, enum key_op op,
+                   const char *key_value, const char *value)
+{
+       int match;
+
+       if (value == NULL)
+               return 0;
+       dbg("match %s '%s' <-> '%s'", key, key_value, value);
+       match = fnmatch(key_value, value, 0) == 0;
+       if (match && op == KEY_OP_MATCH) {
+               dbg("%s is true (matching value)", key);
+               return 1;
+       }
+       if (!match && op == KEY_OP_NOMATCH) {
+               dbg("%s is true (non-matching value)", key);
+               return 1;
+       }
+       dbg("%s is false", key);
+       return 0;
+}
+
+static int ctl_match(snd_ctl_elem_id_t *pattern, snd_ctl_elem_id_t *id)
+{
+       if (snd_ctl_elem_id_get_interface(pattern) != -1 &&
+           snd_ctl_elem_id_get_interface(pattern) != snd_ctl_elem_id_get_interface(id))
+               return 0;
+       if (snd_ctl_elem_id_get_device(pattern) != -1 &&
+           snd_ctl_elem_id_get_device(pattern) != snd_ctl_elem_id_get_device(id))
+               return 0;
+       if (snd_ctl_elem_id_get_subdevice(pattern) != -1 &&
+           snd_ctl_elem_id_get_subdevice(pattern) != snd_ctl_elem_id_get_subdevice(id))
+               return 0;
+       if (snd_ctl_elem_id_get_index(pattern) != -1 &&
+           snd_ctl_elem_id_get_index(pattern) != snd_ctl_elem_id_get_index(id))
+               return 0;
+       if (fnmatch(snd_ctl_elem_id_get_name(pattern), snd_ctl_elem_id_get_name(id), 0) != 0)
+               return 0;
+       return 1;
+}
+
+static
+int run_program1(struct space *space,
+                const char *command0, char *result,
+                size_t ressize, size_t *reslen, int log)
+{
+       char *pos = strchr(command0, ' ');
+       int cmdlen = pos ? pos - command0 : strlen(command0);
+       int err, index;
+       snd_hctl_elem_t *elem;
+       snd_ctl_elem_id_t *id;
+       
+       if (cmdlen == 12 && strncmp(command0, "__ctl_search", 12) == 0) {
+               index = 0;
+               if (pos)
+                       index = strtol(pos, NULL, 0);
+               err = snd_ctl_elem_id_malloc(&id);
+               if (err < 0)
+                       return EXIT_FAILURE;
+               elem = snd_hctl_first_elem(space->ctl_handle);
+               while (elem) {
+                       snd_hctl_elem_get_id(elem, id);
+                       if (!ctl_match(space->ctl_id, id))
+                               goto next_search;
+                       if (index > 0) {
+                               index--;
+                               goto next_search;
+                       }
+                       strlcpy(result, "0", ressize);
+                       snd_ctl_elem_id_copy(space->ctl_id, id);
+                       snd_ctl_elem_id_free(id);
+                       dbg("__ctl_search found a control");
+                       return EXIT_SUCCESS;
+                     next_search:
+                       elem = snd_hctl_elem_next(elem);
+               }
+               snd_ctl_elem_id_free(id);
+               return EXIT_FAILURE;
+       }
+       if (cmdlen == 11 && strncmp(command0, "__ctl_count", 11) == 0) {
+               index = 0;
+               err = snd_ctl_elem_id_malloc(&id);
+               if (err < 0)
+                       return EXIT_FAILURE;
+               elem = snd_hctl_first_elem(space->ctl_handle);
+               while (elem) {
+                       snd_hctl_elem_get_id(elem, id);
+                       if (!ctl_match(space->ctl_id, id))
+                               goto next_count;
+                       index++;
+                     next_count:
+                       elem = snd_hctl_elem_next(elem);
+               }
+               snd_ctl_elem_id_free(id);
+               if (index > 0) {
+                       snprintf(result, ressize, "%u", index);
+                       dbg("__ctl_count found %s controls", result);
+                       return EXIT_SUCCESS;
+               }
+               dbg("__ctl_count no match");
+               return EXIT_FAILURE;
+       }
+       if (cmdlen == 11 && strncmp(command0, "__ctl_write", 11) == 0) {
+       }
+       Perror(space, "unknown buildin command '%s'", command0);
+       return EXIT_FAILURE;
+}
+
+static int parse(struct space *space, const char *filename);
+
+static char *new_root_dir(const char *filename)
+{
+       char *res, *tmp;
+
+       res = strdup(filename);
+       if (res) {
+               tmp = rindex(res, '/');
+               if (tmp)
+                       *tmp = '\0';
+       }
+       dbg("new_root_dir '%s' '%s'", filename, res);
+       return res;
+}
+
+static int parse_line(struct space *space, char *line, size_t linesize)
+{
+       char *linepos;
+       char *key, *value, *attr, *temp;
+       struct pair *pair;
+       enum key_op op;
+       int err = 0, count;
+       char string[PATH_SIZE];
+       char result[PATH_SIZE];
+
+       linepos = line;
+       while (*linepos != '\0') {
+               op = KEY_OP_UNSET;
+               
+               err = get_key(&linepos, &key, &op, &value);
+               if (err < 0)
+                       break;
+
+               if (strncasecmp(key, "LABEL", 5) == 0) {
+                       if (op != KEY_OP_ASSIGN) {
+                               Perror(space, "invalid LABEL operation");
+                               goto invalid;
+                       }
+                       if (space->go_to && strcmp(space->go_to, value) == 0) {
+                               free(space->go_to);
+                               space->go_to = NULL;
+                       }
+                       continue;
+               }
+               
+               if (space->go_to) {
+                       dbg("skip (GOTO '%s')", space->go_to);
+                       break;          /* not for us */
+               }
+
+               if (strncasecmp(key, "CTL{", 4) == 0) {
+                       attr = get_key_attribute(space, key + 3, string, sizeof(string));
+                       if (attr == NULL) {
+                               Perror(space, "error parsing CTL attribute");
+                               goto invalid;
+                       }
+                       if (op == KEY_OP_ASSIGN) {
+                               dbg("ctl assign: '%s' '%s'", value, attr);
+                               apply_format(result, value, sizeof(result));
+                               err = elemid_set(space, attr, result);
+                               if (space->program_result) {
+                                       free(space->program_result);
+                                       space->program_result = NULL;
+                               }
+                               snprintf(string, sizeof(string), "%i", err);
+                               space->program_result = strdup(string);
+                               if (err < 0 || space->program_result == NULL)
+                                       break;
+                       } else if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               dbg("ctl match: '%s' '%s'", value, attr);
+                               temp = (char *)elemid_get(space, attr);
+                               if (!do_match(key, op, value, temp))
+                                       break;
+                       } else {
+                               Perror(space, "invalid CTL{} operation");
+                               goto invalid;
+                       }
+                       continue;
+               }
+               if (strcasecmp(key, "RESULT") == 0) {
+                       if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               if (!do_match(key, op, value, space->program_result))
+                                       break;
+                       } else {
+                               Perror(space, "invalid RESULT operation");
+                               goto invalid;
+                       }
+                       continue;
+               }
+               if (strcasecmp(key, "PROGRAM") == 0) {
+                       if (op == KEY_OP_UNSET)
+                               continue;
+                       strlcpy(string, value, sizeof(string));
+                       apply_format(space, string, sizeof(string));
+                       if (space->program_result) {
+                               free(space->program_result);
+                               space->program_result = NULL;
+                       }
+                       if (run_program(space, string, result, sizeof(result), NULL, space->log_run) != 0) {
+                               dbg("PROGRAM '%s' is false", string);
+                               if (op != KEY_OP_NOMATCH)
+                                       break;
+                       } else {
+                               remove_trailing_chars(result, '\n');
+                               count = replace_untrusted_chars(result);
+                               if (count)
+                                       info("%i untrusted character(s) replaced", count);
+                               dbg("PROGRAM '%s' result is '%s'", string, result);
+                               space->program_result = strdup(result);
+                               if (space->program_result == NULL)
+                                       break;
+                               dbg("PROGRAM returned successful");
+                               if (op == KEY_OP_NOMATCH)
+                                       break;
+                       }
+                       dbg("PROGRAM key is true");
+                       continue;
+               }
+               if (strncasecmp(key, "CARDINFO{", 9) == 0) {
+                       attr = get_key_attribute(space, key + 8, string, sizeof(string));
+                       if (attr == NULL) {
+                               Perror(space, "error parsing CARDINFO attribute");
+                               goto invalid;
+                       }
+                       if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               dbg("cardinfo: '%s' '%s'", value, attr);
+                               temp = (char *)cardinfo_get(space, attr);
+                               if (!do_match(key, op, value, temp))
+                                       break;
+                       } else {
+                               Perror(space, "invalid CARDINFO{} operation");
+                               goto invalid;
+                       }
+                       continue;
+               }
+               if (strncasecmp(key, "ATTR{", 5) == 0) {
+                       attr = get_key_attribute(space, key + 4, string, sizeof(string));
+                       if (attr == NULL) {
+                               Perror(space, "error parsing ATTR attribute");
+                               goto invalid;
+                       }
+                       if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               pair = value_find(space, "SYSFS_DEVICE");
+                               if (pair == NULL)
+                                       break;
+                               dbg("sysfs_attr: '%s' '%s'", pair->value, attr);
+                               temp = sysfs_attr_get_value(pair->value, attr);
+                               if (!do_match(key, op, value, temp))
+                                       break;
+                       } else {
+                               Perror(space, "invalid ATTR{} operation");
+                               goto invalid;
+                       }
+                       continue;
+               }
+               if (strncasecmp(key, "ENV{", 4) == 0) {
+                       attr = get_key_attribute(space, key + 3, string, sizeof(string));
+                       if (attr == NULL) {
+                               Perror(space, "error parsing ENV attribute");
+                               goto invalid;
+                       }
+                       if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+                               temp = getenv(attr);
+                               dbg("env: '%s' '%s'", attr, temp);
+                               if (!do_match(key, op, value, temp))
+                                       break;
+                       } else {
+                               Perror(space, "invalid ENV{} operation");
+                               goto invalid;
+                       }
+                       continue;
+               }
+               if (strcasecmp(key, "GOTO") == 0) {
+                       if (op != KEY_OP_ASSIGN) {
+                               Perror(space, "invalid GOTO operation");
+                               goto invalid;
+                       }
+                       space->go_to = strdup(value);
+                       if (space->go_to == NULL) {
+                               err = -ENOMEM;
+                               break;
+                       }
+                       continue;
+               }
+               if (strcasecmp(key, "INCLUDE") == 0) {
+                       char *rootdir, *go_to;
+                       const char *filename;
+                       int linenum;
+                       if (op != KEY_OP_ASSIGN) {
+                               Perror(space, "invalid INCLUDE operation");
+                               goto invalid;
+                       }
+                       if (value[0] == '/')
+                               strlcpy(string, value, sizeof(string));
+                       else {
+                               strlcpy(string, space->rootdir, sizeof(string));
+                               strlcat(string, "/", sizeof(string));
+                               strlcat(string, value, sizeof(string));
+                       }
+                       rootdir = space->rootdir;
+                       go_to = space->go_to;
+                       filename = space->filename;
+                       linenum = space->linenum;
+                       space->go_to = NULL;
+                       space->rootdir = new_root_dir(string);
+                       if (space->rootdir) {
+                               err = parse(space, string);
+                               free(space->rootdir);
+                       } else
+                               err = -ENOMEM;
+                       if (space->go_to) {
+                               Perror(space, "unterminated GOTO '%s'", space->go_to);
+                               free(space->go_to);
+                       }
+                       space->go_to = go_to;
+                       space->rootdir = rootdir;
+                       space->filename = filename;
+                       space->linenum = linenum;
+                       if (space->quit)
+                               break;
+                       continue;
+               }
+               if (strncasecmp(key, "PRINT", 5) == 0) {
+                       strlcpy(string, value, sizeof(string));
+                       apply_format(space, string, sizeof(string));
+                       fwrite(string, strlen(string), 1, stdout);
+                       continue;
+               }
+               if (strncasecmp(key, "ERROR", 5) == 0) {
+                       strlcpy(string, value, sizeof(string));
+                       apply_format(space, string, sizeof(string));
+                       fwrite(string, strlen(string), 1, stderr);
+                       continue;
+               }
+               if (strncasecmp(key, "EXIT", 4) == 0) {
+                       strlcpy(string, value, sizeof(string));
+                       apply_format(space, string, sizeof(string));
+                       space->exit_code = strtol(string, NULL, 0);
+                       space->quit = 1;
+                       break;
+               }
+               if (strncasecmp(key, "SYSFS_DEVICE", 12) == 0) {
+                       strlcpy(string, value, sizeof(string));
+                       apply_format(space, string, sizeof(string));
+                       err = value_set(space, key, string);
+                       dbg("SYSFS_DEVICE='%s'", string);
+                       break;
+               }
+
+               Perror(space, "unknown key '%s'", key);
+       }
+       return err;
+
+invalid:
+       Perror(space, "invalid rule");
+       return -EINVAL;
+}
+
+static int parse(struct space *space, const char *filename)
+{
+       char *buf, *bufline, *line;
+       size_t bufsize, pos, count, linesize;
+       unsigned int linenum, i, j, linenum_adj;
+       int err;
+
+       dbg("start of file '%s'", filename);
+
+       if (file_map(filename, &buf, &bufsize) != 0) {
+               err = errno;
+               error("Unable to open file '%s': %s", filename, strerror(err));
+               return -err;
+       }
+
+       err = 0;        
+       pos = 0;
+       linenum = 0;
+       linesize = 128;
+       line = malloc(linesize);
+       if (line == NULL)
+               return -ENOMEM;
+       space->filename = filename;
+       while (!err && pos < bufsize && !space->quit) {
+               count = line_width(buf, bufsize, pos);
+               bufline = buf + pos;
+               pos += count + 1;
+               linenum++;
+               
+               /* skip whitespaces */
+               while (count > 0 && isspace(bufline[0])) {
+                       bufline++;
+                       count--;
+               }
+               if (count == 0)
+                       continue;
+                       
+               /* comment check */
+               if (bufline[0] == '#')
+                       continue;
+               
+               if (count > linesize - 1) {
+                       free(line);
+                       linesize = (count + 127 + 1) & ~127;
+                       if (linesize > 2048) {
+                               error("file %s, line %i too long", filename, linenum);
+                               err = -EINVAL;
+                               break;
+                       }
+                       line = malloc(linesize);
+                       if (line == NULL) {
+                               err = -EINVAL;
+                               break;
+                       }
+               }
+               
+               /* skip backslash and newline from multiline rules */
+               linenum_adj = 0;
+               for (i = j = 0; i < count; i++) {
+                       if (bufline[i] == '\\' && bufline[i+1] == '\n') {
+                               linenum_adj++;
+                               continue;
+                       }
+                       line[j++] = bufline[i];
+               }
+               line[j] = '\0';
+
+               dbg("read (%i) '%s'", linenum, line);
+               space->linenum = linenum;
+               err = parse_line(space, line, linesize);
+               linenum += linenum_adj;
+       }
+
+       space->filename = NULL;
+       space->linenum = -1;
+       file_unmap(buf, bufsize);
+       dbg("end of file '%s'", filename);
+       return err;
+}
+
+int init(const char *filename, const char *cardname)
+{
+       struct space *space;
+       int err = 0, card, first;
+       
+       sysfs_init();
+       if (!cardname) {
+               first = 1;
+               card = -1;
+               while (1) {
+                       if (snd_card_next(&card) < 0)
+                               break;
+                       if (card < 0) {
+                               if (first) {
+                                       error("No soundcards found...");
+                                       return -ENODEV;
+                               }
+                               break;
+                       }
+                       first = 0;
+                       err = init_space(&space, card);
+                       if (err == 0 &&
+                           (space->rootdir = new_root_dir(filename)) != NULL)
+                               err = parse(space, filename);
+                       free_space(space);
+                       if (err < 0)
+                               break;
+               }
+       } else {
+               card = snd_card_get_index(cardname);
+               if (card < 0) {
+                       error("Cannot find soundcard '%s'...", cardname);
+                       goto error;
+               }
+               memset(&space, 0, sizeof(space));
+               err = init_space(&space, card);
+               if (err == 0 &&
+                   (space->rootdir = new_root_dir(filename)) != NULL)
+                       err = parse(space, filename);
+               free_space(space);
+       }
+  error:
+       sysfs_cleanup();
+       return err;
+}
diff --git a/alsactl/init_sysdeps.c b/alsactl/init_sysdeps.c
new file mode 100644 (file)
index 0000000..f263138
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2005-2006 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify it
+ *     under the terms of the GNU General Public License as published by the
+ *     Free Software Foundation version 2 of the License.
+ * 
+ *     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
+ *     General Public License for more details.
+ * 
+ *     You should have received a copy of the GNU General Public License along
+ *     with this program; if not, write to the Free Software Foundation, Inc.,
+ *     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifdef __GLIBC__
+static size_t strlcpy(char *dst, const char *src, size_t size)
+{
+       size_t bytes = 0;
+       char *q = dst;
+       const char *p = src;
+       char ch;
+
+       while ((ch = *p++)) {
+               if (bytes+1 < size)
+                       *q++ = ch;
+               bytes++;
+       }
+
+       /* If size == 0 there is no space for a final null... */
+       if (size)
+               *q = '\0';
+       return bytes;
+}
+
+static size_t strlcat(char *dst, const char *src, size_t size)
+{
+       size_t bytes = 0;
+       char *q = dst;
+       const char *p = src;
+       char ch;
+
+       while (bytes < size && *q) {
+               q++;
+               bytes++;
+       }
+       if (bytes == size)
+               return (bytes + strlen(src));
+
+       while ((ch = *p++)) {
+               if (bytes+1 < size)
+               *q++ = ch;
+               bytes++;
+       }
+
+       *q = '\0';
+       return bytes;
+}
+#endif /* __GLIBC__ */
diff --git a/alsactl/init_sysfs.c b/alsactl/init_sysfs.c
new file mode 100644 (file)
index 0000000..0cbada2
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2005-2006 Kay Sievers <kay.sievers@vrfy.org>
+ *                   2008 Jaroslav Kysela <perex@perex.cz>
+ *
+ *     This program is free software; you can redistribute it and/or modify it
+ *     under the terms of the GNU General Public License as published by the
+ *     Free Software Foundation version 2 of the License.
+ * 
+ *     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
+ *     General Public License for more details.
+ * 
+ *     You should have received a copy of the GNU General Public License along
+ *     with this program; if not, write to the Free Software Foundation, Inc.,
+ *     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+
+static char sysfs_path[PATH_SIZE];
+
+/* attribute value cache */
+static LIST_HEAD(attr_list);
+struct sysfs_attr {
+       struct list_head node;
+       char path[PATH_SIZE];
+       char *value;                    /* points to value_local if value is cached */
+       char value_local[NAME_SIZE];
+};
+
+static int sysfs_init(void)
+{
+       const char *env;
+       char sysfs_test[PATH_SIZE];
+
+       env = getenv("SYSFS_PATH");
+       if (env) {
+               strlcpy(sysfs_path, env, sizeof(sysfs_path));
+               remove_trailing_chars(sysfs_path, '/');
+       } else
+               strlcpy(sysfs_path, "/sys", sizeof(sysfs_path));
+       dbg("sysfs_path='%s'", sysfs_path);
+
+       strlcpy(sysfs_test, sysfs_path, sizeof(sysfs_test));
+       strlcat(sysfs_test, "/kernel/uevent_helper", sizeof(sysfs_test));
+       if (access(sysfs_test, F_OK)) {
+               error("sysfs path '%s' is invalid\n", sysfs_path);
+               return -errno;
+       }
+
+       INIT_LIST_HEAD(&attr_list);
+       return 0;
+}
+
+static void sysfs_cleanup(void)
+{
+       struct sysfs_attr *attr_loop;
+       struct sysfs_attr *attr_temp;
+
+       list_for_each_entry_safe(attr_loop, attr_temp, &attr_list, node) {
+               list_del(&attr_loop->node);
+               free(attr_loop);
+       }
+}
+
+static char *sysfs_attr_get_value(const char *devpath, const char *attr_name)
+{
+       char path_full[PATH_SIZE];
+       const char *path;
+       char value[NAME_SIZE];
+       struct sysfs_attr *attr_loop;
+       struct sysfs_attr *attr;
+       struct stat statbuf;
+       int fd;
+       ssize_t size;
+       size_t sysfs_len;
+
+       dbg("open '%s'/'%s'", devpath, attr_name);
+       sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full));
+       path = &path_full[sysfs_len];
+       strlcat(path_full, devpath, sizeof(path_full));
+       strlcat(path_full, "/", sizeof(path_full));
+       strlcat(path_full, attr_name, sizeof(path_full));
+
+       /* look for attribute in cache */
+       list_for_each_entry(attr_loop, &attr_list, node) {
+               if (strcmp(attr_loop->path, path) == 0) {
+                       dbg("found in cache '%s'", attr_loop->path);
+                       return attr_loop->value;
+               }
+       }
+
+       /* store attribute in cache (also negatives are kept in cache) */
+       dbg("new uncached attribute '%s'", path_full);
+       attr = malloc(sizeof(struct sysfs_attr));
+       if (attr == NULL)
+               return NULL;
+       memset(attr, 0x00, sizeof(struct sysfs_attr));
+       strlcpy(attr->path, path, sizeof(attr->path));
+       dbg("add to cache '%s'", path_full);
+       list_add(&attr->node, &attr_list);
+
+       if (lstat(path_full, &statbuf) != 0) {
+               dbg("stat '%s' failed: %s", path_full, strerror(errno));
+               goto out;
+       }
+
+       if (S_ISLNK(statbuf.st_mode)) {
+               /* links return the last element of the target path */
+               char link_target[PATH_SIZE];
+               int len;
+               const char *pos;
+
+               len = readlink(path_full, link_target, sizeof(link_target));
+               if (len > 0) {
+                       link_target[len] = '\0';
+                       pos = strrchr(link_target, '/');
+                       if (pos != NULL) {
+                               dbg("cache '%s' with link value '%s'", path_full, pos+1);
+                               strlcpy(attr->value_local, &pos[1], sizeof(attr->value_local));
+                               attr->value = attr->value_local;
+                       }
+               }
+               goto out;
+       }
+
+       /* skip directories */
+       if (S_ISDIR(statbuf.st_mode))
+               goto out;
+
+       /* skip non-readable files */
+       if ((statbuf.st_mode & S_IRUSR) == 0)
+               goto out;
+
+       /* read attribute value */
+       fd = open(path_full, O_RDONLY);
+       if (fd < 0) {
+               dbg("attribute '%s' does not exist", path_full);
+               goto out;
+       }
+       size = read(fd, value, sizeof(value));
+       close(fd);
+       if (size < 0)
+               goto out;
+       if (size == sizeof(value))
+               goto out;
+
+       /* got a valid value, store and return it */
+       value[size] = '\0';
+       remove_trailing_chars(value, '\n');
+       dbg("cache '%s' with attribute value '%s'", path_full, value);
+       strlcpy(attr->value_local, value, sizeof(attr->value_local));
+       attr->value = attr->value_local;
+
+out:
+       return attr->value;
+}
diff --git a/alsactl/init_utils_run.c b/alsactl/init_utils_run.c
new file mode 100644 (file)
index 0000000..dde490b
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2004-2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify it
+ *     under the terms of the GNU General Public License as published by the
+ *     Free Software Foundation version 2 of the License.
+ * 
+ *     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
+ *     General Public License for more details.
+ * 
+ *     You should have received a copy of the GNU General Public License along
+ *     with this program; if not, write to the Free Software Foundation, Inc.,
+ *     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#define MY_MAX(a,b) ((a) > (b) ? (a) : (b))
+
+#define READ_END       0
+#define WRITE_END      1
+
+static
+int run_program1(struct space *space,
+                const char *command0, char *result,
+                size_t ressize, size_t *reslen, int log);
+
+static
+int run_program0(struct space *space,
+                const char *command0, char *result,
+                size_t ressize, size_t *reslen, int log)
+{
+       int retval = 0;
+       int status;
+       int outpipe[2] = {-1, -1};
+       int errpipe[2] = {-1, -1};
+       pid_t pid;
+       char arg[PATH_SIZE];
+       char program[PATH_SIZE];
+       char *argv[(sizeof(arg) / 2) + 1];
+       int devnull;
+       int i;
+
+       /* build argv from comand */
+       strlcpy(arg, command0, sizeof(arg));
+       i = 0;
+       if (strchr(arg, ' ') != NULL) {
+               char *pos = arg;
+
+               while (pos != NULL) {
+                       if (pos[0] == '\'') {
+                               /* don't separate if in apostrophes */
+                               pos++;
+                               argv[i] = strsep(&pos, "\'");
+                               while (pos != NULL && pos[0] == ' ')
+                                       pos++;
+                       } else {
+                               argv[i] = strsep(&pos, " ");
+                       }
+                       dbg("arg[%i] '%s'", i, argv[i]);
+                       i++;
+               }
+               argv[i] = NULL;
+       } else {
+               argv[0] = arg;
+               argv[1] = NULL;
+       }
+       info("'%s'", command0);
+
+       /* prepare pipes from child to parent */
+       if (result || log) {
+               if (pipe(outpipe) != 0) {
+                       Perror(space, "pipe failed: %s", strerror(errno));
+                       return -1;
+               }
+       }
+       if (log) {
+               if (pipe(errpipe) != 0) {
+                       Perror(space, "pipe failed: %s", strerror(errno));
+                       return -1;
+               }
+       }
+
+       /* allow programs in /lib/alsa called without the path */
+       if (strchr(argv[0], '/') == NULL) {
+               strlcpy(program, "/lib/alsa/", sizeof(program));
+               strlcat(program, argv[0], sizeof(program));
+               argv[0] = program;
+       }
+
+       pid = fork();
+       switch(pid) {
+       case 0:
+               /* child closes parent ends of pipes */
+               if (outpipe[READ_END] > 0)
+                       close(outpipe[READ_END]);
+               if (errpipe[READ_END] > 0)
+                       close(errpipe[READ_END]);
+
+               /* discard child output or connect to pipe */
+               devnull = open("/dev/null", O_RDWR);
+               if (devnull > 0) {
+                       dup2(devnull, STDIN_FILENO);
+                       if (outpipe[WRITE_END] < 0)
+                               dup2(devnull, STDOUT_FILENO);
+                       if (errpipe[WRITE_END] < 0)
+                               dup2(devnull, STDERR_FILENO);
+                       close(devnull);
+               } else
+                       Perror(space, "open /dev/null failed: %s", strerror(errno));
+               if (outpipe[WRITE_END] > 0) {
+                       dup2(outpipe[WRITE_END], STDOUT_FILENO);
+                       close(outpipe[WRITE_END]);
+               }
+               if (errpipe[WRITE_END] > 0) {
+                       dup2(errpipe[WRITE_END], STDERR_FILENO);
+                       close(errpipe[WRITE_END]);
+               }
+               execv(argv[0], argv);
+
+               /* we should never reach this */
+               Perror(space, "exec of program '%s' failed", argv[0]);
+               _exit(1);
+       case -1:
+               Perror(space, "fork of '%s' failed: %s", argv[0], strerror(errno));
+               return -1;
+       default:
+               /* read from child if requested */
+               if (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) {
+                       ssize_t count;
+                       size_t respos = 0;
+
+                       /* parent closes child ends of pipes */
+                       if (outpipe[WRITE_END] > 0)
+                               close(outpipe[WRITE_END]);
+                       if (errpipe[WRITE_END] > 0)
+                               close(errpipe[WRITE_END]);
+
+                       /* read child output */
+                       while (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) {
+                               int fdcount;
+                               fd_set readfds;
+
+                               FD_ZERO(&readfds);
+                               if (outpipe[READ_END] > 0)
+                                       FD_SET(outpipe[READ_END], &readfds);
+                               if (errpipe[READ_END] > 0)
+                                       FD_SET(errpipe[READ_END], &readfds);
+                               fdcount = select(MY_MAX(outpipe[READ_END], errpipe[READ_END])+1, &readfds, NULL, NULL, NULL);
+                               if (fdcount < 0) {
+                                       if (errno == EINTR)
+                                               continue;
+                                       retval = -1;
+                                       break;
+                               }
+
+                               /* get stdout */
+                               if (outpipe[READ_END] > 0 && FD_ISSET(outpipe[READ_END], &readfds)) {
+                                       char inbuf[1024];
+                                       char *pos;
+                                       char *line;
+
+                                       count = read(outpipe[READ_END], inbuf, sizeof(inbuf)-1);
+                                       if (count <= 0) {
+                                               close(outpipe[READ_END]);
+                                               outpipe[READ_END] = -1;
+                                               if (count < 0) {
+                                                       Perror(space, "stdin read failed: %s", strerror(errno));
+                                                       retval = -1;
+                                               }
+                                               continue;
+                                       }
+                                       inbuf[count] = '\0';
+
+                                       /* store result for rule processing */
+                                       if (result) {
+                                               if (respos + count < ressize) {
+                                                       memcpy(&result[respos], inbuf, count);
+                                                       respos += count;
+                                               } else {
+                                                       Perror(space, "ressize %ld too short", (long)ressize);
+                                                       retval = -1;
+                                               }
+                                       }
+                                       pos = inbuf;
+                                       while ((line = strsep(&pos, "\n")))
+                                               if (pos || line[0] != '\0')
+                                                       info("'%s' (stdout) '%s'", argv[0], line);
+                               }
+
+                               /* get stderr */
+                               if (errpipe[READ_END] > 0 && FD_ISSET(errpipe[READ_END], &readfds)) {
+                                       char errbuf[1024];
+                                       char *pos;
+                                       char *line;
+
+                                       count = read(errpipe[READ_END], errbuf, sizeof(errbuf)-1);
+                                       if (count <= 0) {
+                                               close(errpipe[READ_END]);
+                                               errpipe[READ_END] = -1;
+                                               if (count < 0)
+                                                       Perror(space, "stderr read failed: %s", strerror(errno));
+                                               continue;
+                                       }
+                                       errbuf[count] = '\0';
+                                       pos = errbuf;
+                                       while ((line = strsep(&pos, "\n")))
+                                               if (pos || line[0] != '\0')
+                                                       info("'%s' (stderr) '%s'", argv[0], line);
+                               }
+                       }
+                       if (outpipe[READ_END] > 0)
+                               close(outpipe[READ_END]);
+                       if (errpipe[READ_END] > 0)
+                               close(errpipe[READ_END]);
+
+                       /* return the childs stdout string */
+                       if (result) {
+                               result[respos] = '\0';
+                               dbg("result='%s'", result);
+                               if (reslen)
+                                       *reslen = respos;
+                       }
+               }
+               waitpid(pid, &status, 0);
+               if (WIFEXITED(status)) {
+                       info("'%s' returned with status %i", argv[0], WEXITSTATUS(status));
+                       if (WEXITSTATUS(status) != 0)
+                               retval = -1;
+               } else {
+                       Perror(space, "'%s' abnormal exit", argv[0]);
+                       retval = -1;
+               }
+       }
+
+       return retval;
+}
+
+static
+int run_program(struct space *space, const char *command0, char *result,
+               size_t ressize, size_t *reslen, int log)
+{
+       if (command0[0] == '_' && command0[1] == '_')
+               return run_program1(space, command0, result, ressize, reslen, log);
+       return run_program0(space, command0, result, ressize, reslen, log);
+}
diff --git a/alsactl/init_utils_string.c b/alsactl/init_utils_string.c
new file mode 100644 (file)
index 0000000..2598e9f
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2004-2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify it
+ *     under the terms of the GNU General Public License as published by the
+ *     Free Software Foundation version 2 of the License.
+ * 
+ *     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
+ *     General Public License for more details.
+ * 
+ *     You should have received a copy of the GNU General Public License along
+ *     with this program; if not, write to the Free Software Foundation, Inc.,
+ *     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+
+static int string_is_true(const char *str)
+{
+       if (strcasecmp(str, "true") == 0)
+               return 1;
+       if (strcasecmp(str, "yes") == 0)
+               return 1;
+       if (strcasecmp(str, "1") == 0)
+               return 1;
+       return 0;
+}
+
+static void remove_trailing_chars(char *path, char c)
+{
+       size_t len;
+
+       len = strlen(path);
+       while (len > 0 && path[len-1] == c)
+               path[--len] = '\0';
+}
+
+/* count of characters used to encode one unicode char */
+static int utf8_encoded_expected_len(const char *str)
+{
+       unsigned char c = (unsigned char)str[0];
+
+       if (c < 0x80)
+               return 1;
+       if ((c & 0xe0) == 0xc0)
+               return 2;
+       if ((c & 0xf0) == 0xe0)
+               return 3;
+       if ((c & 0xf8) == 0xf0)
+               return 4;
+       if ((c & 0xfc) == 0xf8)
+               return 5;
+       if ((c & 0xfe) == 0xfc)
+               return 6;
+       return 0;
+}
+
+/* decode one unicode char */
+static int utf8_encoded_to_unichar(const char *str)
+{
+       int unichar;
+       int len;
+       int i;
+
+       len = utf8_encoded_expected_len(str);
+       switch (len) {
+       case 1:
+               return (int)str[0];
+       case 2:
+               unichar = str[0] & 0x1f;
+               break;
+       case 3:
+               unichar = (int)str[0] & 0x0f;
+               break;
+       case 4:
+               unichar = (int)str[0] & 0x07;
+               break;
+       case 5:
+               unichar = (int)str[0] & 0x03;
+               break;
+       case 6:
+               unichar = (int)str[0] & 0x01;
+               break;
+       default:
+               return -1;
+       }
+
+       for (i = 1; i < len; i++) {
+               if (((int)str[i] & 0xc0) != 0x80)
+                       return -1;
+               unichar <<= 6;
+               unichar |= (int)str[i] & 0x3f;
+       }
+
+       return unichar;
+}
+
+/* expected size used to encode one unicode char */
+static int utf8_unichar_to_encoded_len(int unichar)
+{
+       if (unichar < 0x80)
+               return 1;
+       if (unichar < 0x800)
+               return 2;
+       if (unichar < 0x10000)
+               return 3;
+       if (unichar < 0x200000)
+               return 4;
+       if (unichar < 0x4000000)
+               return 5;
+       return 6;
+}
+
+/* check if unicode char has a valid numeric range */
+static int utf8_unichar_valid_range(int unichar)
+{
+       if (unichar > 0x10ffff)
+               return 0;
+       if ((unichar & 0xfffff800) == 0xd800)
+               return 0;
+       if ((unichar > 0xfdcf) && (unichar < 0xfdf0))
+               return 0;
+       if ((unichar & 0xffff) == 0xffff)
+               return 0;
+       return 1;
+}
+
+/* validate one encoded unicode char and return its length */
+static int utf8_encoded_valid_unichar(const char *str)
+{
+       int len;
+       int unichar;
+       int i;
+
+       len = utf8_encoded_expected_len(str);
+       if (len == 0)
+               return -1;
+
+       /* ascii is valid */
+       if (len == 1)
+               return 1;
+
+       /* check if expected encoded chars are available */
+       for (i = 0; i < len; i++)
+               if ((str[i] & 0x80) != 0x80)
+                       return -1;
+
+       unichar = utf8_encoded_to_unichar(str);
+
+       /* check if encoded length matches encoded value */
+       if (utf8_unichar_to_encoded_len(unichar) != len)
+               return -1;
+
+       /* check if value has valid range */
+       if (!utf8_unichar_valid_range(unichar))
+               return -1;
+
+       return len;
+}
+
+/* replace everything but whitelisted plain ascii and valid utf8 */
+static int replace_untrusted_chars(char *str)
+{
+       size_t i = 0;
+       int replaced = 0;
+
+       while (str[i] != '\0') {
+               int len;
+
+               /* valid printable ascii char */
+               if ((str[i] >= '0' && str[i] <= '9') ||
+                   (str[i] >= 'A' && str[i] <= 'Z') ||
+                   (str[i] >= 'a' && str[i] <= 'z') ||
+                   strchr(" #$%+-./:=?@_,", str[i])) {
+                       i++;
+                       continue;
+               }
+               /* valid utf8 is accepted */
+               len = utf8_encoded_valid_unichar(&str[i]);
+               if (len > 1) {
+                       i += len;
+                       continue;
+               }
+
+               /* everything else is garbage */
+               str[i] = '_';
+               i++;
+               replaced++;
+       }
+
+       return replaced;
+}
diff --git a/alsactl/list.h b/alsactl/list.h
new file mode 100644 (file)
index 0000000..8626630
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * Copied from the Linux kernel source tree, version 2.6.0-test1.
+ *
+ * Licensed under the GPL v2 as per the whole kernel source tree.
+ *
+ */
+
+#ifndef _LIST_H
+#define _LIST_H
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ *
+ * @ptr:       the pointer to the member.
+ * @type:      the type of the container struct this is embedded in.
+ * @member:    the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({                     \
+       const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
+       (type *)( (char *)__mptr - offsetof(type,member) );})
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1  ((void *) 0x00100100)
+#define LIST_POISON2  ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+       struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+       struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+       (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries. 
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+                             struct list_head *prev,
+                             struct list_head *next)
+{
+       next->prev = new;
+       new->next = next;
+       new->prev = prev;
+       prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+       next->prev = prev;
+       prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+       entry->next = LIST_POISON1;
+       entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+       INIT_LIST_HEAD(entry); 
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+       __list_del(list->prev, list->next);
+       list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+                                 struct list_head *head)
+{
+       __list_del(list->prev, list->next);
+       list_add_tail(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(struct list_head *head)
+{
+       return head->next == head;
+}
+
+static inline void __list_splice(struct list_head *list,
+                                struct list_head *head)
+{
+       struct list_head *first = list->next;
+       struct list_head *last = list->prev;
+       struct list_head *at = head->next;
+
+       first->prev = head;
+       head->next = first;
+
+       last->next = at;
+       at->prev = last;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(struct list_head *list, struct list_head *head)
+{
+       if (!list_empty(list))
+               __list_splice(list, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+                                   struct list_head *head)
+{
+       if (!list_empty(list)) {
+               __list_splice(list, head);
+               INIT_LIST_HEAD(list);
+       }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:       the &struct list_head pointer.
+ * @type:      the type of the struct this is embedded in.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+       container_of(ptr, type, member)
+
+/**
+ * list_for_each       -       iterate over a list
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ */
+#define list_for_each(pos, head) \
+       for (pos = (head)->next; pos != (head); \
+               pos = pos->next)
+
+/**
+ * __list_for_each     -       iterate over a list
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+       for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev  -       iterate over a list backwards
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+       for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+/**
+ * list_for_each_safe  -       iterate over a list safe against removal of list entry
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @n:         another &struct list_head to use as temporary storage
+ * @head:      the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+       for (pos = (head)->next, n = pos->next; pos != (head); \
+               pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry -       iterate over list of given type
+ * @pos:       the type * to use as a loop counter.
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member)                         \
+       for (pos = list_entry((head)->next, typeof(*pos), member);      \
+            &pos->member != (head);                                    \
+            pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos:       the type * to use as a loop counter.
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member)                 \
+       for (pos = list_entry((head)->prev, typeof(*pos), member);      \
+            &pos->member != (head);                                    \
+            pos = list_entry(pos->member.prev, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos:       the type * to use as a loop counter.
+ * @n:         another type * to use as temporary storage
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member)                 \
+       for (pos = list_entry((head)->next, typeof(*pos), member),      \
+               n = list_entry(pos->member.next, typeof(*pos), member); \
+            &pos->member != (head);                                    \
+            pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+#endif /* _LIST_H */
index 6c7f8534f08fb39dcdc557a6152d2722906d7c60..90e58cd05ba35aecd200b56a1dc35dd8d55e0f86 100644 (file)
@@ -151,19 +151,6 @@ static char *tlv_to_str(unsigned int *tlv)
        return s;
 }
 
-static int hextodigit(int c)
-{
-       if (c >= '0' && c <= '9')
-               c -= '0';
-       else if (c >= 'a' && c <= 'f')
-               c = c - 'a' + 10;
-       else if (c >= 'A' && c <= 'F')
-               c = c - 'A' + 10;
-       else
-               return -1;
-       return c;
-}
-
 static unsigned int *str_to_tlv(const char *s)
 {
        int i, j, c, len;
diff --git a/alsactl/utils.c b/alsactl/utils.c
new file mode 100644 (file)
index 0000000..eb22ecc
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program - Support routines
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU 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 General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include "alsactl.h"
+
+int file_map(const char *filename, char **buf, size_t *bufsize)
+{
+       struct stat stats;
+       int fd;
+
+       fd = open(filename, O_RDONLY);
+       if (fd < 0) {
+               return -1;
+       }
+
+       if (fstat(fd, &stats) < 0) {
+               close(fd);
+               return -1;
+       }
+
+       *buf = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0);
+       if (*buf == MAP_FAILED) {
+               close(fd);
+               return -1;
+       }
+       *bufsize = stats.st_size;
+
+       close(fd);
+
+       return 0;
+}
+
+void file_unmap(void *buf, size_t bufsize)
+{
+       munmap(buf, bufsize);
+}
+
+size_t line_width(const char *buf, size_t bufsize, size_t pos)
+{
+       int esc = 0;
+       size_t count;
+       
+       for (count = pos; count < bufsize; count++) {
+               if (!esc && buf[count] == '\n')
+                       break;
+               esc = buf[count] == '\\';
+       }
+
+       return count - pos;
+}