From b402cf543aee5f6cd7561d964065a26f07459c8c Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 31 Jul 2008 15:45:08 +0200 Subject: [PATCH] Initial 'alsactl init' implementation See 'man 7 alsactl_init' for more details. Signed-off-by: Jaroslav Kysela --- .gitignore | 3 + alsactl/Makefile.am | 9 +- alsactl/alsactl.c | 33 +- alsactl/alsactl.h | 50 ++ alsactl/alsactl_init.xml | 534 +++++++++++++ alsactl/init/00main | 16 + alsactl/init/hda | 21 + alsactl/init/help | 7 + alsactl/init/info | 22 + alsactl/init/test | 256 ++++++ alsactl/init_parse.c | 1512 +++++++++++++++++++++++++++++++++++ alsactl/init_sysdeps.c | 63 ++ alsactl/init_sysfs.c | 158 ++++ alsactl/init_utils_run.c | 247 ++++++ alsactl/init_utils_string.c | 194 +++++ alsactl/list.h | 289 +++++++ alsactl/state.c | 13 - alsactl/utils.c | 79 ++ 18 files changed, 3484 insertions(+), 22 deletions(-) create mode 100644 alsactl/alsactl_init.xml create mode 100644 alsactl/init/00main create mode 100644 alsactl/init/hda create mode 100644 alsactl/init/help create mode 100644 alsactl/init/info create mode 100644 alsactl/init/test create mode 100644 alsactl/init_parse.c create mode 100644 alsactl/init_sysdeps.c create mode 100644 alsactl/init_sysfs.c create mode 100644 alsactl/init_utils_run.c create mode 100644 alsactl/init_utils_string.c create mode 100644 alsactl/list.h create mode 100644 alsactl/utils.c diff --git a/.gitignore b/.gitignore index 1128fbe..8b72d6f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/alsactl/Makefile.am b/alsactl/Makefile.am index d21a496..93b3d79 100644 --- a/alsactl/Makefile.am +++ b/alsactl/Makefile.am @@ -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 $? diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index 78c6cb1..8d3987a 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -40,19 +40,25 @@ char *command; static void help(void) { printf("Usage: alsactl 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 save current driver setup for one or each soundcards\n"); printf(" to configuration file\n"); printf(" restore load current driver setup for one or each soundcards\n"); printf(" from configuration file\n"); + printf(" init initialize driver to a default state\n"); printf(" names 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, diff --git a/alsactl/alsactl.h b/alsactl/alsactl.h index f1e2b41..51396da 100644 --- a/alsactl/alsactl.h +++ b/alsactl/alsactl.h @@ -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 index 0000000..d4b65fd --- /dev/null +++ b/alsactl/alsactl_init.xml @@ -0,0 +1,534 @@ + + + +
+
+ alsactl init + + + alsactl init + July 2008 + alsactl + + + + alsactl_init + 7 + + + + + alsactl init + alsa control management - initialization + + + DESCRIPTION + "alsactl init" provides soundcard specific initialization. + + + CONFIGURATION + All "alsactl init" configuration files are placed in + /usr/share/alsa/init/ directory. The top level + configuration file is /usr/share/alsa/init/00main. + 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. + + Rules files + The "alsactl init" rules are read from the files located + in the /usr/share/alsa/init/*. The top + level configuration file is /usr/share/alsa/init/00main. + 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. + + 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: + + + + + Compare for equality. + + + + + + + Compare for non-equality. + + + + + + + Assign a value to a key. Keys that represent a list, + are reset and only this single value is assigned. + + + + + + + Add the value to a key that holds a list + of entries. + + + + + + + Assign a value to a key finally; disallow any + later changes, which may be used to prevent changes by + any later rules. + + + + + The following key names can be used to match against device + properties: + + + + + Match the card index of the ALSA driver. + + + + + + + Set or test universal control attribute. Possible + attributes: + + + + + Numeric control identification. + + + + , + + Control interface name (CARD, HWEDEP, MIXER, PCM, RAWMIDI, TIMER, SEQUENCER) + + + + , + + Subdevice number. + + + + + + Control name + + + + + + Control index + + + + + + Control type (BOOLEAN, INTEGER, INTEGER64, ENUMERATED, BYTES, IEC958) + + + + , + + Attributes (stored in a string - use match characters * and ?): + + + + + control is readable + + + + + + control is writable + + + + + + control is volatile + + + + + + control is inactive + + + + + + control is locked + + + + + + control is TLV readable + + + + + + control is TLV writable + + + + + + control is TLV commandable + + + + + + process is owner of this control + + + + + + control created in user space + + + + + + + + + Control owner process PID number + + + + + + Control count of values + + + + + + Value range - minimum value + + + + + + Value range - maximum value + + + + + + Value range - step value + + + + + + Enumerated value - number of text items + + + + + + Value of control stored to a string delimited by + comma (,). + + + + + + + + + + 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". + + + + + + + + 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. + + + + + + + + Match against the value of an environment variable. Up + to five keys can be specified per rule. + Depending on the type of operator, this key is also used + to export a variable to the environment. + + + + + + + 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. + Several buildin commands are available: + + + + + 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"). + + + + + + Search for a controls and return total count + of matched ones. The CTL{name} key might contain match + characters * and ?. + + + + + + + + + + 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. + + + + + + Most of the fields support a shell style pattern matching. + The following pattern characters are supported: + + + + + Matches zero, or any number of characters. + + + + + + Matches any single character. + + + + + + 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. + + + + + The following keys can get values assigned: + + + , , + , , + , , + + + Select universal control element. + + + + + + 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). + + + + + + + Export a variable to the environment. Depending on the type of operator, + this key is also to match against an environment variable. + + + + + + + Named label where a GOTO can jump to. + + + + + + + Jumps to the next LABEL with a matching name + + + + + + + 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". + + + + + + + + PRINT value to stdout. + + + + + + + PRINT value to stderr. + + + + + + + Exit immediately and set program exit code to value (should be integer). + + + + + + The , , + , , + , + 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: + + + , + + See CARDINFO{} for more details. + + + + + , + + See CTL{} for more details. + + + + + , + + 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. + + + + + , + + The value of an environment variable. + + + + + , + + 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: . + If the number is followed by the '+' char this part plus all remaining parts + of the result string are substituted: + + + + + , + + Root directory where sysfs file-system is mounted. + Ususally, this value is just "/sys". + + + + + + + The '%' character itself. + + + + + + + The '$' character itself. + + + + 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 + + + + AUTHOR + Written by Jaroslav Kysela perex@perex.cz + Some portions are written by Greg Kroah-Hartman greg@kroah.com and + Kay Sievers kay.sievers@vrfy.org. + + + + SEE ALSO + + alsactl1 + + + +
+
diff --git a/alsactl/init/00main b/alsactl/init/00main new file mode 100644 index 0000000..daac952 --- /dev/null +++ b/alsactl/init/00main @@ -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 index 0000000..7621c67 --- /dev/null +++ b/alsactl/init/hda @@ -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 index 0000000..60d9b1c --- /dev/null +++ b/alsactl/init/help @@ -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 index 0000000..a4fea19 --- /dev/null +++ b/alsactl/init/info @@ -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 index 0000000..d1f4f11 --- /dev/null +++ b/alsactl/init/test @@ -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 index 0000000..542ee74 --- /dev/null +++ b/alsactl/init_parse.c @@ -0,0 +1,1512 @@ +/* + * Advanced Linux Sound Architecture Control Program - Parse initialization files + * Copyright (c) by Jaroslav Kysela , + * Greg Kroah-Hartman , + * Kay Sievers + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 index 0000000..f263138 --- /dev/null +++ b/alsactl/init_sysdeps.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003 Greg Kroah-Hartman + * Copyright (C) 2005-2006 Kay Sievers + * + * 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 index 0000000..0cbada2 --- /dev/null +++ b/alsactl/init_sysfs.c @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2005-2006 Kay Sievers + * 2008 Jaroslav Kysela + * + * 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 index 0000000..dde490b --- /dev/null +++ b/alsactl/init_utils_run.c @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2004-2005 Kay Sievers + * + * 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 index 0000000..2598e9f --- /dev/null +++ b/alsactl/init_utils_string.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2004-2005 Kay Sievers + * + * 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 index 0000000..8626630 --- /dev/null +++ b/alsactl/list.h @@ -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 */ diff --git a/alsactl/state.c b/alsactl/state.c index 6c7f853..90e58cd 100644 --- a/alsactl/state.c +++ b/alsactl/state.c @@ -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 index 0000000..eb22ecc --- /dev/null +++ b/alsactl/utils.c @@ -0,0 +1,79 @@ +/* + * Advanced Linux Sound Architecture Control Program - Support routines + * Copyright (c) by Jaroslav Kysela + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +}