1 // SPDX-License-Identifier: GPL-2.0
2 //
3 // kselftest configuration helpers for the hw specific configuration
4 //
5 // Original author: Jaroslav Kysela <perex@perex.cz>
6 // Copyright (c) 2022 Red Hat Inc.
7 
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <stdbool.h>
11 #include <errno.h>
12 #include <assert.h>
13 #include <dirent.h>
14 #include <regex.h>
15 #include <sys/stat.h>
16 
17 #include "../kselftest.h"
18 #include "alsa-local.h"
19 
20 #define SYSFS_ROOT "/sys"
21 
22 struct card_data {
23 	int card;
24 	snd_config_t *config;
25 	const char *filename;
26 	struct card_data *next;
27 };
28 
29 static struct card_data *conf_cards;
30 
31 static const char *alsa_config =
32 "ctl.hw {\n"
33 "	@args [ CARD ]\n"
34 "	@args.CARD.type string\n"
35 "	type hw\n"
36 "	card $CARD\n"
37 "}\n"
38 "pcm.hw {\n"
39 "	@args [ CARD DEV SUBDEV ]\n"
40 "	@args.CARD.type string\n"
41 "	@args.DEV.type integer\n"
42 "	@args.SUBDEV.type integer\n"
43 "	type hw\n"
44 "	card $CARD\n"
45 "	device $DEV\n"
46 "	subdevice $SUBDEV\n"
47 "}\n"
48 ;
49 
50 #ifdef SND_LIB_VER
51 #if SND_LIB_VERSION >= SND_LIB_VER(1, 2, 6)
52 #define LIB_HAS_LOAD_STRING
53 #endif
54 #endif
55 
56 #ifndef LIB_HAS_LOAD_STRING
57 static int snd_config_load_string(snd_config_t **config, const char *s,
58 				  size_t size)
59 {
60 	snd_input_t *input;
61 	snd_config_t *dst;
62 	int err;
63 
64 	assert(config && s);
65 	if (size == 0)
66 		size = strlen(s);
67 	err = snd_input_buffer_open(&input, s, size);
68 	if (err < 0)
69 		return err;
70 	err = snd_config_top(&dst);
71 	if (err < 0) {
72 		snd_input_close(input);
73 		return err;
74 	}
75 	err = snd_config_load(dst, input);
76 	snd_input_close(input);
77 	if (err < 0) {
78 		snd_config_delete(dst);
79 		return err;
80 	}
81 	*config = dst;
82 	return 0;
83 }
84 #endif
85 
86 snd_config_t *get_alsalib_config(void)
87 {
88 	snd_config_t *config;
89 	int err;
90 
91 	err = snd_config_load_string(&config, alsa_config, strlen(alsa_config));
92 	if (err < 0) {
93 		ksft_print_msg("Unable to parse custom alsa-lib configuration: %s\n",
94 			       snd_strerror(err));
95 		ksft_exit_fail();
96 	}
97 	return config;
98 }
99 
100 static struct card_data *conf_data_by_card(int card, bool msg)
101 {
102 	struct card_data *conf;
103 
104 	for (conf = conf_cards; conf; conf = conf->next) {
105 		if (conf->card == card) {
106 			if (msg)
107 				ksft_print_msg("using hw card config %s for card %d\n",
108 					       conf->filename, card);
109 			return conf;
110 		}
111 	}
112 	return NULL;
113 }
114 
115 static int dump_config_tree(snd_config_t *top)
116 {
117 	snd_output_t *out;
118 	int err;
119 
120 	err = snd_output_stdio_attach(&out, stdout, 0);
121 	if (err < 0)
122 		ksft_exit_fail_msg("stdout attach\n");
123 	if (snd_config_save(top, out))
124 		ksft_exit_fail_msg("config save\n");
125 	snd_output_close(out);
126 }
127 
128 static snd_config_t *load(const char *filename)
129 {
130 	snd_config_t *dst;
131 	snd_input_t *input;
132 	int err;
133 
134 	err = snd_input_stdio_open(&input, filename, "r");
135 	if (err < 0)
136 		ksft_exit_fail_msg("Unable to parse filename %s\n", filename);
137 	err = snd_config_top(&dst);
138 	if (err < 0)
139 		ksft_exit_fail_msg("Out of memory\n");
140 	err = snd_config_load(dst, input);
141 	snd_input_close(input);
142 	if (err < 0)
143 		ksft_exit_fail_msg("Unable to parse filename %s\n", filename);
144 	return dst;
145 }
146 
147 static char *sysfs_get(const char *sysfs_root, const char *id)
148 {
149 	char path[PATH_MAX], link[PATH_MAX + 1];
150 	struct stat sb;
151 	ssize_t len;
152 	char *e;
153 	int fd;
154 
155 	if (id[0] == '/')
156 		id++;
157 	snprintf(path, sizeof(path), "%s/%s", sysfs_root, id);
158 	if (lstat(path, &sb) != 0)
159 		return NULL;
160 	if (S_ISLNK(sb.st_mode)) {
161 		len = readlink(path, link, sizeof(link) - 1);
162 		if (len <= 0) {
163 			ksft_exit_fail_msg("sysfs: cannot read link '%s': %s\n",
164 					   path, strerror(errno));
165 			return NULL;
166 		}
167 		link[len] = '\0';
168 		e = strrchr(link, '/');
169 		if (e)
170 			return strdup(e + 1);
171 		return NULL;
172 	}
173 	if (S_ISDIR(sb.st_mode))
174 		return NULL;
175 	if ((sb.st_mode & S_IRUSR) == 0)
176 		return NULL;
177 
178 	fd = open(path, O_RDONLY);
179 	if (fd < 0) {
180 		if (errno == ENOENT)
181 			return NULL;
182 		ksft_exit_fail_msg("sysfs: open failed for '%s': %s\n",
183 				   path, strerror(errno));
184 	}
185 	len = read(fd, path, sizeof(path)-1);
186 	close(fd);
187 	if (len < 0)
188 		ksft_exit_fail_msg("sysfs: unable to read value '%s': %s\n",
189 				   path, errno);
190 	while (len > 0 && path[len-1] == '\n')
191 		len--;
192 	path[len] = '\0';
193 	e = strdup(path);
194 	if (e == NULL)
195 		ksft_exit_fail_msg("Out of memory\n");
196 	return e;
197 }
198 
199 static bool sysfs_match(const char *sysfs_root, snd_config_t *config)
200 {
201 	snd_config_t *node, *path_config, *regex_config;
202 	snd_config_iterator_t i, next;
203 	const char *path_string, *regex_string, *v;
204 	regex_t re;
205 	regmatch_t match[1];
206 	int iter = 0, ret;
207 
208 	snd_config_for_each(i, next, config) {
209 		node = snd_config_iterator_entry(i);
210 		if (snd_config_search(node, "path", &path_config))
211 			ksft_exit_fail_msg("Missing path field in the sysfs block\n");
212 		if (snd_config_search(node, "regex", &regex_config))
213 			ksft_exit_fail_msg("Missing regex field in the sysfs block\n");
214 		if (snd_config_get_string(path_config, &path_string))
215 			ksft_exit_fail_msg("Path field in the sysfs block is not a string\n");
216 		if (snd_config_get_string(regex_config, &regex_string))
217 			ksft_exit_fail_msg("Regex field in the sysfs block is not a string\n");
218 		iter++;
219 		v = sysfs_get(sysfs_root, path_string);
220 		if (!v)
221 			return false;
222 		if (regcomp(&re, regex_string, REG_EXTENDED))
223 			ksft_exit_fail_msg("Wrong regex '%s'\n", regex_string);
224 		ret = regexec(&re, v, 1, match, 0);
225 		regfree(&re);
226 		if (ret)
227 			return false;
228 	}
229 	return iter > 0;
230 }
231 
232 static bool test_filename1(int card, const char *filename, const char *sysfs_card_root)
233 {
234 	struct card_data *data, *data2;
235 	snd_config_t *config, *sysfs_config, *card_config, *sysfs_card_config, *node;
236 	snd_config_iterator_t i, next;
237 
238 	config = load(filename);
239 	if (snd_config_search(config, "sysfs", &sysfs_config) ||
240 	    snd_config_get_type(sysfs_config) != SND_CONFIG_TYPE_COMPOUND)
241 		ksft_exit_fail_msg("Missing global sysfs block in filename %s\n", filename);
242 	if (snd_config_search(config, "card", &card_config) ||
243 	    snd_config_get_type(card_config) != SND_CONFIG_TYPE_COMPOUND)
244 		ksft_exit_fail_msg("Missing global card block in filename %s\n", filename);
245 	if (!sysfs_match(SYSFS_ROOT, sysfs_config))
246 		return false;
247 	snd_config_for_each(i, next, card_config) {
248 		node = snd_config_iterator_entry(i);
249 		if (snd_config_search(node, "sysfs", &sysfs_card_config) ||
250 		    snd_config_get_type(sysfs_card_config) != SND_CONFIG_TYPE_COMPOUND)
251 			ksft_exit_fail_msg("Missing card sysfs block in filename %s\n", filename);
252 		if (!sysfs_match(sysfs_card_root, sysfs_card_config))
253 			continue;
254 		data = malloc(sizeof(*data));
255 		if (!data)
256 			ksft_exit_fail_msg("Out of memory\n");
257 		data2 = conf_data_by_card(card, false);
258 		if (data2)
259 			ksft_exit_fail_msg("Duplicate card '%s' <-> '%s'\n", filename, data2->filename);
260 		data->card = card;
261 		data->filename = filename;
262 		data->config = node;
263 		data->next = conf_cards;
264 		conf_cards = data;
265 		return true;
266 	}
267 	return false;
268 }
269 
270 static bool test_filename(const char *filename)
271 {
272 	char fn[128];
273 	int card;
274 
275 	for (card = 0; card < 32; card++) {
276 		snprintf(fn, sizeof(fn), "%s/class/sound/card%d", SYSFS_ROOT, card);
277 		if (access(fn, R_OK) == 0 && test_filename1(card, filename, fn))
278 			return true;
279 	}
280 	return false;
281 }
282 
283 static int filename_filter(const struct dirent *dirent)
284 {
285 	size_t flen;
286 
287 	if (dirent == NULL)
288 		return 0;
289 	if (dirent->d_type == DT_DIR)
290 		return 0;
291 	flen = strlen(dirent->d_name);
292 	if (flen <= 5)
293 		return 0;
294 	if (strncmp(&dirent->d_name[flen-5], ".conf", 5) == 0)
295 		return 1;
296 	return 0;
297 }
298 
299 void conf_load(void)
300 {
301 	const char *fn = "conf.d";
302 	struct dirent **namelist;
303 	int n, j;
304 
305 	n = scandir(fn, &namelist, filename_filter, alphasort);
306 	if (n < 0)
307 		ksft_exit_fail_msg("scandir: %s\n", strerror(errno));
308 	for (j = 0; j < n; j++) {
309 		size_t sl = strlen(fn) + strlen(namelist[j]->d_name) + 2;
310 		char *filename = malloc(sl);
311 		if (filename == NULL)
312 			ksft_exit_fail_msg("Out of memory\n");
313 		sprintf(filename, "%s/%s", fn, namelist[j]->d_name);
314 		if (test_filename(filename))
315 			filename = NULL;
316 		free(filename);
317 		free(namelist[j]);
318 	}
319 	free(namelist);
320 }
321 
322 void conf_free(void)
323 {
324 	struct card_data *conf;
325 
326 	while (conf_cards) {
327 		conf = conf_cards;
328 		conf_cards = conf->next;
329 		snd_config_delete(conf->config);
330 	}
331 }
332 
333 snd_config_t *conf_by_card(int card)
334 {
335 	struct card_data *conf;
336 
337 	conf = conf_data_by_card(card, true);
338 	if (conf)
339 		return conf->config;
340 	return NULL;
341 }
342 
343 static int conf_get_by_keys(snd_config_t *root, const char *key1,
344 			    const char *key2, snd_config_t **result)
345 {
346 	int ret;
347 
348 	if (key1) {
349 		ret = snd_config_search(root, key1, &root);
350 		if (ret != -ENOENT && ret < 0)
351 			return ret;
352 	}
353 	if (key2)
354 		ret = snd_config_search(root, key2, &root);
355 	if (ret >= 0)
356 		*result = root;
357 	return ret;
358 }
359 
360 snd_config_t *conf_get_subtree(snd_config_t *root, const char *key1, const char *key2)
361 {
362 	int ret;
363 
364 	if (!root)
365 		return NULL;
366 	ret = conf_get_by_keys(root, key1, key2, &root);
367 	if (ret == -ENOENT)
368 		return NULL;
369 	if (ret < 0)
370 		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
371 	return root;
372 }
373 
374 int conf_get_count(snd_config_t *root, const char *key1, const char *key2)
375 {
376 	snd_config_t *cfg;
377 	snd_config_iterator_t i, next;
378 	int count, ret;
379 
380 	if (!root)
381 		return -1;
382 	ret = conf_get_by_keys(root, key1, key2, &cfg);
383 	if (ret == -ENOENT)
384 		return -1;
385 	if (ret < 0)
386 		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
387 	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND)
388 		ksft_exit_fail_msg("key '%s'.'%s' is not a compound\n", key1, key2);
389 	count = 0;
390 	snd_config_for_each(i, next, cfg)
391 		count++;
392 	return count;
393 }
394 
395 const char *conf_get_string(snd_config_t *root, const char *key1, const char *key2, const char *def)
396 {
397 	snd_config_t *cfg;
398 	const char *s;
399 	int ret;
400 
401 	if (!root)
402 		return def;
403 	ret = conf_get_by_keys(root, key1, key2, &cfg);
404 	if (ret == -ENOENT)
405 		return def;
406 	if (ret < 0)
407 		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
408 	if (snd_config_get_string(cfg, &s))
409 		ksft_exit_fail_msg("key '%s'.'%s' is not a string\n", key1, key2);
410 	return s;
411 }
412 
413 long conf_get_long(snd_config_t *root, const char *key1, const char *key2, long def)
414 {
415 	snd_config_t *cfg;
416 	long l;
417 	int ret;
418 
419 	if (!root)
420 		return def;
421 	ret = conf_get_by_keys(root, key1, key2, &cfg);
422 	if (ret == -ENOENT)
423 		return def;
424 	if (ret < 0)
425 		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
426 	if (snd_config_get_integer(cfg, &l))
427 		ksft_exit_fail_msg("key '%s'.'%s' is not an integer\n", key1, key2);
428 	return l;
429 }
430 
431 int conf_get_bool(snd_config_t *root, const char *key1, const char *key2, int def)
432 {
433 	snd_config_t *cfg;
434 	long l;
435 	int ret;
436 
437 	if (!root)
438 		return def;
439 	ret = conf_get_by_keys(root, key1, key2, &cfg);
440 	if (ret == -ENOENT)
441 		return def;
442 	if (ret < 0)
443 		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
444 	ret = snd_config_get_bool(cfg);
445 	if (ret < 0)
446 		ksft_exit_fail_msg("key '%s'.'%s' is not an bool\n", key1, key2);
447 	return !!ret;
448 }
449