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 snd_config_t *conf_load_from_file(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", ®ex_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, ®ex_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 = conf_load_from_file(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 int ret; 435 436 if (!root) 437 return def; 438 ret = conf_get_by_keys(root, key1, key2, &cfg); 439 if (ret == -ENOENT) 440 return def; 441 if (ret < 0) 442 ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret)); 443 ret = snd_config_get_bool(cfg); 444 if (ret < 0) 445 ksft_exit_fail_msg("key '%s'.'%s' is not an bool\n", key1, key2); 446 return !!ret; 447 } 448 449 void conf_get_string_array(snd_config_t *root, const char *key1, const char *key2, 450 const char **array, int array_size, const char *def) 451 { 452 snd_config_t *cfg; 453 char buf[16]; 454 int ret, index; 455 456 ret = conf_get_by_keys(root, key1, key2, &cfg); 457 if (ret == -ENOENT) 458 cfg = NULL; 459 else if (ret < 0) 460 ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret)); 461 for (index = 0; index < array_size; index++) { 462 if (cfg == NULL) { 463 array[index] = def; 464 } else { 465 sprintf(buf, "%i", index); 466 array[index] = conf_get_string(cfg, buf, NULL, def); 467 } 468 } 469 } 470