1 /*
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.
6 *
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.
11 *
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
15 *
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.
22 *
23 * Copyright (C) 2019 Red Hat Inc.
24 * Authors: Jaroslav Kysela <perex@perex.cz>
25 */
26
27 #include "ucm_local.h"
28 #include <regex.h>
29
get_string(snd_config_t * compound,const char * key,const char ** str)30 static int get_string(snd_config_t *compound, const char *key, const char **str)
31 {
32 snd_config_t *node;
33 int err;
34
35 err = snd_config_search(compound, key, &node);
36 if (err < 0)
37 return err;
38 return snd_config_get_string(node, str);
39 }
40
if_eval_string(snd_use_case_mgr_t * uc_mgr,snd_config_t * eval)41 static int if_eval_string(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval)
42 {
43 const char *string1 = NULL, *string2 = NULL;
44 char *s1, *s2;
45 int err;
46
47 if (uc_mgr->conf_format >= 3) {
48 err = get_string(eval, "Empty", &string1);
49 if (err < 0 && err != -ENOENT) {
50 uc_error("String error (If.Condition.Empty)");
51 return -EINVAL;
52 }
53
54 if (string1) {
55 err = uc_mgr_get_substituted_value(uc_mgr, &s1, string1);
56 if (err < 0)
57 return err;
58 err = s1 == NULL || s1[0] == '\0';
59 free(s1);
60 return err;
61 }
62 }
63
64 err = get_string(eval, "String1", &string1);
65 if (err < 0 && err != -ENOENT) {
66 uc_error("String error (If.Condition.String1)");
67 return -EINVAL;
68 }
69
70 err = get_string(eval, "String2", &string2);
71 if (err < 0 && err != -ENOENT) {
72 uc_error("String error (If.Condition.String2)");
73 return -EINVAL;
74 }
75
76 if (string1 || string2) {
77 if (string1 == NULL) {
78 uc_error("If.Condition.String1 not defined");
79 return -EINVAL;
80 }
81 if (string2 == NULL) {
82 uc_error("If.Condition.String2 not defined");
83 return -EINVAL;
84 }
85 err = uc_mgr_get_substituted_value(uc_mgr, &s1, string1);
86 if (err < 0)
87 return err;
88 err = uc_mgr_get_substituted_value(uc_mgr, &s2, string2);
89 if (err < 0) {
90 free(s1);
91 return err;
92 }
93 err = strcasecmp(s1, s2) == 0;
94 free(s2);
95 free(s1);
96 return err;
97 }
98
99 err = get_string(eval, "Haystack", &string1);
100 if (err < 0 && err != -ENOENT) {
101 uc_error("String error (If.Condition.Haystack)");
102 return -EINVAL;
103 }
104
105 err = get_string(eval, "Needle", &string2);
106 if (err < 0 && err != -ENOENT) {
107 uc_error("String error (If.Condition.Needle)");
108 return -EINVAL;
109 }
110
111 if (string1 || string2) {
112 if (string1 == NULL) {
113 uc_error("If.Condition.Haystack not defined");
114 return -EINVAL;
115 }
116 if (string2 == NULL) {
117 uc_error("If.Condition.Needle not defined");
118 return -EINVAL;
119 }
120 err = uc_mgr_get_substituted_value(uc_mgr, &s1, string1);
121 if (err < 0)
122 return err;
123 err = uc_mgr_get_substituted_value(uc_mgr, &s2, string2);
124 if (err < 0) {
125 free(s1);
126 return err;
127 }
128 err = strstr(s1, s2) != NULL;
129 free(s2);
130 free(s1);
131 return err;
132 }
133
134 uc_error("Unknown String condition arguments");
135 return -EINVAL;
136 }
137
if_eval_regex_match(snd_use_case_mgr_t * uc_mgr,snd_config_t * eval)138 static int if_eval_regex_match(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval)
139 {
140 const char *string, *regex_string;
141 char *s;
142 regex_t re;
143 int options = REG_EXTENDED | REG_ICASE;
144 regmatch_t match[1];
145 int err;
146
147 err = get_string(eval, "String", &string);
148 if (err < 0) {
149 uc_error("RegexMatch error (If.Condition.String)");
150 return -EINVAL;
151 }
152
153 err = get_string(eval, "Regex", ®ex_string);
154 if (err < 0) {
155 uc_error("RegexMatch error (If.Condition.Regex)");
156 return -EINVAL;
157 }
158
159 err = uc_mgr_get_substituted_value(uc_mgr, &s, regex_string);
160 if (err < 0)
161 return err;
162 err = regcomp(&re, s, options);
163 if (err) {
164 uc_error("Regex '%s' compilation failed (code %d)", s, err);
165 free(s);
166 return -EINVAL;
167 }
168 free(s);
169
170 err = uc_mgr_get_substituted_value(uc_mgr, &s, string);
171 if (err < 0) {
172 regfree(&re);
173 return err;
174 }
175 err = regexec(&re, s, ARRAY_SIZE(match), match, 0);
176 free(s);
177 regfree(&re);
178 return err == 0;
179 }
180
if_eval_control_exists(snd_use_case_mgr_t * uc_mgr,snd_config_t * eval)181 static int if_eval_control_exists(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval)
182 {
183 snd_ctl_t *ctl;
184 struct ctl_list *ctl_list;
185 const char *device = NULL, *ctldef, *enumval = NULL, *name;
186 snd_ctl_elem_id_t *elem_id;
187 snd_ctl_elem_info_t *elem_info;
188 snd_ctl_elem_type_t type;
189 char *s;
190 int err, i, items;
191
192 snd_ctl_elem_id_alloca(&elem_id);
193 snd_ctl_elem_info_alloca(&elem_info);
194
195 err = get_string(eval, "Device", &device);
196 if (err < 0 && err != -ENOENT) {
197 uc_error("ControlExists error (If.Condition.Device)");
198 return -EINVAL;
199 }
200
201 err = get_string(eval, "Control", &ctldef);
202 if (err < 0) {
203 uc_error("ControlExists error (If.Condition.Control)");
204 return -EINVAL;
205 }
206
207 err = get_string(eval, "ControlEnum", &enumval);
208 if (err < 0 && err != -ENOENT) {
209 uc_error("ControlExists error (If.Condition.ControlEnum)");
210 return -EINVAL;
211 }
212
213 err = uc_mgr_get_substituted_value(uc_mgr, &s, ctldef);
214 if (err < 0)
215 return err;
216 err = snd_ctl_ascii_elem_id_parse(elem_id, s);
217 free(s);
218 if (err < 0) {
219 uc_error("unable to parse element identificator (%s)", ctldef);
220 return -EINVAL;
221 }
222
223 if (device == NULL) {
224 ctl = uc_mgr_get_ctl(uc_mgr);
225 if (ctl == NULL) {
226 uc_error("cannot determine control device");
227 return -EINVAL;
228 }
229 } else {
230 err = uc_mgr_get_substituted_value(uc_mgr, &s, device);
231 if (err < 0)
232 return err;
233 err = uc_mgr_open_ctl(uc_mgr, &ctl_list, s, 1);
234 free(s);
235 if (err < 0)
236 return err;
237 ctl = ctl_list->ctl;
238 }
239
240 snd_ctl_elem_info_set_id(elem_info, elem_id);
241 err = snd_ctl_elem_info(ctl, elem_info);
242 if (err < 0)
243 return 0;
244
245 if (enumval) {
246 type = snd_ctl_elem_info_get_type(elem_info);
247 if (type != SND_CTL_ELEM_TYPE_ENUMERATED)
248 return 0;
249 err = uc_mgr_get_substituted_value(uc_mgr, &s, enumval);
250 if (err < 0)
251 return err;
252 items = snd_ctl_elem_info_get_items(elem_info);
253 for (i = 0; i < items; i++) {
254 snd_ctl_elem_info_set_item(elem_info, i);
255 err = snd_ctl_elem_info(ctl, elem_info);
256 if (err < 0) {
257 free(s);
258 return err;
259 }
260 name = snd_ctl_elem_info_get_item_name(elem_info);
261 if (strcasecmp(name, s) == 0) {
262 free(s);
263 return 1;
264 }
265 }
266 free(s);
267 return 0;
268 }
269
270 return 1;
271 }
272
if_eval_path(snd_use_case_mgr_t * uc_mgr,snd_config_t * eval)273 static int if_eval_path(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval)
274 {
275 const char *path, *mode = "";
276 int err, amode = F_OK;
277
278 if (uc_mgr->conf_format < 4) {
279 uc_error("Path condition is supported in v4+ syntax");
280 return -EINVAL;
281 }
282
283 err = get_string(eval, "Path", &path);
284 if (err < 0) {
285 uc_error("Path error (If.Condition.Path)");
286 return -EINVAL;
287 }
288
289 err = get_string(eval, "Mode", &mode);
290 if (err < 0 && err != -ENOENT) {
291 uc_error("Path error (If.Condition.Mode)");
292 return -EINVAL;
293 }
294
295 if (strncasecmp(mode, "exist", 5) == 0) {
296 amode = F_OK;
297 } else if (strcasecmp(mode, "read") == 0) {
298 amode = R_OK;
299 } else if (strcasecmp(mode, "write") == 0) {
300 amode = W_OK;
301 } else if (strcasecmp(mode, "exec") == 0) {
302 amode = X_OK;
303 } else {
304 uc_error("Path unknown mode (If.Condition.Mode)");
305 return -EINVAL;
306 }
307
308 #ifdef HAVE_EACCESS
309 if (eaccess(path, amode))
310 #else
311 if (access(path, amode))
312 #endif
313 return 0;
314
315 return 1;
316 }
317
if_eval(snd_use_case_mgr_t * uc_mgr,snd_config_t * eval)318 static int if_eval(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval)
319 {
320 const char *type;
321 int err;
322
323 if (snd_config_get_type(eval) != SND_CONFIG_TYPE_COMPOUND) {
324 uc_error("compound type expected for If.Condition");
325 return -EINVAL;
326 }
327
328 err = get_string(eval, "Type", &type);
329 if (err < 0) {
330 uc_error("type block error (If.Condition)");
331 return -EINVAL;
332 }
333
334 if (strcmp(type, "AlwaysTrue") == 0)
335 return 1;
336
337 if (strcmp(type, "String") == 0)
338 return if_eval_string(uc_mgr, eval);
339
340 if (strcmp(type, "ControlExists") == 0)
341 return if_eval_control_exists(uc_mgr, eval);
342
343 if (strcmp(type, "RegexMatch") == 0)
344 return if_eval_regex_match(uc_mgr, eval);
345
346 if (strcmp(type, "Path") == 0)
347 return if_eval_path(uc_mgr, eval);
348
349 uc_error("unknown If.Condition.Type");
350 return -EINVAL;
351 }
352
if_eval_one(snd_use_case_mgr_t * uc_mgr,snd_config_t * cond,snd_config_t ** result,snd_config_t ** before,snd_config_t ** after)353 static int if_eval_one(snd_use_case_mgr_t *uc_mgr,
354 snd_config_t *cond,
355 snd_config_t **result,
356 snd_config_t **before,
357 snd_config_t **after)
358 {
359 snd_config_t *expr, *_true = NULL, *_false = NULL;
360 int err;
361
362 *result = NULL;
363
364 if (snd_config_get_type(cond) != SND_CONFIG_TYPE_COMPOUND) {
365 uc_error("compound type expected for If.1");
366 return -EINVAL;
367 }
368
369 if (snd_config_search(cond, "Condition", &expr) < 0) {
370 uc_error("condition block expected (If)");
371 return -EINVAL;
372 }
373
374 err = snd_config_search(cond, "True", &_true);
375 if (err < 0 && err != -ENOENT) {
376 uc_error("true block error (If)");
377 return -EINVAL;
378 }
379
380 err = snd_config_search(cond, "False", &_false);
381 if (err < 0 && err != -ENOENT) {
382 uc_error("false block error (If)");
383 return -EINVAL;
384 }
385
386 err = snd_config_search(cond, "Before", before);
387 if (err < 0 && err != -ENOENT) {
388 uc_error("before block identifier error");
389 return -EINVAL;
390 }
391
392 err = snd_config_search(cond, "After", after);
393 if (err < 0 && err != -ENOENT) {
394 uc_error("before block identifier error");
395 return -EINVAL;
396 }
397
398 err = if_eval(uc_mgr, expr);
399 if (err > 0) {
400 *result = _true;
401 return 0;
402 } else if (err == 0) {
403 *result = _false;
404 return 0;
405 } else {
406 return err;
407 }
408 }
409
410 #if 0
411 static void config_dump(snd_config_t *cfg)
412 {
413 snd_output_t *out;
414 snd_output_stdio_attach(&out, stderr, 0);
415 snd_output_printf(out, "-----\n");
416 snd_config_save(cfg, out);
417 snd_output_close(out);
418 }
419 #endif
420
421 /*
422 * put back the result from all conditions to the parent
423 */
uc_mgr_evaluate_condition(snd_use_case_mgr_t * uc_mgr,snd_config_t * parent,snd_config_t * cond)424 int uc_mgr_evaluate_condition(snd_use_case_mgr_t *uc_mgr,
425 snd_config_t *parent,
426 snd_config_t *cond)
427 {
428 snd_config_iterator_t i, next;
429 snd_config_t *a, *n, *before, *after;
430 int err;
431
432 if (uc_mgr->conf_format < 2) {
433 uc_error("conditions are not supported for v1 syntax");
434 return -EINVAL;
435 }
436
437 if (snd_config_get_type(cond) != SND_CONFIG_TYPE_COMPOUND) {
438 uc_error("compound type expected for If");
439 return -EINVAL;
440 }
441
442 snd_config_for_each(i, next, cond) {
443 n = snd_config_iterator_entry(i);
444 before = after = NULL;
445 err = if_eval_one(uc_mgr, n, &a, &before, &after);
446 if (err < 0)
447 return err;
448 if (a == NULL)
449 continue;
450 err = uc_mgr_evaluate_inplace(uc_mgr, a);
451 if (err < 0)
452 return err;
453 err = uc_mgr_config_tree_merge(uc_mgr, parent, a, before, after);
454 if (err < 0)
455 return err;
456 snd_config_delete(a);
457 }
458 return 0;
459 }
460