tarina

git clone https://git.tarina.org/tarina
Log | Files | Refs | README | LICENSE

init_parse.c (47297B)


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