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
48 enum 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
57 struct pair {
58 char *key;
59 char *value;
60 struct pair *next;
61 };
62
63 struct 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
Perror(struct space * space,const char * fmt,...)81 static 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
free_space(struct space * space)96 static 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
value_find(struct space * space,const char * key)138 static 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
value_set(struct space * space,const char * key,const char * value)147 static 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
init_space(struct space ** space,int card)178 static 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
cardinfo_get(struct space * space,const char * attr)218 static 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
check_id_changed(struct space * space,unsigned int what)241 static 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
get_ctl_value(struct space * space)269 static 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
set_ctl_value(struct space * space,const char * value,int all)340 static 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 ? pos - value : 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
do_match(const char * key,enum key_op op,const char * key_value,const char * value)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
ctl_match(snd_ctl_elem_id_t * pattern,snd_ctl_elem_id_t * id)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
elemid_get(struct space * space,const char * attr)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, "%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
elemid_set(struct space * space,const char * attr,const char * value)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
get_key(char ** line,char ** key,enum key_op * op,char ** value)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} */
get_key_attribute(struct space * space,char * str,char * res,size_t ressize)942 static 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 */
get_format_attribute(struct space * space,char ** str)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*/
get_format_len(struct space * space,char ** str)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
apply_format(struct space * space,char * string,size_t maxsize)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, 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
1242 static
run_program1(struct space * space,const char * command0,char * result,size_t ressize,size_t * reslen,int log)1243 int run_program1(struct space *space,
1244 const char *command0, char *result,
1245 size_t ressize, 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, ressize);
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
new_root_dir(const char * filename)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" */
conf_name_filter(const struct dirent * d)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
parse_line(struct space * space,char * line,size_t linesize)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
parse(struct space * space,const char * filename)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 file_unmap(buf, bufsize);
1680 return -ENOMEM;
1681 }
1682 space->filename = filename;
1683 while (!err && pos < bufsize && !space->quit) {
1684 count = line_width(buf, bufsize, pos);
1685 bufline = buf + pos;
1686 pos += count + 1;
1687 linenum++;
1688
1689 /* skip whitespaces */
1690 while (count > 0 && isspace(bufline[0])) {
1691 bufline++;
1692 count--;
1693 }
1694 if (count == 0)
1695 continue;
1696
1697 /* comment check */
1698 if (bufline[0] == '#')
1699 continue;
1700
1701 if (count > linesize - 1) {
1702 free(line);
1703 line = NULL;
1704 linesize = (count + 127 + 1) & ~127;
1705 if (linesize > 2048) {
1706 error("file %s, line %i too long", filename, linenum);
1707 err = -EINVAL;
1708 break;
1709 }
1710 line = malloc(linesize);
1711 if (line == NULL) {
1712 err = -EINVAL;
1713 break;
1714 }
1715 }
1716
1717 /* skip backslash and newline from multiline rules */
1718 linenum_adj = 0;
1719 for (i = j = 0; i < count; i++) {
1720 if (bufline[i] == '\\' && bufline[i+1] == '\n') {
1721 linenum_adj++;
1722 continue;
1723 }
1724 line[j++] = bufline[i];
1725 }
1726 line[j] = '\0';
1727
1728 dbg("read (%i) '%s'", linenum, line);
1729 space->linenum = linenum;
1730 err = parse_line(space, line, linesize);
1731 if (err == -EJUSTRETURN) {
1732 err = 0;
1733 break;
1734 }
1735 linenum += linenum_adj;
1736 }
1737
1738 free(line);
1739 space->filename = NULL;
1740 space->linenum = -1;
1741 file_unmap(buf, bufsize);
1742 dbg("end of file '%s'", filename);
1743 return err ? err : -abs(space->exit_code);
1744 }
1745
init(const char * cfgdir,const char * filename,int flags,const char * cardname)1746 int init(const char *cfgdir, const char *filename, int flags, const char *cardname)
1747 {
1748 struct space *space;
1749 struct snd_card_iterator iter;
1750 int err = 0, lasterr = 0;
1751
1752 sysfs_init();
1753 err = snd_card_iterator_sinit(&iter, cardname);
1754 if (err < 0)
1755 goto out;
1756 while (snd_card_iterator_next(&iter)) {
1757 err = snd_card_clean_cfgdir(cfgdir, iter.card);
1758 if (err < 0) {
1759 if (lasterr == 0)
1760 lasterr = err;
1761 continue;
1762 }
1763 err = init_ucm(flags, iter.card);
1764 if (err == 0)
1765 continue;
1766 err = init_space(&space, iter.card);
1767 if (err != 0)
1768 continue;
1769 space->rootdir = new_root_dir(filename);
1770 if (space->rootdir != NULL) {
1771 err = parse(space, filename);
1772 if (!cardname && err <= -99) { /* non-fatal errors */
1773 if (lasterr == 0)
1774 lasterr = err;
1775 err = 0;
1776 }
1777 }
1778 free_space(space);
1779 if (err < 0)
1780 goto out;
1781 }
1782 err = lasterr ? lasterr : snd_card_iterator_error(&iter);
1783 out:
1784 sysfs_cleanup();
1785 return err;
1786 }
1787