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