Home | History | Annotate | Download | only in alsactl
      1 /*
      2  *  Advanced Linux Sound Architecture Control Program - Parse initialization files
      3  *  Copyright (c) by Jaroslav Kysela <perex (at) perex.cz>,
      4  *		     Greg Kroah-Hartman <greg (at) kroah.com>,
      5  *		     Kay Sievers <kay.sievers (at) vrfy.org>
      6  *
      7  *
      8  *   This program is free software; you can redistribute it and/or modify
      9  *   it under the terms of the GNU General Public License as published by
     10  *   the Free Software Foundation; either version 2 of the License, or
     11  *   (at your option) any later version.
     12  *
     13  *   This program is distributed in the hope that it will be useful,
     14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  *   GNU General Public License for more details.
     17  *
     18  *   You should have received a copy of the GNU General Public License
     19  *   along with this program; if not, write to the Free Software
     20  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
     21  *
     22  */
     23 
     24 #include <stdlib.h>
     25 #include <stdio.h>
     26 #include <stddef.h>
     27 #include <unistd.h>
     28 #include <string.h>
     29 #include <fcntl.h>
     30 #include <ctype.h>
     31 #include <errno.h>
     32 #include <fnmatch.h>
     33 #include <sys/stat.h>
     34 #include <sys/socket.h>
     35 #include <sys/un.h>
     36 #include <sys/wait.h>
     37 #include <sys/select.h>
     38 #include <sys/types.h>
     39 #include <dirent.h>
     40 #include <math.h>
     41 #include <alsa/asoundlib.h>
     42 #include "aconfig.h"
     43 #include "alsactl.h"
     44 #include "list.h"
     45 
     46 #define PATH_SIZE	512
     47 #define NAME_SIZE	128
     48 #define EJUSTRETURN	0x7fffffff
     49 
     50 enum key_op {
     51 	KEY_OP_UNSET,
     52 	KEY_OP_MATCH,
     53 	KEY_OP_NOMATCH,
     54 	KEY_OP_ADD,
     55 	KEY_OP_ASSIGN,
     56 	KEY_OP_ASSIGN_FINAL
     57 };
     58 
     59 struct pair {
     60 	char *key;
     61 	char *value;
     62 	struct pair *next;
     63 };
     64 
     65 struct space {
     66 	struct pair *pairs;
     67 	char *rootdir;
     68 	char *go_to;
     69 	char *program_result;
     70 	const char *filename;
     71 	int linenum;
     72 	int log_run;
     73 	int exit_code;
     74 	int quit;
     75 	unsigned int ctl_id_changed;
     76 	snd_hctl_t *ctl_handle;
     77 	snd_ctl_card_info_t *ctl_card_info;
     78 	snd_ctl_elem_id_t *ctl_id;
     79 	snd_ctl_elem_info_t *ctl_info;
     80 	snd_ctl_elem_value_t *ctl_value;
     81 };
     82 
     83 static void Perror(struct space *space, const char *fmt, ...)
     84 {
     85 	va_list arg;
     86 	va_start(arg, fmt);
     87 	fprintf(stderr, "%s:%i: ", space->filename, space->linenum);
     88 	vfprintf(stderr, fmt, arg);
     89 	putc('\n', stderr);
     90 	va_end(arg);
     91 }
     92 
     93 #include "init_sysdeps.c"
     94 #include "init_utils_string.c"
     95 #include "init_utils_run.c"
     96 #include "init_sysfs.c"
     97 
     98 static void free_space(struct space *space)
     99 {
    100 	struct pair *pair = space->pairs;
    101 	struct pair *next = pair;
    102 
    103 	while (next) {
    104 		pair = next;
    105 		next = pair->next;
    106 		free(pair->value);
    107 		free(pair->key);
    108 		free(pair);
    109 	}
    110 	space->pairs = NULL;
    111 	if (space->ctl_value) {
    112 		snd_ctl_elem_value_free(space->ctl_value);
    113 		space->ctl_value = NULL;
    114 	}
    115 	if (space->ctl_info) {
    116 		snd_ctl_elem_info_free(space->ctl_info);
    117 		space->ctl_info = NULL;
    118 	}
    119 	if (space->ctl_id) {
    120 		snd_ctl_elem_id_free(space->ctl_id);
    121 		space->ctl_id = NULL;
    122 	}
    123 	if (space->ctl_card_info) {
    124 		snd_ctl_card_info_free(space->ctl_card_info);
    125 		space->ctl_card_info = NULL;
    126 	}
    127 	if (space->ctl_handle) {
    128 		snd_hctl_close(space->ctl_handle);
    129 		space->ctl_handle = NULL;
    130 	}
    131 	if (space->rootdir)
    132 		free(space->rootdir);
    133 	if (space->program_result)
    134 		free(space->program_result);
    135 	if (space->go_to)
    136 		free(space->go_to);
    137 	free(space);
    138 }
    139 
    140 static struct pair *value_find(struct space *space, const char *key)
    141 {
    142 	struct pair *pair = space->pairs;
    143 
    144 	while (pair && strcmp(pair->key, key) != 0)
    145 		pair = pair->next;
    146 	return pair;
    147 }
    148 
    149 static int value_set(struct space *space, const char *key, const char *value)
    150 {
    151 	struct pair *pair;
    152 
    153 	pair = value_find(space, key);
    154 	if (pair) {
    155 		free(pair->value);
    156 		pair->value = strdup(value);
    157 		if (pair->value == NULL)
    158 			return -ENOMEM;
    159 	} else {
    160 		pair = malloc(sizeof(struct pair));
    161 		if (pair == NULL)
    162 			return -ENOMEM;
    163 		pair->key = strdup(key);
    164 		if (pair->key == NULL) {
    165 			free(pair);
    166 			return -ENOMEM;
    167 		}
    168 		pair->value = strdup(value);
    169 		if (pair->value == NULL) {
    170 			free(pair->key);
    171 			free(pair);
    172 			return -ENOMEM;
    173 		}
    174 		pair->next = space->pairs;
    175 		space->pairs = pair;
    176 	}
    177 	return 0;
    178 }
    179 
    180 static int init_space(struct space **space, int card)
    181 {
    182 	struct space *res;
    183 	char device[16];
    184 	int err;
    185 
    186 	res = calloc(1, sizeof(struct space));
    187 	if (res == NULL)
    188 		return -ENOMEM;
    189 	res->ctl_id_changed = ~0;
    190 	res->linenum = -1;
    191 	sprintf(device, "hw:%u", card);
    192 	err = snd_hctl_open(&res->ctl_handle, device, 0);
    193 	if (err < 0)
    194 		goto error;
    195 	err = snd_hctl_load(res->ctl_handle);
    196 	if (err < 0)
    197 		goto error;
    198 	err = snd_ctl_card_info_malloc(&res->ctl_card_info);
    199 	if (err < 0)
    200 		goto error;
    201 	err = snd_ctl_card_info(snd_hctl_ctl(res->ctl_handle), res->ctl_card_info);
    202 	if (err < 0)
    203 		goto error;
    204 	err = snd_ctl_elem_id_malloc(&res->ctl_id);
    205 	if (err < 0)
    206 		goto error;
    207 	err = snd_ctl_elem_info_malloc(&res->ctl_info);
    208 	if (err < 0)
    209 		goto error;
    210 	err = snd_ctl_elem_value_malloc(&res->ctl_value);
    211 	if (err < 0)
    212 		goto error;
    213 	*space = res;
    214 	return 0;
    215  error:
    216  	free_space(res);
    217  	return err;
    218 }
    219 
    220 static const char *cardinfo_get(struct space *space, const char *attr)
    221 {
    222 	if (strncasecmp(attr, "CARD", 4) == 0) {
    223 		static char res[16];
    224 		sprintf(res, "%u", snd_ctl_card_info_get_card(space->ctl_card_info));
    225 		return res;
    226 	}
    227 	if (strncasecmp(attr, "ID", 2) == 0)
    228 		return snd_ctl_card_info_get_id(space->ctl_card_info);
    229 	if (strncasecmp(attr, "DRIVER", 6) == 0)
    230 		return snd_ctl_card_info_get_driver(space->ctl_card_info);
    231 	if (strncasecmp(attr, "NAME", 4) == 0)
    232 		return snd_ctl_card_info_get_name(space->ctl_card_info);
    233 	if (strncasecmp(attr, "LONGNAME", 8) == 0)
    234 		return snd_ctl_card_info_get_longname(space->ctl_card_info);
    235 	if (strncasecmp(attr, "MIXERNAME", 9) == 0)
    236 		return snd_ctl_card_info_get_mixername(space->ctl_card_info);
    237 	if (strncasecmp(attr, "COMPONENTS", 10) == 0)
    238 		return snd_ctl_card_info_get_components(space->ctl_card_info);
    239 	Perror(space, "unknown cardinfo{} attribute '%s'", attr);
    240 	return NULL;
    241 }
    242 
    243 static int check_id_changed(struct space *space, unsigned int what)
    244 {
    245 	snd_hctl_elem_t *elem;
    246 	int err;
    247 
    248 	if ((space->ctl_id_changed & what & 1) != 0) {
    249 		snd_ctl_elem_id_set_numid(space->ctl_id, 0);
    250 		elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
    251 		if (!elem)
    252 			return -ENOENT;
    253 		err = snd_hctl_elem_info(elem, space->ctl_info);
    254 		if (err == 0)
    255 			space->ctl_id_changed &= ~1;
    256 		return err;
    257 	}
    258 	if ((space->ctl_id_changed & what & 2) != 0) {
    259 		snd_ctl_elem_id_set_numid(space->ctl_id, 0);
    260 		elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
    261 		if (!elem)
    262 			return -ENOENT;
    263 		err = snd_hctl_elem_read(elem, space->ctl_value);
    264 		if (err == 0)
    265 			space->ctl_id_changed &= ~2;
    266 		return err;
    267 	}
    268 	return 0;
    269 }
    270 
    271 static const char *get_ctl_value(struct space *space)
    272 {
    273 	snd_ctl_elem_type_t type;
    274 	unsigned int idx, count;
    275 	static char res[1024], tmp[16];
    276 	static const char hex[] = "0123456789abcdef";
    277 	char *pos;
    278 	const char *pos1;
    279 
    280 	type = snd_ctl_elem_info_get_type(space->ctl_info);
    281 	count = snd_ctl_elem_info_get_count(space->ctl_info);
    282 	res[0] = '\0';
    283 	switch (type) {
    284 	case SND_CTL_ELEM_TYPE_BOOLEAN:
    285 		for (idx = 0; idx < count; idx++) {
    286 			if (idx > 0)
    287 				strlcat(res, ",", sizeof(res));
    288 			strlcat(res, snd_ctl_elem_value_get_boolean(space->ctl_value, idx) ? "on" : "off", sizeof(res));
    289 		}
    290 		break;
    291 	case SND_CTL_ELEM_TYPE_INTEGER:
    292 		for (idx = 0; idx < count; idx++) {
    293 			if (idx > 0)
    294 				strlcat(res, ",", sizeof(res));
    295 			snprintf(tmp, sizeof(tmp), "%li", snd_ctl_elem_value_get_integer(space->ctl_value, idx));
    296 			strlcat(res, tmp, sizeof(res));
    297 		}
    298 		break;
    299 	case SND_CTL_ELEM_TYPE_INTEGER64:
    300 		for (idx = 0; idx < count; idx++) {
    301 			if (idx > 0)
    302 				strlcat(res, ",", sizeof(res));
    303 			snprintf(tmp, sizeof(tmp), "%lli", snd_ctl_elem_value_get_integer64(space->ctl_value, idx));
    304 			strlcat(res, tmp, sizeof(res));
    305 		}
    306 		break;
    307 	case SND_CTL_ELEM_TYPE_ENUMERATED:
    308 		for (idx = 0; idx < count; idx++) {
    309 			if (idx > 0)
    310 				strlcat(res, ",", sizeof(res));
    311 			snprintf(tmp, sizeof(tmp), "%u", snd_ctl_elem_value_get_enumerated(space->ctl_value, idx));
    312 			strlcat(res, tmp, sizeof(res));
    313 		}
    314 		break;
    315 	case SND_CTL_ELEM_TYPE_BYTES:
    316 	case SND_CTL_ELEM_TYPE_IEC958:
    317 		if (type == SND_CTL_ELEM_TYPE_IEC958)
    318 			count = sizeof(snd_aes_iec958_t);
    319 		if (count > (sizeof(res)-1)/2)
    320 			count = (sizeof(res)-1/2);
    321 		pos = res;
    322 		pos1 = snd_ctl_elem_value_get_bytes(space->ctl_value);
    323 		while (count > 0) {
    324 			idx = *pos1++;
    325 			*pos++ = hex[idx >> 4];
    326 			*pos++ = hex[idx & 0x0f];
    327 			count++;
    328 		}
    329 		*pos++ = '\0';
    330 		break;
    331 	default:
    332 		Perror(space, "unknown element type '%i'", type);
    333 		return NULL;
    334 	}
    335 	return res;
    336 }
    337 
    338 /* Function to convert from percentage to volume. val = percentage */
    339 #define convert_prange1(val, min, max) \
    340         ceil((val) * ((max) - (min)) * 0.01 + (min))
    341 
    342 static int set_ctl_value(struct space *space, const char *value, int all)
    343 {
    344 	snd_ctl_elem_type_t type;
    345 	unsigned int idx, idx2, count, items;
    346 	const char *pos, *pos2;
    347 	snd_hctl_elem_t *elem;
    348 	int val;
    349 	long lval;
    350 
    351 	type = snd_ctl_elem_info_get_type(space->ctl_info);
    352 	count = snd_ctl_elem_info_get_count(space->ctl_info);
    353 	switch (type) {
    354 	case SND_CTL_ELEM_TYPE_BOOLEAN:
    355 		for (idx = 0; idx < count; idx++) {
    356 			while (*value == ' ')
    357 				value++;
    358 			if (*value == '\0')
    359 				goto missing;
    360 			val = strncasecmp(value, "true", 4) == 0 ||
    361 				strncasecmp(value, "yes", 3) == 0 ||
    362 				strncasecmp(value, "on", 2) == 0 ||
    363 				strncasecmp(value, "1", 1) == 0;
    364 			snd_ctl_elem_value_set_boolean(space->ctl_value, idx, val);
    365 			if (all)
    366 				continue;
    367 			pos = strchr(value, ',');
    368 			value = pos ? pos + 1 : value + strlen(value) - 1;
    369 		}
    370 		break;
    371 	case SND_CTL_ELEM_TYPE_INTEGER:
    372 		for (idx = 0; idx < count; idx++) {
    373 			while (*value == ' ')
    374 				value++;
    375 			pos = strchr(value, ',');
    376 			if (pos)
    377 				*(char *)pos = '\0';
    378 			remove_trailing_chars((char *)value, ' ');
    379 			items = pos ? pos - value : strlen(value);
    380 			if (items > 1 && value[items-1] == '%') {
    381 				val = convert_prange1(strtol(value, NULL, 0), snd_ctl_elem_info_get_min(space->ctl_info), snd_ctl_elem_info_get_max(space->ctl_info));
    382 				snd_ctl_elem_value_set_integer(space->ctl_value, idx, val);
    383 			} else if (items > 2 && value[items-2] == 'd' && value[items-1] == 'B') {
    384 				val = strtol(value, NULL, 0) * 100;
    385 				if ((pos2 = index(value, '.')) != NULL) {
    386 					if (isdigit(*(pos2-1)) && isdigit(*(pos2-2))) {
    387 						if (val < 0)
    388 							val -= strtol(pos2 + 1, NULL, 0);
    389 						else
    390 							val += strtol(pos2 + 1, NULL, 0);
    391 					} else if (isdigit(*(pos2-1))) {
    392 						if (val < 0)
    393 							val -= strtol(pos2 + 1, NULL, 0) * 10;
    394 						else
    395 							val += strtol(pos2 + 1, NULL, 0) * 10;
    396 					}
    397 				}
    398 				val = snd_ctl_convert_from_dB(snd_hctl_ctl(space->ctl_handle), space->ctl_id, val, &lval, -1);
    399 				if (val < 0) {
    400 					Perror(space, "unable to convert dB value '%s' to internal integer range", value);
    401 					return val;
    402 				}
    403 				snd_ctl_elem_value_set_integer(space->ctl_value, idx, lval);
    404 			} else {
    405 				snd_ctl_elem_value_set_integer(space->ctl_value, idx, strtol(value, NULL, 0));
    406 			}
    407 			if (all)
    408 				continue;
    409 			value = pos ? pos + 1 : value + strlen(value) - 1;
    410 		}
    411 		break;
    412 	case SND_CTL_ELEM_TYPE_INTEGER64:
    413 		for (idx = 0; idx < count; idx++) {
    414 			while (*value == ' ')
    415 				value++;
    416 			snd_ctl_elem_value_set_integer64(space->ctl_value, idx, strtoll(value, NULL, 0));
    417 			if (all)
    418 				continue;
    419 			pos = strchr(value, ',');
    420 			value = pos ? pos + 1 : value + strlen(value) - 1;
    421 		}
    422 		break;
    423 	case SND_CTL_ELEM_TYPE_ENUMERATED:
    424 		for (idx = 0; idx < count; idx++) {
    425 			while (*value == ' ')
    426 				value++;
    427 			pos = strchr(value, ',');
    428 			if (isdigit(value[0]) || value[0] == '-') {
    429 				snd_ctl_elem_value_set_enumerated(space->ctl_value, idx, strtol(value, NULL, 0));
    430 			} else {
    431 				if (pos)
    432 					*(char *)pos = '\0';
    433 				remove_trailing_chars((char *)value, ' ');
    434 				items = snd_ctl_elem_info_get_items(space->ctl_info);
    435 				for (idx2 = 0; idx2 < items; idx2++) {
    436 					snd_ctl_elem_info_set_item(space->ctl_info, idx2);
    437 					elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
    438 					if (elem == NULL)
    439 						return -ENOENT;
    440 					val = snd_hctl_elem_info(elem, space->ctl_info);
    441 					if (val < 0)
    442 						return val;
    443 					if (strcasecmp(snd_ctl_elem_info_get_item_name(space->ctl_info), value) == 0) {
    444 						snd_ctl_elem_value_set_enumerated(space->ctl_value, idx, idx2);
    445 						break;
    446 					}
    447 				}
    448 				if (idx2 >= items) {
    449 					Perror(space, "wrong enum identifier '%s'", value);
    450 					return -EINVAL;
    451 				}
    452 			}
    453 			if (all)
    454 				continue;
    455 			value = pos ? pos + 1 : value + strlen(value) - 1;
    456 		}
    457 		break;
    458 	case SND_CTL_ELEM_TYPE_BYTES:
    459 	case SND_CTL_ELEM_TYPE_IEC958:
    460 		if (type == SND_CTL_ELEM_TYPE_IEC958)
    461 			count = sizeof(snd_aes_iec958_t);
    462 		while (*value == ' ')
    463 			value++;
    464 		if (strlen(value) != count * 2) {
    465 			Perror(space, "bad ctl value hexa length (should be %u bytes)", count);
    466 			return -EINVAL;
    467 		}
    468 		for (idx = 0; idx < count; idx += 2) {
    469 			val = hextodigit(*(value++)) << 4;
    470 			val |= hextodigit(*(value++));
    471 			if (val > 255) {
    472 				Perror(space, "bad ctl hexa value");
    473 				return -EINVAL;
    474 			}
    475 			snd_ctl_elem_value_set_byte(space->ctl_value, idx, val);
    476 		}
    477 		break;
    478 	default:
    479 		Perror(space, "unknown element type '%i'", type);
    480 		return -EINVAL;
    481 	}
    482 	return 0;
    483   missing:
    484   	printf("%i %i\n", type, count);
    485   	Perror(space, "missing some ctl values (line %i)", space->linenum);
    486   	return -EINVAL;
    487 }
    488 
    489 static const char *elemid_get(struct space *space, const char *attr)
    490 {
    491 	long long val;
    492 	snd_ctl_elem_type_t type;
    493 	static char res[256];
    494 
    495 	if (strncasecmp(attr, "numid", 5) == 0) {
    496 		val = snd_ctl_elem_id_get_numid(space->ctl_id);
    497 	    	goto value;
    498 	}
    499 	if (strncasecmp(attr, "iface", 5) == 0 ||
    500 	    strncasecmp(attr, "interface", 9) == 0)
    501 	    	return snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(space->ctl_id));
    502 	if (strncasecmp(attr, "device", 6) == 0) {
    503 		val = snd_ctl_elem_id_get_device(space->ctl_id);
    504 	    	goto value;
    505 	}
    506 	if (strncasecmp(attr, "subdev", 6) == 0) {
    507 		val = snd_ctl_elem_id_get_subdevice(space->ctl_id);
    508 	    	goto value;
    509 	}
    510 	if (strncasecmp(attr, "name", 4) == 0)
    511 		return snd_ctl_elem_id_get_name(space->ctl_id);
    512 	if (strncasecmp(attr, "index", 5) == 0) {
    513 		val = snd_ctl_elem_id_get_index(space->ctl_id);
    514 	    	goto value;
    515 	}
    516 	if (strncasecmp(attr, "type", 4) == 0) {
    517 		if (check_id_changed(space, 1))
    518 			return NULL;
    519 		return snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(space->ctl_info));
    520 	}
    521 	if (strncasecmp(attr, "attr", 4) == 0) {
    522 		if (check_id_changed(space, 1))
    523 			return NULL;
    524 		res[0] = '\0';
    525 		if (snd_ctl_elem_info_is_readable(space->ctl_info))
    526 			strcat(res, "r");
    527 		if (snd_ctl_elem_info_is_writable(space->ctl_info))
    528 			strcat(res, "w");
    529 		if (snd_ctl_elem_info_is_volatile(space->ctl_info))
    530 			strcat(res, "v");
    531 		if (snd_ctl_elem_info_is_inactive(space->ctl_info))
    532 			strcat(res, "i");
    533 		if (snd_ctl_elem_info_is_locked(space->ctl_info))
    534 			strcat(res, "l");
    535 		if (snd_ctl_elem_info_is_tlv_readable(space->ctl_info))
    536 			strcat(res, "R");
    537 		if (snd_ctl_elem_info_is_tlv_writable(space->ctl_info))
    538 			strcat(res, "W");
    539 		if (snd_ctl_elem_info_is_tlv_commandable(space->ctl_info))
    540 			strcat(res, "C");
    541 		if (snd_ctl_elem_info_is_owner(space->ctl_info))
    542 			strcat(res, "o");
    543 		if (snd_ctl_elem_info_is_user(space->ctl_info))
    544 			strcat(res, "u");
    545 		return res;
    546 	}
    547 	if (strncasecmp(attr, "owner", 5) == 0) {
    548 		if (check_id_changed(space, 1))
    549 			return NULL;
    550 		val = snd_ctl_elem_info_get_owner(space->ctl_info);
    551 		goto value;
    552 	}
    553 	if (strncasecmp(attr, "count", 5) == 0) {
    554 		if (check_id_changed(space, 1))
    555 			return NULL;
    556 		val = snd_ctl_elem_info_get_count(space->ctl_info);
    557 		goto value;
    558 	}
    559 	if (strncasecmp(attr, "min", 3) == 0) {
    560 		if (check_id_changed(space, 1))
    561 			return NULL;
    562 		type = snd_ctl_elem_info_get_type(space->ctl_info);
    563 		if (type == SND_CTL_ELEM_TYPE_INTEGER64)
    564 			val = snd_ctl_elem_info_get_min64(space->ctl_info);
    565 		else if (type == SND_CTL_ELEM_TYPE_INTEGER)
    566 			val = snd_ctl_elem_info_get_min(space->ctl_info);
    567 		else
    568 			goto empty;
    569 		goto value;
    570 	}
    571 	if (strncasecmp(attr, "max", 3) == 0) {
    572 		if (check_id_changed(space, 1))
    573 			return NULL;
    574 		type = snd_ctl_elem_info_get_type(space->ctl_info);
    575 		if (type == SND_CTL_ELEM_TYPE_INTEGER64)
    576 			val = snd_ctl_elem_info_get_max64(space->ctl_info);
    577 		else if (type == SND_CTL_ELEM_TYPE_INTEGER)
    578 			val = snd_ctl_elem_info_get_max(space->ctl_info);
    579 		else
    580 			goto empty;
    581 		goto value;
    582 	}
    583 	if (strncasecmp(attr, "step", 3) == 0) {
    584 		if (check_id_changed(space, 1))
    585 			return NULL;
    586 		type = snd_ctl_elem_info_get_type(space->ctl_info);
    587 		if (type == SND_CTL_ELEM_TYPE_INTEGER64)
    588 			val = snd_ctl_elem_info_get_step64(space->ctl_info);
    589 		else if (type == SND_CTL_ELEM_TYPE_INTEGER)
    590 			val = snd_ctl_elem_info_get_step(space->ctl_info);
    591 		else
    592 			goto empty;
    593 		goto value;
    594 	}
    595 	if (strncasecmp(attr, "items", 5) == 0) {
    596 		if (check_id_changed(space, 1))
    597 			return NULL;
    598 		if (snd_ctl_elem_info_get_type(space->ctl_info) == SND_CTL_ELEM_TYPE_ENUMERATED)
    599 			val = snd_ctl_elem_info_get_items(space->ctl_info);
    600 		else {
    601 		  empty:
    602 			res[0] = '\0';
    603 			return res;
    604 		}
    605 		goto value;
    606 	}
    607 	if (strncasecmp(attr, "value", 5) == 0) {
    608 		if (check_id_changed(space, 3))
    609 			return NULL;
    610 		return get_ctl_value(space);
    611 	}
    612 	if (strncasecmp(attr, "dBmin", 5) == 0) {
    613 		long min, max;
    614 		if (check_id_changed(space, 1))
    615 			return NULL;
    616 		if (snd_ctl_get_dB_range(snd_hctl_ctl(space->ctl_handle), space->ctl_id, &min, &max) < 0)
    617 			goto empty;
    618 		val = min;
    619 dbvalue:
    620 		sprintf(res, "%li.%02idB", (long)(val / 100), (int)abs(val % 100));
    621 		return res;
    622 	}
    623 	if (strncasecmp(attr, "dBmax", 5) == 0) {
    624 		long min, max;
    625 		if (check_id_changed(space, 1))
    626 			return NULL;
    627 		if (snd_ctl_get_dB_range(snd_hctl_ctl(space->ctl_handle), space->ctl_id, &min, &max) < 0)
    628 			goto empty;
    629 		val = max;
    630 		goto dbvalue;
    631 	}
    632 	if (strncasecmp(attr, "enums", 5) == 0) {
    633 		unsigned int idx, items;
    634 		snd_hctl_elem_t *elem;
    635 		if (check_id_changed(space, 1))
    636 			return NULL;
    637 		if (snd_ctl_elem_info_get_type(space->ctl_info) != SND_CTL_ELEM_TYPE_ENUMERATED)
    638 			goto empty;
    639 		items = snd_ctl_elem_info_get_items(space->ctl_info);
    640 		strcpy(res, "|");
    641 		for (idx = 0; idx < items; idx++) {
    642 			snd_ctl_elem_info_set_item(space->ctl_info, idx);
    643 			elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
    644 			if (elem == NULL)
    645 				break;
    646 			if (snd_hctl_elem_info(elem, space->ctl_info) < 0)
    647 				break;
    648 			strlcat(res, snd_ctl_elem_info_get_item_name(space->ctl_info), sizeof(res));
    649 			strlcat(res, "|", sizeof(res));
    650 		}
    651 		return res;
    652 	}
    653 	Perror(space, "unknown ctl{} attribute '%s'", attr);
    654 	return NULL;
    655   value:
    656   	sprintf(res, "%lli", val);
    657   	return res;
    658 }
    659 
    660 static int elemid_set(struct space *space, const char *attr, const char *value)
    661 {
    662 	unsigned int val;
    663 	void (*fcn)(snd_ctl_elem_id_t *, unsigned int);
    664 	snd_ctl_elem_iface_t iface;
    665 	int err;
    666 
    667 	if (strncasecmp(attr, "numid", 5) == 0) {
    668 		fcn = snd_ctl_elem_id_set_numid;
    669 	    	goto value;
    670 	}
    671 	if (strncasecmp(attr, "iface", 5) == 0 ||
    672 	    strncasecmp(attr, "interface", 9) == 0 ||
    673 	    strncasecmp(attr, "reset", 5) == 0 ||
    674 	    strncasecmp(attr, "search", 6) == 0) {
    675 	    	if (strlen(value) == 0 && strncasecmp(attr, "search", 6) == 0) {
    676 	    		iface = 0;
    677 	    		goto search;
    678 		}
    679 	    	for (iface = 0; iface <= SND_CTL_ELEM_IFACE_LAST; iface++) {
    680 	    		if (strcasecmp(value, snd_ctl_elem_iface_name(iface)) == 0) {
    681 			    	if (strncasecmp(attr, "reset", 5) == 0)
    682 			    		snd_ctl_elem_id_clear(space->ctl_id);
    683 			    	if (strncasecmp(attr, "search", 5) == 0) {
    684 			    	  search:
    685 			    		snd_ctl_elem_id_clear(space->ctl_id);
    686 			    		/* -1 means all */
    687 			    		snd_ctl_elem_id_set_interface(space->ctl_id, -1);
    688 			    		snd_ctl_elem_id_set_device(space->ctl_id, -1);
    689 			    		snd_ctl_elem_id_set_subdevice(space->ctl_id, -1);
    690 			    		snd_ctl_elem_id_set_name(space->ctl_id, "*");
    691 			    		snd_ctl_elem_id_set_index(space->ctl_id, -1);
    692 			    		if (strlen(value) == 0)
    693 			    			return 0;
    694 				}
    695 				snd_ctl_elem_id_set_interface(space->ctl_id, iface);
    696 				space->ctl_id_changed = ~0;
    697 			    	return 0;
    698 			}
    699 		}
    700 		Perror(space, "unknown control interface name '%s'", value);
    701 		return -EINVAL;
    702 	}
    703 	if (strncasecmp(attr, "device", 6) == 0) {
    704 		fcn = snd_ctl_elem_id_set_device;
    705 	    	goto value;
    706 	}
    707 	if (strncasecmp(attr, "subdev", 6) == 0) {
    708 		fcn = snd_ctl_elem_id_set_subdevice;
    709 	    	goto value;
    710 	}
    711 	if (strncasecmp(attr, "name", 4) == 0) {
    712 		snd_ctl_elem_id_set_name(space->ctl_id, value);
    713 	  	space->ctl_id_changed = ~0;
    714 		return 0;
    715 	}
    716 	if (strncasecmp(attr, "index", 5) == 0) {
    717 		fcn = snd_ctl_elem_id_set_index;
    718 	    	goto value;
    719 	}
    720 	if (strncasecmp(attr, "values", 6) == 0 ||
    721 	    strncasecmp(attr, "value", 5) == 0) {
    722 		err = check_id_changed(space, 1);
    723 		if (err < 0) {
    724 			Perror(space, "control element not found");
    725 			return err;
    726 		}
    727 		err = set_ctl_value(space, value, strncasecmp(attr, "values", 6) == 0);
    728 		if (err < 0) {
    729 			space->ctl_id_changed |= 2;
    730 		} else {
    731 			space->ctl_id_changed &= ~2;
    732 			snd_ctl_elem_value_set_id(space->ctl_value, space->ctl_id);
    733 			err = snd_ctl_elem_write(snd_hctl_ctl(space->ctl_handle), space->ctl_value);
    734 			if (err < 0) {
    735 				Perror(space, "value write error: %s", snd_strerror(err));
    736 				return err;
    737 			}
    738 		}
    739 	    	return err;
    740 	}
    741 	Perror(space, "unknown CTL{} attribute '%s'", attr);
    742 	return -EINVAL;
    743   value:
    744   	val = (unsigned int)strtol(value, NULL, 0);
    745   	fcn(space->ctl_id, val);
    746   	space->ctl_id_changed = ~0;
    747   	return 0;
    748 }
    749 
    750 static int get_key(char **line, char **key, enum key_op *op, char **value)
    751 {
    752 	char *linepos;
    753 	char *temp;
    754 
    755 	linepos = *line;
    756 	if (linepos == NULL && linepos[0] == '\0')
    757 		return -EINVAL;
    758 
    759 	/* skip whitespace */
    760 	while (isspace(linepos[0]) || linepos[0] == ',')
    761 		linepos++;
    762 
    763 	/* get the key */
    764 	if (linepos[0] == '\0')
    765 		return -EINVAL;
    766 	*key = linepos;
    767 
    768 	while (1) {
    769 		linepos++;
    770 		if (linepos[0] == '\0')
    771 			return -1;
    772 		if (isspace(linepos[0]))
    773 			break;
    774 		if (linepos[0] == '=')
    775 			break;
    776 		if (linepos[0] == '+')
    777 			break;
    778 		if (linepos[0] == '!')
    779 			break;
    780 		if (linepos[0] == ':')
    781 			break;
    782 	}
    783 
    784 	/* remember end of key */
    785 	temp = linepos;
    786 
    787 	/* skip whitespace after key */
    788 	while (isspace(linepos[0]))
    789 		linepos++;
    790 	if (linepos[0] == '\0')
    791 		return -EINVAL;
    792 
    793 	/* get operation type */
    794 	if (linepos[0] == '=' && linepos[1] == '=') {
    795 		*op = KEY_OP_MATCH;
    796 		linepos += 2;
    797 		dbg("operator=match");
    798 	} else if (linepos[0] == '!' && linepos[1] == '=') {
    799 		*op = KEY_OP_NOMATCH;
    800 		linepos += 2;
    801 		dbg("operator=nomatch");
    802 	} else if (linepos[0] == '+' && linepos[1] == '=') {
    803 		*op = KEY_OP_ADD;
    804 		linepos += 2;
    805 		dbg("operator=add");
    806 	} else if (linepos[0] == '=') {
    807 		*op = KEY_OP_ASSIGN;
    808 		linepos++;
    809 		dbg("operator=assign");
    810 	} else if (linepos[0] == ':' && linepos[1] == '=') {
    811 		*op = KEY_OP_ASSIGN_FINAL;
    812 		linepos += 2;
    813 		dbg("operator=assign_final");
    814 	} else
    815 		return -EINVAL;
    816 
    817 	/* terminate key */
    818 	temp[0] = '\0';
    819 	dbg("key='%s'", *key);
    820 
    821 	/* skip whitespace after operator */
    822 	while (isspace(linepos[0]))
    823 		linepos++;
    824 	if (linepos[0] == '\0')
    825 		return -EINVAL;
    826 
    827 	/* get the value*/
    828 	if (linepos[0] != '"')
    829 		return -EINVAL;
    830 	linepos++;
    831 	*value = linepos;
    832 
    833 	while (1) {
    834 		temp = strchr(linepos, '"');
    835 		if (temp && temp[-1] == '\\') {
    836 			linepos = temp + 1;
    837 			continue;
    838 		}
    839 		break;
    840 	}
    841 	if (!temp)
    842 		return -EINVAL;
    843 	temp[0] = '\0';
    844 	temp++;
    845 	dbg("value='%s'", *value);
    846 
    847 	/* move line to next key */
    848 	*line = temp;
    849 
    850 	return 0;
    851 }
    852 
    853 /* extract possible KEY{attr} */
    854 static char *get_key_attribute(struct space *space, char *str, char *res, size_t ressize)
    855 {
    856 	char *pos;
    857 	char *attr;
    858 
    859 	attr = strchr(str, '{');
    860 	if (attr != NULL) {
    861 		attr++;
    862 		pos = strchr(attr, '}');
    863 		if (pos == NULL) {
    864 			Perror(space, "missing closing brace for format");
    865 			return NULL;
    866 		}
    867 		pos[0] = '\0';
    868 		strlcpy(res, attr, ressize);
    869 		pos[0] = '}';
    870 		dbg("attribute='%s'", res);
    871 		return res;
    872 	}
    873 
    874 	return NULL;
    875 }
    876 
    877 /* extract possible {attr} and move str behind it */
    878 static char *get_format_attribute(struct space *space, char **str)
    879 {
    880 	char *pos;
    881 	char *attr = NULL;
    882 
    883 	if (*str[0] == '{') {
    884 		pos = strchr(*str, '}');
    885 		if (pos == NULL) {
    886 			Perror(space, "missing closing brace for format");
    887 			return NULL;
    888 		}
    889 		pos[0] = '\0';
    890 		attr = *str+1;
    891 		*str = pos+1;
    892 		dbg("attribute='%s', str='%s'", attr, *str);
    893 	}
    894 	return attr;
    895 }
    896 
    897 /* extract possible format length and move str behind it*/
    898 static int get_format_len(struct space *space, char **str)
    899 {
    900 	int num;
    901 	char *tail;
    902 
    903 	if (isdigit(*str[0])) {
    904 		num = (int) strtoul(*str, &tail, 10);
    905 		if (num > 0) {
    906 			*str = tail;
    907 			dbg("format length=%i", num);
    908 			return num;
    909 		} else {
    910 			Perror(space, "format parsing error '%s'", *str);
    911 		}
    912 	}
    913 	return -1;
    914 }
    915 
    916 static void apply_format(struct space *space, char *string, size_t maxsize)
    917 {
    918 	char temp[PATH_SIZE];
    919 	char temp2[PATH_SIZE];
    920 	char *head, *tail, *pos, *cpos, *attr, *rest;
    921 	struct pair *pair;
    922 	int len;
    923 	int i;
    924 	int count;
    925 	enum subst_type {
    926 		SUBST_UNKNOWN,
    927 		SUBST_CARDINFO,
    928 		SUBST_CTL,
    929 		SUBST_RESULT,
    930 		SUBST_ATTR,
    931 		SUBST_SYSFSROOT,
    932 		SUBST_ENV,
    933 	};
    934 	static const struct subst_map {
    935 		char *name;
    936 		char fmt;
    937 		enum subst_type type;
    938 	} map[] = {
    939 		{ .name = "cardinfo",	.fmt = 'i',	.type = SUBST_CARDINFO },
    940 		{ .name = "ctl",	.fmt = 'C',	.type = SUBST_CTL },
    941 		{ .name = "result",	.fmt = 'c',	.type = SUBST_RESULT },
    942 		{ .name = "attr",	.fmt = 's',	.type = SUBST_ATTR },
    943 		{ .name = "sysfsroot",	.fmt = 'r',	.type = SUBST_SYSFSROOT },
    944 		{ .name = "env",	.fmt = 'E',	.type = SUBST_ENV },
    945 		{ NULL, '\0', 0 }
    946 	};
    947 	enum subst_type type;
    948 	const struct subst_map *subst;
    949 
    950 	head = string;
    951 	while (1) {
    952 		len = -1;
    953 		while (head[0] != '\0') {
    954 			if (head[0] == '$') {
    955 				/* substitute named variable */
    956 				if (head[1] == '\0')
    957 					break;
    958 				if (head[1] == '$') {
    959 					strlcpy(temp, head+2, sizeof(temp));
    960 					strlcpy(head+1, temp, maxsize);
    961 					head++;
    962 					continue;
    963 				}
    964 				head[0] = '\0';
    965 				for (subst = map; subst->name; subst++) {
    966 					if (strncasecmp(&head[1], subst->name, strlen(subst->name)) == 0) {
    967 						type = subst->type;
    968 						tail = head + strlen(subst->name)+1;
    969 						dbg("will substitute format name '%s'", subst->name);
    970 						goto found;
    971 					}
    972 				}
    973 			} else if (head[0] == '%') {
    974 				/* substitute format char */
    975 				if (head[1] == '\0')
    976 					break;
    977 				if (head[1] == '%') {
    978 					strlcpy(temp, head+2, sizeof(temp));
    979 					strlcpy(head+1, temp, maxsize);
    980 					head++;
    981 					continue;
    982 				}
    983 				head[0] = '\0';
    984 				tail = head+1;
    985 				len = get_format_len(space, &tail);
    986 				for (subst = map; subst->name; subst++) {
    987 					if (tail[0] == subst->fmt) {
    988 						type = subst->type;
    989 						tail++;
    990 						dbg("will substitute format char '%c'", subst->fmt);
    991 						goto found;
    992 					}
    993 				}
    994 			}
    995 			head++;
    996 		}
    997 		break;
    998 found:
    999 		attr = get_format_attribute(space, &tail);
   1000 		strlcpy(temp, tail, sizeof(temp));
   1001 		dbg("format=%i, string='%s', tail='%s'", type ,string, tail);
   1002 
   1003 		switch (type) {
   1004 		case SUBST_CARDINFO:
   1005 			if (attr == NULL)
   1006 				Perror(space, "missing identification parametr for cardinfo");
   1007 			else {
   1008 				const char *value = cardinfo_get(space, attr);
   1009 				if (value == NULL)
   1010 					break;
   1011 				strlcat(string, value, maxsize);
   1012 				dbg("substitute cardinfo{%s} '%s'", attr, value);
   1013 			}
   1014 			break;
   1015 		case SUBST_CTL:
   1016 			if (attr == NULL)
   1017 				Perror(space, "missing identification parametr for ctl");
   1018 			else {
   1019 				const char *value = elemid_get(space, attr);
   1020 				if (value == NULL)
   1021 					break;
   1022 				strlcat(string, value, maxsize);
   1023 				dbg("substitute ctl{%s} '%s'", attr, value);
   1024 			}
   1025 			break;
   1026 		case SUBST_RESULT:
   1027 			if (space->program_result == NULL)
   1028 				break;
   1029 			/* get part part of the result string */
   1030 			i = 0;
   1031 			if (attr != NULL)
   1032 				i = strtoul(attr, &rest, 10);
   1033 			if (i > 0) {
   1034 				dbg("request part #%d of result string", i);
   1035 				cpos = space->program_result;
   1036 				while (--i) {
   1037 					while (cpos[0] != '\0' && !isspace(cpos[0]))
   1038 						cpos++;
   1039 					while (isspace(cpos[0]))
   1040 						cpos++;
   1041 				}
   1042 				if (i > 0) {
   1043 					Perror(space, "requested part of result string not found");
   1044 					break;
   1045 				}
   1046 				strlcpy(temp2, cpos, sizeof(temp2));
   1047 				/* %{2+}c copies the whole string from the second part on */
   1048 				if (rest[0] != '+') {
   1049 					cpos = strchr(temp2, ' ');
   1050 					if (cpos)
   1051 						cpos[0] = '\0';
   1052 				}
   1053 				strlcat(string, temp2, maxsize);
   1054 				dbg("substitute part of result string '%s'", temp2);
   1055 			} else {
   1056 				strlcat(string, space->program_result, maxsize);
   1057 				dbg("substitute result string '%s'", space->program_result);
   1058 			}
   1059 			break;
   1060 		case SUBST_ATTR:
   1061 			if (attr == NULL)
   1062 				Perror(space, "missing file parameter for attr");
   1063 			else {
   1064 				const char *value = NULL;
   1065 				size_t size;
   1066 
   1067 				pair = value_find(space, "sysfs_device");
   1068 				if (pair == NULL)
   1069 					break;
   1070 				value = sysfs_attr_get_value(pair->value, attr);
   1071 
   1072 				if (value == NULL)
   1073 					break;
   1074 
   1075 				/* strip trailing whitespace and replace untrusted characters of sysfs value */
   1076 				size = strlcpy(temp2, value, sizeof(temp2));
   1077 				if (size >= sizeof(temp2))
   1078 					size = sizeof(temp2)-1;
   1079 				while (size > 0 && isspace(temp2[size-1]))
   1080 					temp2[--size] = '\0';
   1081 				count = replace_untrusted_chars(temp2);
   1082 				if (count > 0)
   1083 					Perror(space, "%i untrusted character(s) replaced" , count);
   1084 				strlcat(string, temp2, maxsize);
   1085 				dbg("substitute sysfs value '%s'", temp2);
   1086 			}
   1087 			break;
   1088 		case SUBST_SYSFSROOT:
   1089 			strlcat(string, sysfs_path, maxsize);
   1090 			dbg("substitute sysfs_path '%s'", sysfs_path);
   1091 			break;
   1092 		case SUBST_ENV:
   1093 			if (attr == NULL) {
   1094 				dbg("missing attribute");
   1095 				break;
   1096 			}
   1097 			pos = getenv(attr);
   1098 			if (pos == NULL) {
   1099 				dbg("env '%s' not available", attr);
   1100 				break;
   1101 			}
   1102 			dbg("substitute env '%s=%s'", attr, pos);
   1103 			strlcat(string, pos, maxsize);
   1104 			break;
   1105 		default:
   1106 			Perror(space, "unknown substitution type=%i", type);
   1107 			break;
   1108 		}
   1109 		/* possibly truncate to format-char specified length */
   1110 		if (len != -1) {
   1111 			head[len] = '\0';
   1112 			dbg("truncate to %i chars, subtitution string becomes '%s'", len, head);
   1113 		}
   1114 		strlcat(string, temp, maxsize);
   1115 	}
   1116 	/* unescape strings */
   1117 	head = tail = string;
   1118 	while (*head != '\0') {
   1119 		if (*head == '\\') {
   1120 			head++;
   1121 			if (*head == '\0')
   1122 				break;
   1123 			switch (*head) {
   1124 			case 'a': *tail++ = '\a'; break;
   1125 			case 'b': *tail++ = '\b'; break;
   1126 			case 'n': *tail++ = '\n'; break;
   1127 			case 'r': *tail++ = '\r'; break;
   1128 			case 't': *tail++ = '\t'; break;
   1129 			case 'v': *tail++ = '\v'; break;
   1130 			case '\\': *tail++ = '\\'; break;
   1131 			default: *tail++ = *head; break;
   1132 			}
   1133 			head++;
   1134 			continue;
   1135 		}
   1136 		if (*head)
   1137 			*tail++ = *head++;
   1138 	}
   1139 	*tail = 0;
   1140 }
   1141 
   1142 static int do_match(const char *key, enum key_op op,
   1143 		    const char *key_value, const char *value)
   1144 {
   1145 	int match;
   1146 
   1147 	if (value == NULL)
   1148 		return 0;
   1149 	dbg("match %s '%s' <-> '%s'", key, key_value, value);
   1150 	match = fnmatch(key_value, value, 0) == 0;
   1151 	if (match && op == KEY_OP_MATCH) {
   1152 		dbg("%s is true (matching value)", key);
   1153 		return 1;
   1154 	}
   1155 	if (!match && op == KEY_OP_NOMATCH) {
   1156 		dbg("%s is true (non-matching value)", key);
   1157 		return 1;
   1158 	}
   1159 	dbg("%s is false", key);
   1160 	return 0;
   1161 }
   1162 
   1163 static int ctl_match(snd_ctl_elem_id_t *pattern, snd_ctl_elem_id_t *id)
   1164 {
   1165 	if (snd_ctl_elem_id_get_interface(pattern) != -1 &&
   1166 	    snd_ctl_elem_id_get_interface(pattern) != snd_ctl_elem_id_get_interface(id))
   1167 	    	return 0;
   1168 	if (snd_ctl_elem_id_get_device(pattern) != -1 &&
   1169 	    snd_ctl_elem_id_get_device(pattern) != snd_ctl_elem_id_get_device(id))
   1170 		return 0;
   1171 	if (snd_ctl_elem_id_get_subdevice(pattern) != -1 &&
   1172 	    snd_ctl_elem_id_get_subdevice(pattern) != snd_ctl_elem_id_get_subdevice(id))
   1173 	    	return 0;
   1174 	if (snd_ctl_elem_id_get_index(pattern) != -1 &&
   1175 	    snd_ctl_elem_id_get_index(pattern) != snd_ctl_elem_id_get_index(id))
   1176 	    	return 0;
   1177 	if (fnmatch(snd_ctl_elem_id_get_name(pattern), snd_ctl_elem_id_get_name(id), 0) != 0)
   1178 		return 0;
   1179 	return 1;
   1180 }
   1181 
   1182 static
   1183 int run_program1(struct space *space,
   1184 		 const char *command0, char *result,
   1185 		 size_t ressize, size_t *reslen, int log)
   1186 {
   1187 	char *pos = strchr(command0, ' ');
   1188 	int cmdlen = pos ? pos - command0 : strlen(command0);
   1189 	int err, index;
   1190 	snd_hctl_elem_t *elem;
   1191 	snd_ctl_elem_id_t *id;
   1192 
   1193 	if (cmdlen == 12 && strncmp(command0, "__ctl_search", 12) == 0) {
   1194 		index = 0;
   1195 		if (pos)
   1196 			index = strtol(pos, NULL, 0);
   1197 		err = snd_ctl_elem_id_malloc(&id);
   1198 		if (err < 0)
   1199 			return EXIT_FAILURE;
   1200 		elem = snd_hctl_first_elem(space->ctl_handle);
   1201 		while (elem) {
   1202 			snd_hctl_elem_get_id(elem, id);
   1203 			if (!ctl_match(space->ctl_id, id))
   1204 				goto next_search;
   1205 			if (index > 0) {
   1206 				index--;
   1207 				goto next_search;
   1208 			}
   1209 			strlcpy(result, "0", ressize);
   1210 			snd_ctl_elem_id_copy(space->ctl_id, id);
   1211 			snd_ctl_elem_id_free(id);
   1212 			dbg("__ctl_search found a control");
   1213 			return EXIT_SUCCESS;
   1214 		      next_search:
   1215 			elem = snd_hctl_elem_next(elem);
   1216 		}
   1217 		snd_ctl_elem_id_free(id);
   1218 		return EXIT_FAILURE;
   1219 	}
   1220 	if (cmdlen == 11 && strncmp(command0, "__ctl_count", 11) == 0) {
   1221 		index = 0;
   1222 		err = snd_ctl_elem_id_malloc(&id);
   1223 		if (err < 0)
   1224 			return EXIT_FAILURE;
   1225 		elem = snd_hctl_first_elem(space->ctl_handle);
   1226 		while (elem) {
   1227 			snd_hctl_elem_get_id(elem, id);
   1228 			if (!ctl_match(space->ctl_id, id))
   1229 				goto next_count;
   1230 			index++;
   1231 		      next_count:
   1232 			elem = snd_hctl_elem_next(elem);
   1233 		}
   1234 		snd_ctl_elem_id_free(id);
   1235 		if (index > 0) {
   1236 			snprintf(result, ressize, "%u", index);
   1237 			dbg("__ctl_count found %s controls", result);
   1238 			return EXIT_SUCCESS;
   1239 		}
   1240 		dbg("__ctl_count no match");
   1241 		return EXIT_FAILURE;
   1242 	}
   1243 	if (cmdlen == 11 && strncmp(command0, "__ctl_write", 11) == 0) {
   1244 	}
   1245 	Perror(space, "unknown buildin command '%s'", command0);
   1246 	return EXIT_FAILURE;
   1247 }
   1248 
   1249 static int parse(struct space *space, const char *filename);
   1250 
   1251 static char *new_root_dir(const char *filename)
   1252 {
   1253 	char *res, *tmp;
   1254 
   1255 	res = strdup(filename);
   1256 	if (res) {
   1257 		tmp = rindex(res, '/');
   1258 		if (tmp)
   1259 			*tmp = '\0';
   1260 	}
   1261 	dbg("new_root_dir '%s' '%s'", filename, res);
   1262 	return res;
   1263 }
   1264 
   1265 static int parse_line(struct space *space, char *line, size_t linesize)
   1266 {
   1267 	char *linepos;
   1268 	char *key, *value, *attr, *temp;
   1269 	struct pair *pair;
   1270 	enum key_op op;
   1271 	int err = 0, count;
   1272 	char string[PATH_SIZE];
   1273 	char result[PATH_SIZE];
   1274 
   1275 	linepos = line;
   1276 	while (*linepos != '\0') {
   1277 		op = KEY_OP_UNSET;
   1278 
   1279 		err = get_key(&linepos, &key, &op, &value);
   1280 		if (err < 0)
   1281 			goto invalid;
   1282 
   1283 		if (strncasecmp(key, "LABEL", 5) == 0) {
   1284 			if (op != KEY_OP_ASSIGN) {
   1285 				Perror(space, "invalid LABEL operation");
   1286 				goto invalid;
   1287 			}
   1288 			if (space->go_to && strcmp(space->go_to, value) == 0) {
   1289 				free(space->go_to);
   1290 				space->go_to = NULL;
   1291 			}
   1292 			continue;
   1293 		}
   1294 
   1295 		if (space->go_to) {
   1296 			dbg("skip (GOTO '%s')", space->go_to);
   1297 			break;		/* not for us */
   1298 		}
   1299 
   1300 		if (strncasecmp(key, "CTL{", 4) == 0) {
   1301 			attr = get_key_attribute(space, key + 3, string, sizeof(string));
   1302 			if (attr == NULL) {
   1303 				Perror(space, "error parsing CTL attribute");
   1304 				goto invalid;
   1305 			}
   1306 			if (op == KEY_OP_ASSIGN) {
   1307 				strlcpy(result, value, sizeof(result));
   1308 				apply_format(space, result, sizeof(result));
   1309 				dbg("ctl assign: '%s' '%s'", value, attr);
   1310 				err = elemid_set(space, attr, result);
   1311 				if (space->program_result) {
   1312 					free(space->program_result);
   1313 					space->program_result = NULL;
   1314 				}
   1315 				snprintf(string, sizeof(string), "%i", err);
   1316 				space->program_result = strdup(string);
   1317 				if (err < 0 || space->program_result == NULL) {
   1318 					err = 0;
   1319 					break;
   1320 				}
   1321 			} else if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
   1322 				dbg("ctl match: '%s' '%s'", value, attr);
   1323 				temp = (char *)elemid_get(space, attr);
   1324 				if (!do_match(key, op, value, temp))
   1325 					break;
   1326 			} else {
   1327 				Perror(space, "invalid CTL{} operation");
   1328 				goto invalid;
   1329 			}
   1330 			continue;
   1331 		}
   1332 		if (strcasecmp(key, "RESULT") == 0) {
   1333 			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
   1334 				if (!do_match(key, op, value, space->program_result))
   1335 					break;
   1336 			} else if (op == KEY_OP_ASSIGN) {
   1337 				if (space->program_result) {
   1338 					free(space->program_result);
   1339 					space->program_result = NULL;
   1340 				}
   1341 				strlcpy(string, value, sizeof(string));
   1342 				apply_format(space, string, sizeof(string));
   1343 				space->program_result = strdup(string);
   1344 				if (space->program_result == NULL)
   1345 					break;
   1346  			} else {
   1347 				Perror(space, "invalid RESULT operation");
   1348 				goto invalid;
   1349 			}
   1350 			continue;
   1351 		}
   1352 		if (strcasecmp(key, "PROGRAM") == 0) {
   1353 			if (op == KEY_OP_UNSET)
   1354 				continue;
   1355 			strlcpy(string, value, sizeof(string));
   1356 			apply_format(space, string, sizeof(string));
   1357 			if (space->program_result) {
   1358 				free(space->program_result);
   1359 				space->program_result = NULL;
   1360 			}
   1361 			if (run_program(space, string, result, sizeof(result), NULL, space->log_run) != 0) {
   1362 				dbg("PROGRAM '%s' is false", string);
   1363 				if (op != KEY_OP_NOMATCH)
   1364 					break;
   1365 			} else {
   1366 				remove_trailing_chars(result, '\n');
   1367 				count = replace_untrusted_chars(result);
   1368 				if (count)
   1369 					info("%i untrusted character(s) replaced", count);
   1370 				dbg("PROGRAM '%s' result is '%s'", string, result);
   1371 				space->program_result = strdup(result);
   1372 				if (space->program_result == NULL)
   1373 					break;
   1374 				dbg("PROGRAM returned successful");
   1375 				if (op == KEY_OP_NOMATCH)
   1376 					break;
   1377 			}
   1378 			dbg("PROGRAM key is true");
   1379 			continue;
   1380 		}
   1381 		if (strncasecmp(key, "CARDINFO{", 9) == 0) {
   1382 			attr = get_key_attribute(space, key + 8, string, sizeof(string));
   1383 			if (attr == NULL) {
   1384 				Perror(space, "error parsing CARDINFO attribute");
   1385 				goto invalid;
   1386 			}
   1387 			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
   1388 				dbg("cardinfo: '%s' '%s'", value, attr);
   1389 				temp = (char *)cardinfo_get(space, attr);
   1390 				if (!do_match(key, op, value, temp))
   1391 					break;
   1392 			} else {
   1393 				Perror(space, "invalid CARDINFO{} operation");
   1394 				goto invalid;
   1395 			}
   1396 			continue;
   1397 		}
   1398 		if (strncasecmp(key, "ATTR{", 5) == 0) {
   1399 			attr = get_key_attribute(space, key + 4, string, sizeof(string));
   1400 			if (attr == NULL) {
   1401 				Perror(space, "error parsing ATTR attribute");
   1402 				goto invalid;
   1403 			}
   1404 			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
   1405 				pair = value_find(space, "sysfs_device");
   1406 				if (pair == NULL)
   1407 					break;
   1408 				dbg("sysfs_attr: '%s' '%s'", pair->value, attr);
   1409 				temp = sysfs_attr_get_value(pair->value, attr);
   1410 				if (!do_match(key, op, value, temp))
   1411 					break;
   1412 			} else {
   1413 				Perror(space, "invalid ATTR{} operation");
   1414 				goto invalid;
   1415 			}
   1416 			continue;
   1417 		}
   1418 		if (strncasecmp(key, "ENV{", 4) == 0) {
   1419 			attr = get_key_attribute(space, key + 3, string, sizeof(string));
   1420 			if (attr == NULL) {
   1421 				Perror(space, "error parsing ENV attribute");
   1422 				goto invalid;
   1423 			}
   1424 			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
   1425 				temp = getenv(attr);
   1426 				dbg("env: '%s' '%s'", attr, temp);
   1427 				if (!do_match(key, op, value, temp))
   1428 					break;
   1429 			} else if (op == KEY_OP_ASSIGN ||
   1430 				   op == KEY_OP_ASSIGN_FINAL) {
   1431 				strlcpy(result, value, sizeof(result));
   1432 				apply_format(space, result, sizeof(result));
   1433 				dbg("env set: '%s' '%s'", attr, result);
   1434 				if (setenv(attr, result, op == KEY_OP_ASSIGN_FINAL))
   1435 					break;
   1436 			} else {
   1437 				Perror(space, "invalid ENV{} operation");
   1438 				goto invalid;
   1439 			}
   1440 			continue;
   1441 		}
   1442 		if (strcasecmp(key, "GOTO") == 0) {
   1443 			if (op != KEY_OP_ASSIGN) {
   1444 				Perror(space, "invalid GOTO operation");
   1445 				goto invalid;
   1446 			}
   1447 			space->go_to = strdup(value);
   1448 			if (space->go_to == NULL) {
   1449 				err = -ENOMEM;
   1450 				break;
   1451 			}
   1452 			continue;
   1453 		}
   1454 		if (strcasecmp(key, "INCLUDE") == 0) {
   1455 			char *rootdir, *go_to;
   1456 			const char *filename;
   1457 			struct dirent *dirent;
   1458 			DIR *dir;
   1459 			int linenum;
   1460 			if (op != KEY_OP_ASSIGN) {
   1461 				Perror(space, "invalid INCLUDE operation");
   1462 				goto invalid;
   1463 			}
   1464 			if (value[0] == '/')
   1465 				strlcpy(string, value, sizeof(string));
   1466 			else {
   1467 				strlcpy(string, space->rootdir, sizeof(string));
   1468 				strlcat(string, "/", sizeof(string));
   1469 				strlcat(string, value, sizeof(string));
   1470 			}
   1471 			rootdir = space->rootdir;
   1472 			go_to = space->go_to;
   1473 			filename = space->filename;
   1474 			linenum = space->linenum;
   1475 			dir = opendir(string);
   1476 			if (dir) {
   1477 				count = strlen(string);
   1478 				while ((dirent = readdir(dir)) != NULL) {
   1479 					if (strcmp(dirent->d_name, ".") == 0 ||
   1480 					    strcmp(dirent->d_name, "..") == 0)
   1481 						continue;
   1482 					string[count] = '\0';
   1483 					strlcat(string, "/", sizeof(string));
   1484 					strlcat(string, dirent->d_name, sizeof(string));
   1485 					space->go_to = NULL;
   1486 					space->rootdir = new_root_dir(string);
   1487 					if (space->rootdir) {
   1488 						err = parse(space, string);
   1489 						free(space->rootdir);
   1490 					} else
   1491 						err = -ENOMEM;
   1492 					if (space->go_to) {
   1493 						Perror(space, "unterminated GOTO '%s'", space->go_to);
   1494 						free(space->go_to);
   1495 					}
   1496 					if (err)
   1497 						break;
   1498 				}
   1499 				closedir(dir);
   1500 			} else {
   1501 				space->go_to = NULL;
   1502 				space->rootdir = new_root_dir(string);
   1503 				if (space->rootdir) {
   1504 					err = parse(space, string);
   1505 					free(space->rootdir);
   1506 				} else
   1507 					err = -ENOMEM;
   1508 				if (space->go_to) {
   1509 					Perror(space, "unterminated GOTO '%s'", space->go_to);
   1510 					free(space->go_to);
   1511 				}
   1512 			}
   1513 			space->go_to = go_to;
   1514 			space->rootdir = rootdir;
   1515 			space->filename = filename;
   1516 			space->linenum = linenum;
   1517 			if (space->quit)
   1518 				break;
   1519 			if (err)
   1520 				break;
   1521 			continue;
   1522 		}
   1523 		if (strncasecmp(key, "ACCESS", 6) == 0) {
   1524 			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
   1525 				if (value[0] != '/') {
   1526 					strlcpy(string, space->rootdir, sizeof(string));
   1527 					strlcat(string, "/", sizeof(string));
   1528 					strlcat(string, value, sizeof(string));
   1529 				} else {
   1530 					strlcat(string, value, sizeof(string));
   1531 				}
   1532 				count = access(string, F_OK);
   1533 				dbg("access(%s) = %i", value, count);
   1534 				if (op == KEY_OP_MATCH && count != 0)
   1535 					break;
   1536 				if (op == KEY_OP_NOMATCH && count == 0)
   1537 					break;
   1538 			} else {
   1539 				Perror(space, "invalid ACCESS operation");
   1540 				goto invalid;
   1541 			}
   1542 			continue;
   1543 		}
   1544 		if (strncasecmp(key, "PRINT", 5) == 0) {
   1545 			if (op != KEY_OP_ASSIGN) {
   1546 				Perror(space, "invalid PRINT operation");
   1547 				goto invalid;
   1548 			}
   1549 			strlcpy(string, value, sizeof(string));
   1550 			apply_format(space, string, sizeof(string));
   1551 			fwrite(string, strlen(string), 1, stdout);
   1552 			continue;
   1553 		}
   1554 		if (strncasecmp(key, "ERROR", 5) == 0) {
   1555 			if (op != KEY_OP_ASSIGN) {
   1556 				Perror(space, "invalid ERROR operation");
   1557 				goto invalid;
   1558 			}
   1559 			strlcpy(string, value, sizeof(string));
   1560 			apply_format(space, string, sizeof(string));
   1561 			fwrite(string, strlen(string), 1, stderr);
   1562 			continue;
   1563 		}
   1564 		if (strncasecmp(key, "EXIT", 4) == 0) {
   1565 			if (op != KEY_OP_ASSIGN) {
   1566 				Perror(space, "invalid EXIT operation");
   1567 				goto invalid;
   1568 			}
   1569 			strlcpy(string, value, sizeof(string));
   1570 			apply_format(space, string, sizeof(string));
   1571 			if (strcmp(string, "return") == 0)
   1572 				return -EJUSTRETURN;
   1573 			space->exit_code = strtol(string, NULL, 0);
   1574 			space->quit = 1;
   1575 			break;
   1576 		}
   1577 		if (strncasecmp(key, "CONFIG{", 7) == 0) {
   1578 			attr = get_key_attribute(space, key + 6, string, sizeof(string));
   1579 			if (attr == NULL) {
   1580 				Perror(space, "error parsing CONFIG attribute");
   1581 				goto invalid;
   1582 			}
   1583 			strlcpy(result, value, sizeof(result));
   1584 			apply_format(space, result, sizeof(result));
   1585 			if (op == KEY_OP_ASSIGN) {
   1586 				err = value_set(space, attr, result);
   1587 				dbg("CONFIG{%s}='%s'", attr, result);
   1588 				break;
   1589 			} else if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
   1590 				pair = value_find(space, attr);
   1591 				if (pair == NULL)
   1592 					break;
   1593 				if (!do_match(key, op, result, pair->value))
   1594 					break;
   1595 			} else {
   1596 				Perror(space, "invalid CONFIG{} operation");
   1597 				goto invalid;
   1598 			}
   1599 		}
   1600 
   1601 		Perror(space, "unknown key '%s'", key);
   1602 	}
   1603 	return err;
   1604 
   1605 invalid:
   1606 	Perror(space, "invalid rule");
   1607 	return -EINVAL;
   1608 }
   1609 
   1610 static int parse(struct space *space, const char *filename)
   1611 {
   1612 	char *buf, *bufline, *line;
   1613 	size_t bufsize, pos, count, linesize;
   1614 	unsigned int linenum, i, j, linenum_adj;
   1615 	int err;
   1616 
   1617 	dbg("start of file '%s'", filename);
   1618 
   1619 	if (file_map(filename, &buf, &bufsize) != 0) {
   1620 		err = errno;
   1621 		error("Unable to open file '%s': %s", filename, strerror(err));
   1622 		return -err;
   1623 	}
   1624 
   1625 	err = 0;
   1626 	pos = 0;
   1627 	linenum = 0;
   1628 	linesize = 128;
   1629 	line = malloc(linesize);
   1630 	if (line == NULL)
   1631 		return -ENOMEM;
   1632 	space->filename = filename;
   1633 	while (!err && pos < bufsize && !space->quit) {
   1634 		count = line_width(buf, bufsize, pos);
   1635 		bufline = buf + pos;
   1636 		pos += count + 1;
   1637 		linenum++;
   1638 
   1639 		/* skip whitespaces */
   1640 		while (count > 0 && isspace(bufline[0])) {
   1641 			bufline++;
   1642 			count--;
   1643 		}
   1644 		if (count == 0)
   1645 			continue;
   1646 
   1647 		/* comment check */
   1648 		if (bufline[0] == '#')
   1649 			continue;
   1650 
   1651 		if (count > linesize - 1) {
   1652 			free(line);
   1653 			linesize = (count + 127 + 1) & ~127;
   1654 			if (linesize > 2048) {
   1655 				error("file %s, line %i too long", filename, linenum);
   1656 				err = -EINVAL;
   1657 				break;
   1658 			}
   1659 			line = malloc(linesize);
   1660 			if (line == NULL) {
   1661 				err = -EINVAL;
   1662 				break;
   1663 			}
   1664 		}
   1665 
   1666 		/* skip backslash and newline from multiline rules */
   1667 		linenum_adj = 0;
   1668 		for (i = j = 0; i < count; i++) {
   1669 			if (bufline[i] == '\\' && bufline[i+1] == '\n') {
   1670 				linenum_adj++;
   1671 				continue;
   1672 			}
   1673 			line[j++] = bufline[i];
   1674 		}
   1675 		line[j] = '\0';
   1676 
   1677 		dbg("read (%i) '%s'", linenum, line);
   1678 		space->linenum = linenum;
   1679 		err = parse_line(space, line, linesize);
   1680 		if (err == -EJUSTRETURN) {
   1681 			err = 0;
   1682 			break;
   1683 		}
   1684 		linenum += linenum_adj;
   1685 	}
   1686 
   1687 	free(line);
   1688 	space->filename = NULL;
   1689 	space->linenum = -1;
   1690 	file_unmap(buf, bufsize);
   1691 	dbg("end of file '%s'", filename);
   1692 	return err ? err : -abs(space->exit_code);
   1693 }
   1694 
   1695 int init(const char *filename, const char *cardname)
   1696 {
   1697 	struct space *space;
   1698 	int err = 0, card, first;
   1699 
   1700 	sysfs_init();
   1701 	if (!cardname) {
   1702 		first = 1;
   1703 		card = -1;
   1704 		while (1) {
   1705 			if (snd_card_next(&card) < 0)
   1706 				break;
   1707 			if (card < 0) {
   1708 				if (first) {
   1709 					error("No soundcards found...");
   1710 					return -ENODEV;
   1711 				}
   1712 				break;
   1713 			}
   1714 			first = 0;
   1715 			err = init_space(&space, card);
   1716 			if (err == 0 &&
   1717 			    (space->rootdir = new_root_dir(filename)) != NULL)
   1718 				err = parse(space, filename);
   1719 			free_space(space);
   1720 			if (err < 0)
   1721 				break;
   1722 		}
   1723 	} else {
   1724 		card = snd_card_get_index(cardname);
   1725 		if (card < 0) {
   1726 			error("Cannot find soundcard '%s'...", cardname);
   1727 			goto error;
   1728 		}
   1729 		memset(&space, 0, sizeof(space));
   1730 		err = init_space(&space, card);
   1731 		if (err == 0 &&
   1732 		    (space->rootdir = new_root_dir(filename)) != NULL)
   1733 			err = parse(space, filename);
   1734 		free_space(space);
   1735 	}
   1736   error:
   1737 	sysfs_cleanup();
   1738 	return err;
   1739 }
   1740