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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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 "aconfig.h"
41#include "alsactl.h"
42#include "list.h"
43
44#define PATH_SIZE	512
45#define NAME_SIZE	128
46#define EJUSTRETURN	0x7fffffff
47
48enum key_op {
49	KEY_OP_UNSET,
50	KEY_OP_MATCH,
51	KEY_OP_NOMATCH,
52	KEY_OP_ADD,
53	KEY_OP_ASSIGN,
54	KEY_OP_ASSIGN_FINAL
55};
56
57struct pair {
58	char *key;
59	char *value;
60	struct pair *next;
61};
62
63struct space {
64	struct pair *pairs;
65	char *rootdir;
66	char *go_to;
67	char *program_result;
68	const char *filename;
69	int linenum;
70	int log_run;
71	int exit_code;
72	int quit;
73	unsigned int ctl_id_changed;
74	snd_hctl_t *ctl_handle;
75	snd_ctl_card_info_t *ctl_card_info;
76	snd_ctl_elem_id_t *ctl_id;
77	snd_ctl_elem_info_t *ctl_info;
78	snd_ctl_elem_value_t *ctl_value;
79};
80
81static void Perror(struct space *space, const char *fmt, ...)
82{
83	va_list arg;
84	va_start(arg, fmt);
85	fprintf(stderr, "%s:%i: ", space->filename, space->linenum);
86	vfprintf(stderr, fmt, arg);
87	putc('\n', stderr);
88	va_end(arg);
89}
90
91#include "init_sysdeps.c"
92#include "init_utils_string.c"
93#include "init_utils_run.c"
94#include "init_sysfs.c"
95
96static void free_space(struct space *space)
97{
98	struct pair *pair = space->pairs;
99	struct pair *next = pair;
100
101	while (next) {
102		pair = next;
103		next = pair->next;
104		free(pair->value);
105		free(pair->key);
106		free(pair);
107	}
108	space->pairs = NULL;
109	if (space->ctl_value) {
110		snd_ctl_elem_value_free(space->ctl_value);
111		space->ctl_value = NULL;
112	}
113	if (space->ctl_info) {
114		snd_ctl_elem_info_free(space->ctl_info);
115		space->ctl_info = NULL;
116	}
117	if (space->ctl_id) {
118		snd_ctl_elem_id_free(space->ctl_id);
119		space->ctl_id = NULL;
120	}
121	if (space->ctl_card_info) {
122		snd_ctl_card_info_free(space->ctl_card_info);
123		space->ctl_card_info = NULL;
124	}
125	if (space->ctl_handle) {
126		snd_hctl_close(space->ctl_handle);
127		space->ctl_handle = NULL;
128	}
129	if (space->rootdir)
130		free(space->rootdir);
131	if (space->program_result)
132		free(space->program_result);
133	if (space->go_to)
134		free(space->go_to);
135	free(space);
136}
137
138static struct pair *value_find(struct space *space, const char *key)
139{
140	struct pair *pair = space->pairs;
141
142	while (pair && strcmp(pair->key, key) != 0)
143		pair = pair->next;
144	return pair;
145}
146
147static int value_set(struct space *space, const char *key, const char *value)
148{
149	struct pair *pair;
150
151	pair = value_find(space, key);
152	if (pair) {
153		free(pair->value);
154		pair->value = strdup(value);
155		if (pair->value == NULL)
156			return -ENOMEM;
157	} else {
158		pair = malloc(sizeof(struct pair));
159		if (pair == NULL)
160			return -ENOMEM;
161		pair->key = strdup(key);
162		if (pair->key == NULL) {
163			free(pair);
164			return -ENOMEM;
165		}
166		pair->value = strdup(value);
167		if (pair->value == NULL) {
168			free(pair->key);
169			free(pair);
170			return -ENOMEM;
171		}
172		pair->next = space->pairs;
173		space->pairs = pair;
174	}
175	return 0;
176}
177
178static int init_space(struct space **space, int card)
179{
180	struct space *res;
181	char device[16];
182	int err;
183
184	res = calloc(1, sizeof(struct space));
185	if (res == NULL)
186		return -ENOMEM;
187	res->ctl_id_changed = ~0;
188	res->linenum = -1;
189	sprintf(device, "hw:%d", card);
190	err = snd_hctl_open(&res->ctl_handle, device, 0);
191	if (err < 0)
192		goto error;
193	err = snd_hctl_load(res->ctl_handle);
194	if (err < 0)
195		goto error;
196	err = snd_ctl_card_info_malloc(&res->ctl_card_info);
197	if (err < 0)
198		goto error;
199	err = snd_ctl_card_info(snd_hctl_ctl(res->ctl_handle), res->ctl_card_info);
200	if (err < 0)
201		goto error;
202	err = snd_ctl_elem_id_malloc(&res->ctl_id);
203	if (err < 0)
204		goto error;
205	err = snd_ctl_elem_info_malloc(&res->ctl_info);
206	if (err < 0)
207		goto error;
208	err = snd_ctl_elem_value_malloc(&res->ctl_value);
209	if (err < 0)
210		goto error;
211	*space = res;
212	return 0;
213 error:
214 	free_space(res);
215 	return err;
216}
217
218static const char *cardinfo_get(struct space *space, const char *attr)
219{
220	if (strncasecmp(attr, "CARD", 4) == 0) {
221		static char res[16];
222		sprintf(res, "%u", snd_ctl_card_info_get_card(space->ctl_card_info));
223		return res;
224	}
225	if (strncasecmp(attr, "ID", 2) == 0)
226		return snd_ctl_card_info_get_id(space->ctl_card_info);
227	if (strncasecmp(attr, "DRIVER", 6) == 0)
228		return snd_ctl_card_info_get_driver(space->ctl_card_info);
229	if (strncasecmp(attr, "NAME", 4) == 0)
230		return snd_ctl_card_info_get_name(space->ctl_card_info);
231	if (strncasecmp(attr, "LONGNAME", 8) == 0)
232		return snd_ctl_card_info_get_longname(space->ctl_card_info);
233	if (strncasecmp(attr, "MIXERNAME", 9) == 0)
234		return snd_ctl_card_info_get_mixername(space->ctl_card_info);
235	if (strncasecmp(attr, "COMPONENTS", 10) == 0)
236		return snd_ctl_card_info_get_components(space->ctl_card_info);
237	Perror(space, "unknown cardinfo{} attribute '%s'", attr);
238	return NULL;
239}
240
241static int check_id_changed(struct space *space, unsigned int what)
242{
243	snd_hctl_elem_t *elem;
244	int err;
245
246	if ((space->ctl_id_changed & what & 1) != 0) {
247		snd_ctl_elem_id_set_numid(space->ctl_id, 0);
248		elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
249		if (!elem)
250			return -ENOENT;
251		err = snd_hctl_elem_info(elem, space->ctl_info);
252		if (err == 0)
253			space->ctl_id_changed &= ~1;
254		return err;
255	}
256	if ((space->ctl_id_changed & what & 2) != 0) {
257		snd_ctl_elem_id_set_numid(space->ctl_id, 0);
258		elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
259		if (!elem)
260			return -ENOENT;
261		err = snd_hctl_elem_read(elem, space->ctl_value);
262		if (err == 0)
263			space->ctl_id_changed &= ~2;
264		return err;
265	}
266	return 0;
267}
268
269static const char *get_ctl_value(struct space *space)
270{
271	snd_ctl_elem_type_t type;
272	unsigned int idx, count;
273	static char res[1024], tmp[16];
274	static const char hex[] = "0123456789abcdef";
275	char *pos;
276	const char *pos1;
277
278	type = snd_ctl_elem_info_get_type(space->ctl_info);
279	count = snd_ctl_elem_info_get_count(space->ctl_info);
280	res[0] = '\0';
281	switch (type) {
282	case SND_CTL_ELEM_TYPE_BOOLEAN:
283		for (idx = 0; idx < count; idx++) {
284			if (idx > 0)
285				strlcat(res, ",", sizeof(res));
286			strlcat(res, snd_ctl_elem_value_get_boolean(space->ctl_value, idx) ? "on" : "off", sizeof(res));
287		}
288		break;
289	case SND_CTL_ELEM_TYPE_INTEGER:
290		for (idx = 0; idx < count; idx++) {
291			if (idx > 0)
292				strlcat(res, ",", sizeof(res));
293			snprintf(tmp, sizeof(tmp), "%li", snd_ctl_elem_value_get_integer(space->ctl_value, idx));
294			strlcat(res, tmp, sizeof(res));
295		}
296		break;
297	case SND_CTL_ELEM_TYPE_INTEGER64:
298		for (idx = 0; idx < count; idx++) {
299			if (idx > 0)
300				strlcat(res, ",", sizeof(res));
301			snprintf(tmp, sizeof(tmp), "%lli", snd_ctl_elem_value_get_integer64(space->ctl_value, idx));
302			strlcat(res, tmp, sizeof(res));
303		}
304		break;
305	case SND_CTL_ELEM_TYPE_ENUMERATED:
306		for (idx = 0; idx < count; idx++) {
307			if (idx > 0)
308				strlcat(res, ",", sizeof(res));
309			snprintf(tmp, sizeof(tmp), "%u", snd_ctl_elem_value_get_enumerated(space->ctl_value, idx));
310			strlcat(res, tmp, sizeof(res));
311		}
312		break;
313	case SND_CTL_ELEM_TYPE_BYTES:
314	case SND_CTL_ELEM_TYPE_IEC958:
315		if (type == SND_CTL_ELEM_TYPE_IEC958)
316			count = sizeof(snd_aes_iec958_t);
317		if (count > (sizeof(res)-1)/2)
318			count = (sizeof(res)-1/2);
319		pos = res;
320		pos1 = snd_ctl_elem_value_get_bytes(space->ctl_value);
321		while (count > 0) {
322			idx = *pos1++;
323			*pos++ = hex[idx >> 4];
324			*pos++ = hex[idx & 0x0f];
325			count++;
326		}
327		*pos++ = '\0';
328		break;
329	default:
330		Perror(space, "unknown element type '%i'", type);
331		return NULL;
332	}
333	return res;
334}
335
336/* Function to convert from percentage to volume. val = percentage */
337#define convert_prange1(val, min, max) \
338        ceil((val) * ((max) - (min)) * 0.01 + (min))
339
340static int set_ctl_value(struct space *space, const char *value, int all)
341{
342	snd_ctl_elem_type_t type;
343	unsigned int idx, idx2, count, items;
344	const char *pos, *pos2;
345	snd_hctl_elem_t *elem;
346	int val;
347	long lval;
348
349	type = snd_ctl_elem_info_get_type(space->ctl_info);
350	count = snd_ctl_elem_info_get_count(space->ctl_info);
351	switch (type) {
352	case SND_CTL_ELEM_TYPE_BOOLEAN:
353		for (idx = 0; idx < count; idx++) {
354			while (*value == ' ')
355				value++;
356			if (*value == '\0')
357				goto missing;
358			val = strncasecmp(value, "true", 4) == 0 ||
359				strncasecmp(value, "yes", 3) == 0 ||
360				strncasecmp(value, "on", 2) == 0 ||
361				strncasecmp(value, "1", 1) == 0;
362			snd_ctl_elem_value_set_boolean(space->ctl_value, idx, val);
363			if (all)
364				continue;
365			pos = strchr(value, ',');
366			value = pos ? pos + 1 : value + strlen(value) - 1;
367		}
368		break;
369	case SND_CTL_ELEM_TYPE_INTEGER:
370		for (idx = 0; idx < count; idx++) {
371			while (*value == ' ')
372				value++;
373			pos = strchr(value, ',');
374			if (pos)
375				*(char *)pos = '\0';
376			remove_trailing_chars((char *)value, ' ');
377			items = pos ? (unsigned)(pos - value) : (unsigned)strlen(value);
378			if (items > 1 && value[items-1] == '%') {
379				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));
380				snd_ctl_elem_value_set_integer(space->ctl_value, idx, val);
381			} else if (items > 2 && value[items-2] == 'd' && value[items-1] == 'B') {
382				val = strtol(value, NULL, 0) * 100;
383				if ((pos2 = strchr(value, '.')) != NULL) {
384					if (isdigit(*(pos2-1)) && isdigit(*(pos2-2))) {
385						if (val < 0)
386							val -= strtol(pos2 + 1, NULL, 0);
387						else
388							val += strtol(pos2 + 1, NULL, 0);
389					} else if (isdigit(*(pos2-1))) {
390						if (val < 0)
391							val -= strtol(pos2 + 1, NULL, 0) * 10;
392						else
393							val += strtol(pos2 + 1, NULL, 0) * 10;
394					}
395				}
396				val = snd_ctl_convert_from_dB(snd_hctl_ctl(space->ctl_handle), space->ctl_id, val, &lval, -1);
397				if (val < 0) {
398					dbg("unable to convert dB value '%s' to internal integer range", value);
399					return val;
400				}
401				snd_ctl_elem_value_set_integer(space->ctl_value, idx, lval);
402			} else {
403				snd_ctl_elem_value_set_integer(space->ctl_value, idx, strtol(value, NULL, 0));
404			}
405			if (all)
406				continue;
407			value = pos ? pos + 1 : value + strlen(value) - 1;
408		}
409		break;
410	case SND_CTL_ELEM_TYPE_INTEGER64:
411		for (idx = 0; idx < count; idx++) {
412			while (*value == ' ')
413				value++;
414			snd_ctl_elem_value_set_integer64(space->ctl_value, idx, strtoll(value, NULL, 0));
415			if (all)
416				continue;
417			pos = strchr(value, ',');
418			value = pos ? pos + 1 : value + strlen(value) - 1;
419		}
420		break;
421	case SND_CTL_ELEM_TYPE_ENUMERATED:
422		for (idx = 0; idx < count; idx++) {
423			while (*value == ' ')
424				value++;
425			pos = strchr(value, ',');
426			if (isdigit(value[0]) || value[0] == '-') {
427				snd_ctl_elem_value_set_enumerated(space->ctl_value, idx, strtol(value, NULL, 0));
428			} else {
429				if (pos)
430					*(char *)pos = '\0';
431				remove_trailing_chars((char *)value, ' ');
432				items = snd_ctl_elem_info_get_items(space->ctl_info);
433				for (idx2 = 0; idx2 < items; idx2++) {
434					snd_ctl_elem_info_set_item(space->ctl_info, idx2);
435					elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
436					if (elem == NULL)
437						return -ENOENT;
438					val = snd_hctl_elem_info(elem, space->ctl_info);
439					if (val < 0)
440						return val;
441					if (strcasecmp(snd_ctl_elem_info_get_item_name(space->ctl_info), value) == 0) {
442						snd_ctl_elem_value_set_enumerated(space->ctl_value, idx, idx2);
443						break;
444					}
445				}
446				if (idx2 >= items) {
447					Perror(space, "wrong enum identifier '%s'", value);
448					return -EINVAL;
449				}
450			}
451			if (all)
452				continue;
453			value = pos ? pos + 1 : value + strlen(value) - 1;
454		}
455		break;
456	case SND_CTL_ELEM_TYPE_BYTES:
457	case SND_CTL_ELEM_TYPE_IEC958:
458		if (type == SND_CTL_ELEM_TYPE_IEC958)
459			count = sizeof(snd_aes_iec958_t);
460		while (*value == ' ')
461			value++;
462		if (strlen(value) != count * 2) {
463			Perror(space, "bad ctl value hexa length (should be %u bytes)", count);
464			return -EINVAL;
465		}
466		for (idx = 0; idx < count; idx += 2) {
467			int nibble1 = hextodigit(*(value++));
468			int nibble2 = hextodigit(*(value++));
469			if (nibble1 < 0 || nibble2 < 0) {
470				Perror(space, "bad ctl hexa value");
471				return -EINVAL;
472			}
473			val = (nibble1 << 4) | nibble2;
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
487static 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
508static int ctl_match(snd_ctl_elem_id_t *pattern, snd_ctl_elem_id_t *id)
509{
510	if ((int)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 ((int)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 ((int)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 ((int)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
527static 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;
657dbvalue:
658		sprintf(res, "%li.%02lidB", (long)(val / 100), labs(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, "%d", 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
748static 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
838static 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} */
942static char *get_key_attribute(struct space *space, char *str, char *res, size_t ressize)
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, ressize);
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 */
966static 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*/
986static 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
1004static 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;
1088found:
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, subtitution 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
1242static
1243int run_program1(struct space *space,
1244		 const char *command0, char *result,
1245		 size_t ressize, size_t *reslen ATTRIBUTE_UNUSED,
1246		 int log ATTRIBUTE_UNUSED)
1247{
1248	if (strncmp(command0, "__ctl_search", 12) == 0) {
1249		const char *res = elemid_get(space, "do_search");
1250		if (res == NULL || strcmp(res, "1") != 0)
1251			return EXIT_FAILURE;
1252		return EXIT_SUCCESS;
1253	}
1254	if (strncmp(command0, "__ctl_count", 11) == 0) {
1255		const char *res = elemid_get(space, "do_count");
1256		if (res == NULL || strcmp(res, "0") == 0)
1257			return EXIT_FAILURE;
1258		strlcpy(result, res, ressize);
1259		return EXIT_SUCCESS;
1260	}
1261	Perror(space, "unknown buildin command '%s'", command0);
1262	return EXIT_FAILURE;
1263}
1264
1265static int parse(struct space *space, const char *filename);
1266
1267static char *new_root_dir(const char *filename)
1268{
1269	char *res, *tmp;
1270
1271	res = strdup(filename);
1272	if (res) {
1273		tmp = strrchr(res, '/');
1274		if (tmp)
1275			*tmp = '\0';
1276	}
1277	dbg("new_root_dir '%s' '%s'", filename, res);
1278	return res;
1279}
1280
1281/* return non-zero if the file name has the extension ".conf" */
1282static int conf_name_filter(const struct dirent *d)
1283{
1284	char *ext = strrchr(d->d_name, '.');
1285	return ext && !strcmp(ext, ".conf");
1286}
1287
1288static int parse_line(struct space *space, char *line, size_t linesize ATTRIBUTE_UNUSED)
1289{
1290	char *linepos;
1291	char *key, *value, *attr, *temp;
1292	struct pair *pair;
1293	enum key_op op;
1294	int err = 0, count;
1295	char string[PATH_SIZE];
1296	char result[PATH_SIZE];
1297
1298	linepos = line;
1299	while (*linepos != '\0') {
1300		op = KEY_OP_UNSET;
1301
1302		err = get_key(&linepos, &key, &op, &value);
1303		if (err < 0)
1304			goto invalid;
1305
1306		if (strncasecmp(key, "LABEL", 5) == 0) {
1307			if (op != KEY_OP_ASSIGN) {
1308				Perror(space, "invalid LABEL operation");
1309				goto invalid;
1310			}
1311			if (space->go_to && strcmp(space->go_to, value) == 0) {
1312				free(space->go_to);
1313				space->go_to = NULL;
1314			}
1315			continue;
1316		}
1317
1318		if (space->go_to) {
1319			dbg("skip (GOTO '%s')", space->go_to);
1320			break;		/* not for us */
1321		}
1322
1323		if (strncasecmp(key, "CTL{", 4) == 0) {
1324			attr = get_key_attribute(space, key + 3, string, sizeof(string));
1325			if (attr == NULL) {
1326				Perror(space, "error parsing CTL attribute");
1327				goto invalid;
1328			}
1329			if (op == KEY_OP_ASSIGN) {
1330				strlcpy(result, value, sizeof(result));
1331				apply_format(space, result, sizeof(result));
1332				dbg("ctl assign: '%s' '%s'", value, attr);
1333				err = elemid_set(space, attr, result);
1334				if (space->program_result) {
1335					free(space->program_result);
1336					space->program_result = NULL;
1337				}
1338				snprintf(string, sizeof(string), "%i", err);
1339				space->program_result = strdup(string);
1340				err = 0;
1341				if (space->program_result == NULL)
1342					break;
1343			} else if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
1344				if (strncmp(attr, "write", 5) == 0) {
1345					strlcpy(result, value, sizeof(result));
1346					apply_format(space, result, sizeof(result));
1347					dbg("ctl write: '%s' '%s'", value, attr);
1348					err = elemid_set(space, "values", result);
1349					if (err == 0 && op == KEY_OP_NOMATCH)
1350						break;
1351					if (err != 0 && op == KEY_OP_MATCH)
1352						break;
1353				} else {
1354					temp = (char *)elemid_get(space, attr);
1355					dbg("ctl match: '%s' '%s' '%s'", attr, value, temp);
1356					if (!do_match(key, op, value, temp))
1357						break;
1358				}
1359			} else {
1360				Perror(space, "invalid CTL{} operation");
1361				goto invalid;
1362			}
1363			continue;
1364		}
1365		if (strcasecmp(key, "RESULT") == 0) {
1366			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
1367				if (!do_match(key, op, value, space->program_result))
1368					break;
1369			} else if (op == KEY_OP_ASSIGN) {
1370				if (space->program_result) {
1371					free(space->program_result);
1372					space->program_result = NULL;
1373				}
1374				strlcpy(string, value, sizeof(string));
1375				apply_format(space, string, sizeof(string));
1376				space->program_result = strdup(string);
1377				if (space->program_result == NULL)
1378					break;
1379 			} else {
1380				Perror(space, "invalid RESULT operation");
1381				goto invalid;
1382			}
1383			continue;
1384		}
1385		if (strcasecmp(key, "PROGRAM") == 0) {
1386			if (op == KEY_OP_UNSET)
1387				continue;
1388			strlcpy(string, value, sizeof(string));
1389			apply_format(space, string, sizeof(string));
1390			if (space->program_result) {
1391				free(space->program_result);
1392				space->program_result = NULL;
1393			}
1394			if (run_program(space, string, result, sizeof(result), NULL, space->log_run) != 0) {
1395				dbg("PROGRAM '%s' is false", string);
1396				if (op != KEY_OP_NOMATCH)
1397					break;
1398			} else {
1399				remove_trailing_chars(result, '\n');
1400				count = replace_untrusted_chars(result);
1401				if (count)
1402					info("%i untrusted character(s) replaced", count);
1403				dbg("PROGRAM '%s' result is '%s'", string, result);
1404				space->program_result = strdup(result);
1405				if (space->program_result == NULL)
1406					break;
1407				dbg("PROGRAM returned successful");
1408				if (op == KEY_OP_NOMATCH)
1409					break;
1410			}
1411			dbg("PROGRAM key is true");
1412			continue;
1413		}
1414		if (strncasecmp(key, "CARDINFO{", 9) == 0) {
1415			attr = get_key_attribute(space, key + 8, string, sizeof(string));
1416			if (attr == NULL) {
1417				Perror(space, "error parsing CARDINFO attribute");
1418				goto invalid;
1419			}
1420			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
1421				dbg("cardinfo: '%s' '%s'", value, attr);
1422				temp = (char *)cardinfo_get(space, attr);
1423				if (!do_match(key, op, value, temp))
1424					break;
1425			} else {
1426				Perror(space, "invalid CARDINFO{} operation");
1427				goto invalid;
1428			}
1429			continue;
1430		}
1431		if (strncasecmp(key, "ATTR{", 5) == 0) {
1432			attr = get_key_attribute(space, key + 4, string, sizeof(string));
1433			if (attr == NULL) {
1434				Perror(space, "error parsing ATTR attribute");
1435				goto invalid;
1436			}
1437			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
1438				pair = value_find(space, "sysfs_device");
1439				if (pair == NULL)
1440					break;
1441				dbg("sysfs_attr: '%s' '%s'", pair->value, attr);
1442				temp = sysfs_attr_get_value(pair->value, attr);
1443				if (!do_match(key, op, value, temp))
1444					break;
1445			} else {
1446				Perror(space, "invalid ATTR{} operation");
1447				goto invalid;
1448			}
1449			continue;
1450		}
1451		if (strncasecmp(key, "ENV{", 4) == 0) {
1452			attr = get_key_attribute(space, key + 3, string, sizeof(string));
1453			if (attr == NULL) {
1454				Perror(space, "error parsing ENV attribute");
1455				goto invalid;
1456			}
1457			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
1458				temp = getenv(attr);
1459				dbg("env: '%s' '%s'", attr, temp);
1460				if (!do_match(key, op, value, temp))
1461					break;
1462			} else if (op == KEY_OP_ASSIGN ||
1463				   op == KEY_OP_ASSIGN_FINAL) {
1464				strlcpy(result, value, sizeof(result));
1465				apply_format(space, result, sizeof(result));
1466				dbg("env set: '%s' '%s'", attr, result);
1467				if (setenv(attr, result, op == KEY_OP_ASSIGN_FINAL))
1468					break;
1469			} else {
1470				Perror(space, "invalid ENV{} operation");
1471				goto invalid;
1472			}
1473			continue;
1474		}
1475		if (strcasecmp(key, "GOTO") == 0) {
1476			if (op != KEY_OP_ASSIGN) {
1477				Perror(space, "invalid GOTO operation");
1478				goto invalid;
1479			}
1480			space->go_to = strdup(value);
1481			if (space->go_to == NULL) {
1482				err = -ENOMEM;
1483				break;
1484			}
1485			continue;
1486		}
1487		if (strcasecmp(key, "INCLUDE") == 0) {
1488			char *rootdir, *go_to;
1489			const char *filename;
1490			struct stat st;
1491			int linenum;
1492			if (op != KEY_OP_ASSIGN) {
1493				Perror(space, "invalid INCLUDE operation");
1494				goto invalid;
1495			}
1496			if (value[0] == '/')
1497				strlcpy(string, value, sizeof(string));
1498			else {
1499				strlcpy(string, space->rootdir, sizeof(string));
1500				strlcat(string, "/", sizeof(string));
1501				strlcat(string, value, sizeof(string));
1502			}
1503			rootdir = space->rootdir;
1504			go_to = space->go_to;
1505			filename = space->filename;
1506			linenum = space->linenum;
1507			if (stat(string, &st)) {
1508				Perror(space, "invalid filename '%s'", string);
1509				continue;
1510			}
1511			if (S_ISDIR(st.st_mode)) {
1512				struct dirent **list;
1513				int i, num;
1514				num = scandir(string, &list, conf_name_filter,
1515					      alphasort);
1516				if (num < 0) {
1517					Perror(space, "invalid directory '%s'", string);
1518					continue;
1519				}
1520				count = strlen(string);
1521				for (i = 0; i < num; i++) {
1522					string[count] = '\0';
1523					strlcat(string, "/", sizeof(string));
1524					strlcat(string, list[i]->d_name, sizeof(string));
1525					space->go_to = NULL;
1526					space->rootdir = new_root_dir(string);
1527					free(list[i]);
1528					if (space->rootdir) {
1529						err = parse(space, string);
1530						free(space->rootdir);
1531					} else
1532						err = -ENOMEM;
1533					if (space->go_to) {
1534						Perror(space, "unterminated GOTO '%s'", space->go_to);
1535						free(space->go_to);
1536					}
1537					if (err)
1538						break;
1539				}
1540				free(list);
1541			} else {
1542				space->go_to = NULL;
1543				space->rootdir = new_root_dir(string);
1544				if (space->rootdir) {
1545					err = parse(space, string);
1546					free(space->rootdir);
1547				} else
1548					err = -ENOMEM;
1549				if (space->go_to) {
1550					Perror(space, "unterminated GOTO '%s'", space->go_to);
1551					free(space->go_to);
1552				}
1553			}
1554			space->go_to = go_to;
1555			space->rootdir = rootdir;
1556			space->filename = filename;
1557			space->linenum = linenum;
1558			if (space->quit)
1559				break;
1560			if (err)
1561				break;
1562			continue;
1563		}
1564		if (strncasecmp(key, "ACCESS", 6) == 0) {
1565			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
1566				if (value[0] == '$') {
1567					strlcpy(string, value, sizeof(string));
1568					apply_format(space, string, sizeof(string));
1569					if (string[0] == '/')
1570						goto __access1;
1571				}
1572				if (value[0] != '/') {
1573					strlcpy(string, space->rootdir, sizeof(string));
1574					strlcat(string, "/", sizeof(string));
1575					strlcat(string, value, sizeof(string));
1576				} else {
1577					strlcpy(string, value, sizeof(string));
1578				}
1579				apply_format(space, string, sizeof(string));
1580			      __access1:
1581				count = access(string, F_OK);
1582				dbg("access(%s) = %i (%s)", string, count, value);
1583				if (op == KEY_OP_MATCH && count != 0)
1584					break;
1585				if (op == KEY_OP_NOMATCH && count == 0)
1586					break;
1587			} else {
1588				Perror(space, "invalid ACCESS operation");
1589				goto invalid;
1590			}
1591			continue;
1592		}
1593		if (strncasecmp(key, "PRINT", 5) == 0) {
1594			if (op != KEY_OP_ASSIGN) {
1595				Perror(space, "invalid PRINT operation");
1596				goto invalid;
1597			}
1598			strlcpy(string, value, sizeof(string));
1599			apply_format(space, string, sizeof(string));
1600			fwrite(string, strlen(string), 1, stdout);
1601			continue;
1602		}
1603		if (strncasecmp(key, "ERROR", 5) == 0) {
1604			if (op != KEY_OP_ASSIGN) {
1605				Perror(space, "invalid ERROR operation");
1606				goto invalid;
1607			}
1608			strlcpy(string, value, sizeof(string));
1609			apply_format(space, string, sizeof(string));
1610			fwrite(string, strlen(string), 1, stderr);
1611			continue;
1612		}
1613		if (strncasecmp(key, "EXIT", 4) == 0) {
1614			if (op != KEY_OP_ASSIGN) {
1615				Perror(space, "invalid EXIT operation");
1616				goto invalid;
1617			}
1618			strlcpy(string, value, sizeof(string));
1619			apply_format(space, string, sizeof(string));
1620			if (strcmp(string, "return") == 0)
1621				return -EJUSTRETURN;
1622			space->exit_code = strtol(string, NULL, 0);
1623			space->quit = 1;
1624			break;
1625		}
1626		if (strncasecmp(key, "CONFIG{", 7) == 0) {
1627			attr = get_key_attribute(space, key + 6, string, sizeof(string));
1628			if (attr == NULL) {
1629				Perror(space, "error parsing CONFIG attribute");
1630				goto invalid;
1631			}
1632			strlcpy(result, value, sizeof(result));
1633			apply_format(space, result, sizeof(result));
1634			if (op == KEY_OP_ASSIGN) {
1635				err = value_set(space, attr, result);
1636				dbg("CONFIG{%s}='%s'", attr, result);
1637				break;
1638			} else if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
1639				pair = value_find(space, attr);
1640				if (pair == NULL)
1641					break;
1642				if (!do_match(key, op, result, pair->value))
1643					break;
1644			} else {
1645				Perror(space, "invalid CONFIG{} operation");
1646				goto invalid;
1647			}
1648		}
1649
1650		Perror(space, "unknown key '%s'", key);
1651	}
1652	return err;
1653
1654invalid:
1655	Perror(space, "invalid rule");
1656	return -EINVAL;
1657}
1658
1659static int parse(struct space *space, const char *filename)
1660{
1661	char *buf, *bufline, *line;
1662	size_t bufsize, pos, count, linesize;
1663	unsigned int linenum, i, j, linenum_adj;
1664	int err;
1665
1666	dbg("start of file '%s'", filename);
1667
1668	if (file_map(filename, &buf, &bufsize) != 0) {
1669		err = errno;
1670		error("Unable to open file '%s': %s", filename, strerror(err));
1671		return -err;
1672	}
1673
1674	err = 0;
1675	pos = 0;
1676	linenum = 0;
1677	linesize = 128;
1678	line = malloc(linesize);
1679	if (line == NULL) {
1680		file_unmap(buf, bufsize);
1681		return -ENOMEM;
1682	}
1683	space->filename = filename;
1684	while (!err && pos < bufsize && !space->quit) {
1685		count = line_width(buf, bufsize, pos);
1686		bufline = buf + pos;
1687		pos += count + 1;
1688		linenum++;
1689
1690		/* skip whitespaces */
1691		while (count > 0 && isspace(bufline[0])) {
1692			bufline++;
1693			count--;
1694		}
1695		if (count == 0)
1696			continue;
1697
1698		/* comment check */
1699		if (bufline[0] == '#')
1700			continue;
1701
1702		if (count > linesize - 1) {
1703			free(line);
1704			line = NULL;
1705			linesize = (count + 127 + 1) & ~127;
1706			if (linesize > 2048) {
1707				error("file %s, line %i too long", filename, linenum);
1708				err = -EINVAL;
1709				break;
1710			}
1711			line = malloc(linesize);
1712			if (line == NULL) {
1713				err = -EINVAL;
1714				break;
1715			}
1716		}
1717
1718		/* skip backslash and newline from multiline rules */
1719		linenum_adj = 0;
1720		for (i = j = 0; i < count; i++) {
1721			if (bufline[i] == '\\' && bufline[i+1] == '\n') {
1722				linenum_adj++;
1723				continue;
1724			}
1725			line[j++] = bufline[i];
1726		}
1727		line[j] = '\0';
1728
1729		dbg("read (%i) '%s'", linenum, line);
1730		space->linenum = linenum;
1731		err = parse_line(space, line, linesize);
1732		if (err == -EJUSTRETURN) {
1733			err = 0;
1734			break;
1735		}
1736		linenum += linenum_adj;
1737	}
1738
1739	free(line);
1740	space->filename = NULL;
1741	space->linenum = -1;
1742	file_unmap(buf, bufsize);
1743	dbg("end of file '%s'", filename);
1744	return err ? err : -abs(space->exit_code);
1745}
1746
1747int init(const char *cfgdir, const char *filename, int flags, const char *cardname)
1748{
1749	struct space *space;
1750	struct snd_card_iterator iter;
1751	int err = 0, lasterr = 0;
1752
1753	sysfs_init();
1754	err = snd_card_iterator_sinit(&iter, cardname);
1755	if (err < 0)
1756		goto out;
1757	while (snd_card_iterator_next(&iter)) {
1758		err = snd_card_clean_cfgdir(cfgdir, iter.card);
1759		if (err < 0) {
1760			if (lasterr == 0)
1761				lasterr = err;
1762			continue;
1763		}
1764		err = init_ucm(flags, iter.card);
1765		if (err == 0)
1766			continue;
1767		err = init_space(&space, iter.card);
1768		if (err != 0)
1769			continue;
1770		space->rootdir = new_root_dir(filename);
1771		if (space->rootdir != NULL) {
1772			err = parse(space, filename);
1773			if (!cardname && err <= -99) { /* non-fatal errors */
1774				if (lasterr == 0)
1775					lasterr = err;
1776				err = 0;
1777			}
1778		}
1779		free_space(space);
1780		if (err < 0)
1781			goto out;
1782	}
1783	err = lasterr ? lasterr : snd_card_iterator_error(&iter);
1784out:
1785	sysfs_cleanup();
1786	return err;
1787}
1788