2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2 of the License, or (at your option) any later version.
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 * Support for the verb/device/modifier core logic and API,
17 * command line tool and file parser was kindly sponsored by
18 * Texas Instruments Inc.
19 * Support for multiple active modifiers and devices,
20 * transition sequences, multiple client access and user defined use
21 * cases was kindly sponsored by Wolfson Microelectronics PLC.
23 * Copyright (C) 2019 Red Hat Inc.
24 * Authors: Jaroslav Kysela <perex@perex.cz>
27 #include "ucm_local.h"
33 static char *rval_open_name(snd_use_case_mgr_t *uc_mgr)
36 if (uc_mgr->conf_format < 3)
38 name = uc_mgr->card_name;
40 if (strncmp(name, "strict:", 7) == 0)
47 static char *rval_conf_libdir(snd_use_case_mgr_t *uc_mgr)
49 if (uc_mgr->conf_format < 4)
51 return strdup(snd_config_topdir());
54 static char *rval_conf_topdir(snd_use_case_mgr_t *uc_mgr)
58 if (uc_mgr->conf_format < 3)
60 dir = uc_mgr_config_dir(uc_mgr->conf_format);
66 static char *rval_conf_dir(snd_use_case_mgr_t *uc_mgr)
68 if (uc_mgr->conf_format < 3)
70 if (uc_mgr->conf_dir_name && uc_mgr->conf_dir_name[0])
71 return strdup(uc_mgr->conf_dir_name);
75 static char *rval_conf_name(snd_use_case_mgr_t *uc_mgr)
77 if (uc_mgr->conf_file_name && uc_mgr->conf_file_name[0])
78 return strdup(uc_mgr->conf_file_name);
82 static char *get_card_number(struct ctl_list *ctl_list)
88 snprintf(num, sizeof(num), "%i", snd_ctl_card_info_get_card(ctl_list->ctl_info));
92 static char *rval_card_number(snd_use_case_mgr_t *uc_mgr)
94 if (uc_mgr->conf_format < 3)
96 return get_card_number(uc_mgr_get_master_ctl(uc_mgr));
99 static char *rval_card_id(snd_use_case_mgr_t *uc_mgr)
101 struct ctl_list *ctl_list;
103 ctl_list = uc_mgr_get_master_ctl(uc_mgr);
104 if (ctl_list == NULL)
106 return strdup(snd_ctl_card_info_get_id(ctl_list->ctl_info));
109 static char *rval_card_driver(snd_use_case_mgr_t *uc_mgr)
111 struct ctl_list *ctl_list;
113 ctl_list = uc_mgr_get_master_ctl(uc_mgr);
114 if (ctl_list == NULL)
116 return strdup(snd_ctl_card_info_get_driver(ctl_list->ctl_info));
119 static char *rval_card_name(snd_use_case_mgr_t *uc_mgr)
121 struct ctl_list *ctl_list;
123 ctl_list = uc_mgr_get_master_ctl(uc_mgr);
124 if (ctl_list == NULL)
126 return strdup(snd_ctl_card_info_get_name(ctl_list->ctl_info));
129 static char *rval_card_longname(snd_use_case_mgr_t *uc_mgr)
131 struct ctl_list *ctl_list;
133 ctl_list = uc_mgr_get_master_ctl(uc_mgr);
134 if (ctl_list == NULL)
136 return strdup(snd_ctl_card_info_get_longname(ctl_list->ctl_info));
139 static char *rval_card_components(snd_use_case_mgr_t *uc_mgr)
141 struct ctl_list *ctl_list;
143 ctl_list = uc_mgr_get_master_ctl(uc_mgr);
144 if (ctl_list == NULL)
146 return strdup(snd_ctl_card_info_get_components(ctl_list->ctl_info));
149 static struct ctl_list *get_ctl_list_by_name(snd_use_case_mgr_t *uc_mgr, const char *id)
154 name = alloca(strlen(id) + 1);
156 index = strchr(name, '#');
159 if (safe_strtol(index + 1, &idx))
162 return uc_mgr_get_ctl_by_name(uc_mgr, name, idx);
165 static char *rval_card_number_by_name(snd_use_case_mgr_t *uc_mgr, const char *id)
167 if (uc_mgr->conf_format < 3) {
168 uc_error("CardNumberByName substitution is supported in v3+ syntax");
172 uc_error("${CardNumberByName} substitution is obsolete - use ${find-card}!");
174 return get_card_number(get_ctl_list_by_name(uc_mgr, id));
177 static char *rval_card_id_by_name(snd_use_case_mgr_t *uc_mgr, const char *id)
179 struct ctl_list *ctl_list;
181 if (uc_mgr->conf_format < 3) {
182 uc_error("CardIdByName substitution is supported in v3+ syntax");
186 uc_error("${CardIdByName} substitution is obsolete - use ${find-card}!");
188 ctl_list = get_ctl_list_by_name(uc_mgr, id);
189 if (ctl_list == NULL)
191 return strdup(snd_ctl_card_info_get_id(ctl_list->ctl_info));
194 typedef struct lookup_iterate *(*lookup_iter_fcn_t)
195 (snd_use_case_mgr_t *uc_mgr, struct lookup_iterate *iter);
196 typedef const char *(*lookup_fcn_t)(void *);
200 const char *(*fcn)(void *opaque);
203 struct lookup_iterate {
204 int (*init)(snd_use_case_mgr_t *uc_mgr, struct lookup_iterate *iter,
205 snd_config_t *config);
206 void (*done)(struct lookup_iterate *iter);
207 lookup_iter_fcn_t first;
208 lookup_iter_fcn_t next;
209 char *(*retfcn)(struct lookup_iterate *iter, snd_config_t *config);
210 struct lookup_fcn *fcns;
212 struct ctl_list *ctl_list;
216 static snd_config_t *parse_lookup_query(const char *query)
219 snd_config_t *config;
222 err = snd_input_buffer_open(&input, query, strlen(query));
224 uc_error("unable to create memory input buffer");
227 err = snd_config_top(&config);
229 snd_input_close(input);
232 err = snd_config_load(config, input);
233 snd_input_close(input);
235 snd_config_delete(config);
236 uc_error("wrong arguments '%s'", query);
242 static char *rval_lookup_main(snd_use_case_mgr_t *uc_mgr,
244 struct lookup_iterate *iter)
246 snd_config_t *config, *d;
247 struct lookup_fcn *fcn;
248 struct lookup_iterate *curr;
255 if (uc_mgr->conf_format < 4) {
256 uc_error("Lookups are supported in v4+ syntax");
260 config = parse_lookup_query(query);
263 if (iter->init && iter->init(uc_mgr, iter, config))
265 if (snd_config_search(config, "field", &d)) {
266 uc_error("Lookups require field!");
269 if (snd_config_get_string(d, &s))
271 for (fcn = iter->fcns ; fcn; fcn++) {
272 if (strcasecmp(fcn->name, s) == 0) {
273 iter->fcn = fcn->fcn;
277 if (iter->fcn == NULL) {
278 uc_error("Unknown field value '%s'", s);
281 if (snd_config_search(config, "regex", &d)) {
282 uc_error("Lookups require regex!");
285 if (snd_config_get_string(d, &s))
287 err = regcomp(&re, s, REG_EXTENDED | REG_ICASE);
289 uc_error("Regex '%s' compilation failed (code %d)", s, err);
294 for (curr = iter->first(uc_mgr, iter); curr; curr = iter->next(uc_mgr, iter)) {
295 s = curr->fcn(iter->info);
298 if (regexec(&re, s, ARRAY_SIZE(match), match, 0) == 0) {
299 result = curr->retfcn(iter, config);
305 snd_config_delete(config);
314 static struct lookup_iterate *rval_card_lookup1(snd_use_case_mgr_t *uc_mgr,
315 struct lookup_iterate *iter,
318 if (snd_card_next(&card) < 0 || card < 0)
320 iter->ctl_list = uc_mgr_get_ctl_by_card(uc_mgr, card);
321 if (iter->ctl_list == NULL)
323 iter->info = iter->ctl_list->ctl_info;
327 static struct lookup_iterate *rval_card_lookup_first(snd_use_case_mgr_t *uc_mgr,
328 struct lookup_iterate *iter)
330 return rval_card_lookup1(uc_mgr, iter, -1);
333 static struct lookup_iterate *rval_card_lookup_next(snd_use_case_mgr_t *uc_mgr,
334 struct lookup_iterate *iter)
336 return rval_card_lookup1(uc_mgr, iter, snd_ctl_card_info_get_card(iter->info));
339 static char *rval_card_lookup_return(struct lookup_iterate *iter, snd_config_t *config)
344 if (snd_config_search(config, "return", &d))
345 return strdup(snd_ctl_card_info_get_id(iter->info));
346 else if (snd_config_get_string(d, &s))
348 else if (strcasecmp(s, "id") == 0)
349 return strdup(snd_ctl_card_info_get_id(iter->info));
350 else if (strcasecmp(s, "number") == 0) {
352 snprintf(num, sizeof(num), "%d", snd_ctl_card_info_get_card(iter->info));
355 uc_error("Unknown return type '%s'", s);
360 static char *rval_card_lookup(snd_use_case_mgr_t *uc_mgr, const char *query)
362 static struct lookup_fcn fcns[] = {
363 { .name = "id", (lookup_fcn_t)snd_ctl_card_info_get_id },
364 { .name = "driver", (lookup_fcn_t)snd_ctl_card_info_get_driver },
365 { .name = "name", (lookup_fcn_t)snd_ctl_card_info_get_name },
366 { .name = "longname", (lookup_fcn_t)snd_ctl_card_info_get_longname },
367 { .name = "mixername", (lookup_fcn_t)snd_ctl_card_info_get_mixername },
368 { .name = "components", (lookup_fcn_t)snd_ctl_card_info_get_components },
371 struct lookup_iterate iter = {
372 .first = rval_card_lookup_first,
373 .next = rval_card_lookup_next,
374 .retfcn = rval_card_lookup_return,
377 return rval_lookup_main(uc_mgr, query, &iter);
380 static struct lookup_iterate *rval_pcm_lookup1(struct lookup_iterate *iter,
383 snd_pcm_info_t *pcminfo;
384 snd_ctl_t *ctl = iter->ctl_list->ctl;
388 if (snd_ctl_pcm_next_device(ctl, &device) < 0 || device < 0)
390 pcminfo = iter->info;
391 snd_pcm_info_set_device(pcminfo, device);
392 err = snd_ctl_pcm_info(ctl, pcminfo);
396 uc_error("Unable to obtain PCM info (device %d)", device);
402 static struct lookup_iterate *rval_pcm_lookup_first(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
403 struct lookup_iterate *iter)
405 return rval_pcm_lookup1(iter, -1);
408 static struct lookup_iterate *rval_pcm_lookup_next(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
409 struct lookup_iterate *iter)
411 return rval_pcm_lookup1(iter, snd_pcm_info_get_device(iter->info));
414 static char *rval_pcm_lookup_return(struct lookup_iterate *iter,
415 snd_config_t *config ATTRIBUTE_UNUSED)
418 snprintf(num, sizeof(num), "%d", snd_pcm_info_get_device(iter->info));
422 static int rval_pcm_lookup_init(struct lookup_iterate *iter,
423 snd_config_t *config)
425 static struct lookup_fcn pcm_fcns[] = {
426 { .name = "id", (lookup_fcn_t)snd_pcm_info_get_id },
427 { .name = "name", (lookup_fcn_t)snd_pcm_info_get_name },
428 { .name = "subname", (lookup_fcn_t)snd_pcm_info_get_subdevice_name },
433 snd_pcm_info_t *pcminfo;
434 snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
436 if (snd_config_search(config, "stream", &d) == 0 &&
437 snd_config_get_string(d, &s) == 0) {
438 if (strcasecmp(s, "playback") == 0)
439 stream = SND_PCM_STREAM_PLAYBACK;
440 else if (strcasecmp(s, "capture") == 0)
441 stream = SND_PCM_STREAM_CAPTURE;
443 uc_error("Unknown stream type '%s'", s);
447 if (snd_pcm_info_malloc(&pcminfo))
449 snd_pcm_info_set_device(pcminfo, 0);
450 snd_pcm_info_set_subdevice(pcminfo, 0);
451 snd_pcm_info_set_stream(pcminfo, stream);
452 iter->first = rval_pcm_lookup_first;
453 iter->next = rval_pcm_lookup_next;
454 iter->retfcn = rval_pcm_lookup_return;
455 iter->fcns = pcm_fcns;
456 iter->info = pcminfo;
460 static int rval_device_lookup_init(snd_use_case_mgr_t *uc_mgr,
461 struct lookup_iterate *iter,
462 snd_config_t *config)
466 int (*init)(struct lookup_iterate *iter, snd_config_t *config);
468 { .name = "pcm", .init = rval_pcm_lookup_init },
475 if (snd_config_search(config, "ctl", &d) || snd_config_get_string(d, &s)) {
476 iter->ctl_list = uc_mgr_get_master_ctl(uc_mgr);
477 if (iter->ctl_list == NULL) {
478 uc_error("Control device is not defined!");
482 err = uc_mgr_open_ctl(uc_mgr, &iter->ctl_list, s, 1);
484 uc_error("Control device '%s' not found", s);
488 if (snd_config_search(config, "type", &d) || snd_config_get_string(d, &s)) {
489 uc_error("Missing device type!");
492 for (t = types; t->name; t++)
493 if (strcasecmp(t->name, s) == 0)
494 return t->init(iter, config);
495 uc_error("Device type '%s' is invalid", s);
499 static void rval_device_lookup_done(struct lookup_iterate *iter)
504 static char *rval_device_lookup(snd_use_case_mgr_t *uc_mgr, const char *query)
506 struct lookup_iterate iter = {
507 .init = rval_device_lookup_init,
508 .done = rval_device_lookup_done,
510 return rval_lookup_main(uc_mgr, query, &iter);
513 static char *rval_env(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, const char *id)
523 static char *rval_sysfs(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, const char *id)
525 char path[PATH_MAX], link[PATH_MAX + 1];
531 e = uc_mgr_sysfs_root();
536 snprintf(path, sizeof(path), "%s/%s", e, id);
537 if (lstat(path, &sb) != 0)
539 if (S_ISLNK(sb.st_mode)) {
540 len = readlink(path, link, sizeof(link) - 1);
542 uc_error("sysfs: cannot read link '%s' (%d)", path, errno);
546 e = strrchr(link, '/');
548 return strdup(e + 1);
551 if (S_ISDIR(sb.st_mode))
553 if ((sb.st_mode & S_IRUSR) == 0)
556 fd = open(path, O_RDONLY);
558 uc_error("sysfs open failed for '%s' (%d)", path, errno);
561 len = read(fd, path, sizeof(path)-1);
564 uc_error("sysfs unable to read value '%s' (%d)", path, errno);
567 while (len > 0 && path[len-1] == '\n')
573 static char *rval_var(snd_use_case_mgr_t *uc_mgr, const char *id)
577 if (uc_mgr->conf_format < 3) {
578 uc_error("variable substitution is supported in v3+ syntax");
582 v = uc_mgr_get_variable(uc_mgr, id);
588 #define MATCH_VARIABLE(name, id, fcn, empty_ok) \
589 if (strncmp((name), (id), sizeof(id) - 1) == 0) { \
590 rval = fcn(uc_mgr); \
591 idsize = sizeof(id) - 1; \
592 allow_empty = (empty_ok); \
596 #define MATCH_VARIABLE2(name, id, fcn, empty_ok) \
597 if (strncmp((name), (id), sizeof(id) - 1) == 0) { \
598 idsize = sizeof(id) - 1; \
599 allow_empty = (empty_ok); \
605 * skip escaped } character (simple version)
607 static inline const char *strchr_with_escape(const char *str, char c)
614 if (*(s - 1) == '\\') {
624 * remove escaped } character (simple version)
626 static inline void strncpy_with_escape(char *dst, const char *src, size_t len)
631 while (c != '\0' && len > 0) {
632 if (c == '\\' && *src == '}') {
643 int uc_mgr_get_substituted_value(snd_use_case_mgr_t *uc_mgr,
647 size_t size, nsize, idsize, rvalsize, dpos = 0;
649 char *r, *nr, *rval, v2[128];
650 bool ignore_error, allow_empty;
651 char *(*fcn2)(snd_use_case_mgr_t *, const char *id);
657 size = strlen(value) + 1;
669 ignore_error = false;
670 if (value[1] == '$' && value[2] == '{' && uc_mgr->conf_format >= 3) {
673 } else if (value[1] != '{') {
677 MATCH_VARIABLE(value, "${OpenName}", rval_open_name, false);
678 MATCH_VARIABLE(value, "${ConfLibDir}", rval_conf_libdir, false);
679 MATCH_VARIABLE(value, "${ConfTopDir}", rval_conf_topdir, false);
680 MATCH_VARIABLE(value, "${ConfDir}", rval_conf_dir, false);
681 MATCH_VARIABLE(value, "${ConfName}", rval_conf_name, false);
682 MATCH_VARIABLE(value, "${CardNumber}", rval_card_number, true);
683 MATCH_VARIABLE(value, "${CardId}", rval_card_id, false);
684 MATCH_VARIABLE(value, "${CardDriver}", rval_card_driver, false);
685 MATCH_VARIABLE(value, "${CardName}", rval_card_name, false);
686 MATCH_VARIABLE(value, "${CardLongName}", rval_card_longname, false);
687 MATCH_VARIABLE(value, "${CardComponents}", rval_card_components, true);
688 MATCH_VARIABLE2(value, "${env:", rval_env, false);
689 MATCH_VARIABLE2(value, "${sys:", rval_sysfs, false);
690 MATCH_VARIABLE2(value, "${var:", rval_var, true);
691 MATCH_VARIABLE2(value, "${find-card:", rval_card_lookup, false);
692 MATCH_VARIABLE2(value, "${find-device:", rval_device_lookup, false);
693 MATCH_VARIABLE2(value, "${CardNumberByName:", rval_card_number_by_name, false);
694 MATCH_VARIABLE2(value, "${CardIdByName:", rval_card_id_by_name, false);
697 tmp = strchr(value, '}');
699 strncpy(r, value, tmp + 1 - value);
700 r[tmp + 1 - value] = '\0';
701 uc_error("variable '%s' is not known!", r);
703 uc_error("variable reference '%s' is not complete", value);
707 tmp = strchr_with_escape(value + idsize, '}');
709 rvalsize = tmp - (value + idsize);
710 if (rvalsize >= sizeof(v2)) {
714 strncpy_with_escape(v2, value + idsize, rvalsize);
715 idsize += rvalsize + 1;
716 if (*v2 == '$' && uc_mgr->conf_format >= 3) {
717 tmp = uc_mgr_get_variable(uc_mgr, v2 + 1);
719 uc_error("define '%s' is not reachable in this context!", v2 + 1);
722 rval = fcn2(uc_mgr, tmp);
725 rval = fcn2(uc_mgr, v2);
731 if (rval == NULL || (!allow_empty && rval[0] == '\0')) {
737 strncpy(r, value, idsize);
739 uc_error("variable '%s' is %s in this context!", r,
740 rval ? "empty" : "not defined");
745 rvalsize = strlen(rval);
746 nsize = size + rvalsize - idsize;
748 nr = realloc(r, nsize);
757 strcpy(r + dpos, rval);
771 static inline int uc_mgr_substitute_check(const char *s)
773 return s && strstr(s, "${") != NULL;
776 int uc_mgr_substitute_tree(snd_use_case_mgr_t *uc_mgr, snd_config_t *node)
778 snd_config_iterator_t i, next;
784 err = snd_config_get_id(node, &id);
787 if (uc_mgr_substitute_check(id)) {
788 err = uc_mgr_get_substituted_value(uc_mgr, &s, id);
791 err = snd_config_set_id(node, s);
793 uc_error("unable to set substituted id '%s' (old id '%s')", s, id);
799 if (snd_config_get_type(node) != SND_CONFIG_TYPE_COMPOUND) {
800 if (snd_config_get_type(node) == SND_CONFIG_TYPE_STRING) {
801 err = snd_config_get_string(node, &s2);
804 if (!uc_mgr_substitute_check(s2))
806 err = uc_mgr_get_substituted_value(uc_mgr, &s, s2);
809 err = snd_config_set_string(node, s);
816 snd_config_for_each(i, next, node) {
817 n = snd_config_iterator_entry(i);
818 err = uc_mgr_substitute_tree(uc_mgr, n);