mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2026-03-30 11:10:19 -04:00
Since glibc-2.43: For ISO C23, the functions bsearch, memchr, strchr, strpbrk, strrchr, strstr, wcschr, wcspbrk, wcsrchr, wcsstr and wmemchr that return pointers into their input arrays now have definitions as macros that return a pointer to a const-qualified type when the input argument is a pointer to a const-qualified type. https://lists.gnu.org/archive/html/info-gnu/2026-01/msg00005.html Signed-off-by: Rudi Heitbaum <rudi@heitbaum.com>
1058 lines
26 KiB
C
1058 lines
26 KiB
C
/*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Support for the verb/device/modifier core logic and API,
|
|
* command line tool and file parser was kindly sponsored by
|
|
* Texas Instruments Inc.
|
|
* Support for multiple active modifiers and devices,
|
|
* transition sequences, multiple client access and user defined use
|
|
* cases was kindly sponsored by Wolfson Microelectronics PLC.
|
|
*
|
|
* Copyright (C) 2019-2025 Red Hat Inc.
|
|
* Authors: Jaroslav Kysela <perex@perex.cz>
|
|
*/
|
|
|
|
#include "ucm_local.h"
|
|
#include <stdbool.h>
|
|
#include <sys/stat.h>
|
|
#include <limits.h>
|
|
#include <regex.h>
|
|
|
|
/* capabilities string like "*a*b*c" will be used to check library extensions */
|
|
/* use Needle like "*a*" or regex expression for a match */
|
|
#define LIB_CAPS_STRING "*" "*"
|
|
|
|
static unsigned char _hex_table[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
|
|
|
static char *rval_lib_caps(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED)
|
|
{
|
|
if (uc_mgr->conf_format < 8)
|
|
return NULL;
|
|
return strdup(LIB_CAPS_STRING);
|
|
}
|
|
|
|
static char *rval_open_name(snd_use_case_mgr_t *uc_mgr)
|
|
{
|
|
const char *name;
|
|
if (uc_mgr->conf_format < 3)
|
|
return NULL;
|
|
name = uc_mgr->card_name;
|
|
if (name) {
|
|
if (strncmp(name, "strict:", 7) == 0)
|
|
name += 7;
|
|
return strdup(name);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static char *rval_conf_libdir(snd_use_case_mgr_t *uc_mgr)
|
|
{
|
|
if (uc_mgr->conf_format < 4)
|
|
return NULL;
|
|
return strdup(snd_config_topdir());
|
|
}
|
|
|
|
static char *rval_conf_topdir(snd_use_case_mgr_t *uc_mgr)
|
|
{
|
|
const char *dir;
|
|
|
|
if (uc_mgr->conf_format < 3)
|
|
return NULL;
|
|
dir = uc_mgr_config_dir(uc_mgr->conf_format);
|
|
if (dir && dir[0])
|
|
return strdup(dir);
|
|
return NULL;
|
|
}
|
|
|
|
static char *rval_conf_dir(snd_use_case_mgr_t *uc_mgr)
|
|
{
|
|
if (uc_mgr->conf_format < 3)
|
|
return NULL;
|
|
if (uc_mgr->conf_dir_name && uc_mgr->conf_dir_name[0])
|
|
return strdup(uc_mgr->conf_dir_name);
|
|
return NULL;
|
|
}
|
|
|
|
static char *rval_conf_name(snd_use_case_mgr_t *uc_mgr)
|
|
{
|
|
if (uc_mgr->conf_file_name && uc_mgr->conf_file_name[0])
|
|
return strdup(uc_mgr->conf_file_name);
|
|
return NULL;
|
|
}
|
|
|
|
static char *get_card_number(struct ctl_list *ctl_list)
|
|
{
|
|
char num[16];
|
|
|
|
if (ctl_list == NULL)
|
|
return strdup("");
|
|
snprintf(num, sizeof(num), "%i", snd_ctl_card_info_get_card(ctl_list->ctl_info));
|
|
return strdup(num);
|
|
}
|
|
|
|
static char *rval_card_number(snd_use_case_mgr_t *uc_mgr)
|
|
{
|
|
if (uc_mgr->conf_format < 3)
|
|
return NULL;
|
|
return get_card_number(uc_mgr_get_master_ctl(uc_mgr));
|
|
}
|
|
|
|
static char *rval_card_id(snd_use_case_mgr_t *uc_mgr)
|
|
{
|
|
struct ctl_list *ctl_list;
|
|
|
|
ctl_list = uc_mgr_get_master_ctl(uc_mgr);
|
|
if (ctl_list == NULL)
|
|
return NULL;
|
|
return strdup(snd_ctl_card_info_get_id(ctl_list->ctl_info));
|
|
}
|
|
|
|
static char *rval_card_driver(snd_use_case_mgr_t *uc_mgr)
|
|
{
|
|
struct ctl_list *ctl_list;
|
|
|
|
ctl_list = uc_mgr_get_master_ctl(uc_mgr);
|
|
if (ctl_list == NULL)
|
|
return NULL;
|
|
return strdup(snd_ctl_card_info_get_driver(ctl_list->ctl_info));
|
|
}
|
|
|
|
static char *rval_card_name(snd_use_case_mgr_t *uc_mgr)
|
|
{
|
|
struct ctl_list *ctl_list;
|
|
|
|
ctl_list = uc_mgr_get_master_ctl(uc_mgr);
|
|
if (ctl_list == NULL)
|
|
return NULL;
|
|
return strdup(snd_ctl_card_info_get_name(ctl_list->ctl_info));
|
|
}
|
|
|
|
static char *rval_card_longname(snd_use_case_mgr_t *uc_mgr)
|
|
{
|
|
struct ctl_list *ctl_list;
|
|
|
|
ctl_list = uc_mgr_get_master_ctl(uc_mgr);
|
|
if (ctl_list == NULL)
|
|
return NULL;
|
|
return strdup(snd_ctl_card_info_get_longname(ctl_list->ctl_info));
|
|
}
|
|
|
|
static char *rval_card_components(snd_use_case_mgr_t *uc_mgr)
|
|
{
|
|
struct ctl_list *ctl_list;
|
|
|
|
ctl_list = uc_mgr_get_master_ctl(uc_mgr);
|
|
if (ctl_list == NULL)
|
|
return NULL;
|
|
return strdup(snd_ctl_card_info_get_components(ctl_list->ctl_info));
|
|
}
|
|
|
|
static struct ctl_list *get_ctl_list_by_name(snd_use_case_mgr_t *uc_mgr, const char *id)
|
|
{
|
|
char *name, *index;
|
|
long idx = 0;
|
|
|
|
name = alloca(strlen(id) + 1);
|
|
strcpy(name, id);
|
|
index = strchr(name, '#');
|
|
if (index) {
|
|
*index = '\0';
|
|
if (safe_strtol(index + 1, &idx))
|
|
return NULL;
|
|
}
|
|
return uc_mgr_get_ctl_by_name(uc_mgr, name, idx);
|
|
}
|
|
|
|
static char *rval_card_number_by_name(snd_use_case_mgr_t *uc_mgr, const char *id)
|
|
{
|
|
if (uc_mgr->conf_format < 3) {
|
|
snd_error(UCM, "CardNumberByName substitution is supported in v3+ syntax");
|
|
return NULL;
|
|
}
|
|
|
|
snd_error(UCM, "${CardNumberByName} substitution is obsolete - use ${find-card}!");
|
|
|
|
return get_card_number(get_ctl_list_by_name(uc_mgr, id));
|
|
}
|
|
|
|
static char *rval_card_id_by_name(snd_use_case_mgr_t *uc_mgr, const char *id)
|
|
{
|
|
struct ctl_list *ctl_list;
|
|
|
|
if (uc_mgr->conf_format < 3) {
|
|
snd_error(UCM, "CardIdByName substitution is supported in v3+ syntax");
|
|
return NULL;
|
|
}
|
|
|
|
snd_error(UCM, "${CardIdByName} substitution is obsolete - use ${find-card}!");
|
|
|
|
ctl_list = get_ctl_list_by_name(uc_mgr, id);
|
|
if (ctl_list == NULL)
|
|
return NULL;
|
|
return strdup(snd_ctl_card_info_get_id(ctl_list->ctl_info));
|
|
}
|
|
|
|
#ifndef DOC_HIDDEN
|
|
typedef struct lookup_iterate *(*lookup_iter_fcn_t)
|
|
(snd_use_case_mgr_t *uc_mgr, struct lookup_iterate *iter);
|
|
typedef const char *(*lookup_fcn_t)(void *);
|
|
|
|
struct lookup_fcn {
|
|
char *name;
|
|
const char *(*fcn)(void *opaque);
|
|
};
|
|
|
|
struct lookup_iterate {
|
|
int (*init)(snd_use_case_mgr_t *uc_mgr, struct lookup_iterate *iter,
|
|
snd_config_t *config);
|
|
void (*done)(struct lookup_iterate *iter);
|
|
lookup_iter_fcn_t first;
|
|
lookup_iter_fcn_t next;
|
|
char *(*retfcn)(struct lookup_iterate *iter, snd_config_t *config);
|
|
struct lookup_fcn *fcns;
|
|
lookup_fcn_t fcn;
|
|
struct ctl_list *ctl_list;
|
|
void *info;
|
|
};
|
|
#endif /* DOC_HIDDEN */
|
|
|
|
static char *rval_lookup_main(snd_use_case_mgr_t *uc_mgr,
|
|
const char *query,
|
|
struct lookup_iterate *iter)
|
|
{
|
|
snd_config_t *config, *d;
|
|
struct lookup_fcn *fcn;
|
|
struct lookup_iterate *curr;
|
|
const char *s;
|
|
char *result;
|
|
regmatch_t match[1];
|
|
regex_t re;
|
|
int err;
|
|
|
|
if (uc_mgr->conf_format < 4) {
|
|
snd_error(UCM, "Lookups are supported in v4+ syntax");
|
|
return NULL;
|
|
}
|
|
|
|
err = snd_config_load_string(&config, query, 0);
|
|
if (err < 0) {
|
|
snd_error(UCM, "The lookup arguments '%s' are invalid", query);
|
|
return NULL;
|
|
}
|
|
if (iter->init && iter->init(uc_mgr, iter, config))
|
|
goto null;
|
|
if (snd_config_search(config, "field", &d)) {
|
|
snd_error(UCM, "Lookups require field!");
|
|
goto null;
|
|
}
|
|
if (snd_config_get_string(d, &s))
|
|
goto null;
|
|
for (fcn = iter->fcns ; fcn; fcn++) {
|
|
if (strcasecmp(fcn->name, s) == 0) {
|
|
iter->fcn = fcn->fcn;
|
|
break;
|
|
}
|
|
}
|
|
if (iter->fcn == NULL) {
|
|
snd_error(UCM, "Unknown field value '%s'", s);
|
|
goto null;
|
|
}
|
|
if (snd_config_search(config, "regex", &d)) {
|
|
snd_error(UCM, "Lookups require regex!");
|
|
goto null;
|
|
}
|
|
if (snd_config_get_string(d, &s))
|
|
goto null;
|
|
err = regcomp(&re, s, REG_EXTENDED | REG_ICASE);
|
|
if (err) {
|
|
snd_error(UCM, "Regex '%s' compilation failed (code %d)", s, err);
|
|
goto null;
|
|
}
|
|
|
|
result = NULL;
|
|
for (curr = iter->first(uc_mgr, iter); curr; curr = iter->next(uc_mgr, iter)) {
|
|
s = curr->fcn(iter->info);
|
|
if (s == NULL)
|
|
continue;
|
|
if (regexec(&re, s, ARRAY_SIZE(match), match, 0) == 0) {
|
|
result = curr->retfcn(iter, config);
|
|
break;
|
|
}
|
|
}
|
|
regfree(&re);
|
|
fin:
|
|
snd_config_delete(config);
|
|
if (iter->done)
|
|
iter->done(iter);
|
|
return result;
|
|
null:
|
|
result = NULL;
|
|
goto fin;
|
|
}
|
|
|
|
static struct lookup_iterate *rval_card_lookup1(snd_use_case_mgr_t *uc_mgr,
|
|
struct lookup_iterate *iter,
|
|
int card)
|
|
{
|
|
if (snd_card_next(&card) < 0 || card < 0)
|
|
return NULL;
|
|
iter->ctl_list = uc_mgr_get_ctl_by_card(uc_mgr, card);
|
|
if (iter->ctl_list == NULL)
|
|
return NULL;
|
|
iter->info = iter->ctl_list->ctl_info;
|
|
return iter;
|
|
}
|
|
|
|
static struct lookup_iterate *rval_card_lookup_first(snd_use_case_mgr_t *uc_mgr,
|
|
struct lookup_iterate *iter)
|
|
{
|
|
return rval_card_lookup1(uc_mgr, iter, -1);
|
|
}
|
|
|
|
static struct lookup_iterate *rval_card_lookup_next(snd_use_case_mgr_t *uc_mgr,
|
|
struct lookup_iterate *iter)
|
|
{
|
|
return rval_card_lookup1(uc_mgr, iter, snd_ctl_card_info_get_card(iter->info));
|
|
}
|
|
|
|
static char *rval_card_lookup_return(struct lookup_iterate *iter, snd_config_t *config)
|
|
{
|
|
snd_config_t *d;
|
|
const char *s;
|
|
|
|
if (snd_config_search(config, "return", &d))
|
|
return strdup(snd_ctl_card_info_get_id(iter->info));
|
|
else if (snd_config_get_string(d, &s))
|
|
return NULL;
|
|
else if (strcasecmp(s, "id") == 0)
|
|
return strdup(snd_ctl_card_info_get_id(iter->info));
|
|
else if (strcasecmp(s, "number") == 0) {
|
|
char num[16];
|
|
snprintf(num, sizeof(num), "%d", snd_ctl_card_info_get_card(iter->info));
|
|
return strdup(num);
|
|
} else {
|
|
snd_error(UCM, "Unknown return type '%s'", s);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static char *rval_card_lookup(snd_use_case_mgr_t *uc_mgr, const char *query)
|
|
{
|
|
static struct lookup_fcn fcns[] = {
|
|
{ .name = "id", (lookup_fcn_t)snd_ctl_card_info_get_id },
|
|
{ .name = "driver", (lookup_fcn_t)snd_ctl_card_info_get_driver },
|
|
{ .name = "name", (lookup_fcn_t)snd_ctl_card_info_get_name },
|
|
{ .name = "longname", (lookup_fcn_t)snd_ctl_card_info_get_longname },
|
|
{ .name = "mixername", (lookup_fcn_t)snd_ctl_card_info_get_mixername },
|
|
{ .name = "components", (lookup_fcn_t)snd_ctl_card_info_get_components },
|
|
{ 0 },
|
|
};
|
|
struct lookup_iterate iter = {
|
|
.first = rval_card_lookup_first,
|
|
.next = rval_card_lookup_next,
|
|
.retfcn = rval_card_lookup_return,
|
|
.fcns = fcns,
|
|
};
|
|
return rval_lookup_main(uc_mgr, query, &iter);
|
|
}
|
|
|
|
static struct lookup_iterate *rval_pcm_lookup1(struct lookup_iterate *iter,
|
|
int device)
|
|
{
|
|
snd_pcm_info_t *pcminfo;
|
|
snd_ctl_t *ctl = iter->ctl_list->ctl;
|
|
int err;
|
|
|
|
next:
|
|
if (snd_ctl_pcm_next_device(ctl, &device) < 0 || device < 0)
|
|
return NULL;
|
|
pcminfo = iter->info;
|
|
snd_pcm_info_set_device(pcminfo, device);
|
|
err = snd_ctl_pcm_info(ctl, pcminfo);
|
|
if (err < 0) {
|
|
if (err == -ENOENT)
|
|
goto next;
|
|
snd_error(UCM, "Unable to obtain PCM info (device %d)", device);
|
|
return NULL;
|
|
}
|
|
return iter;
|
|
}
|
|
|
|
static struct lookup_iterate *rval_pcm_lookup_first(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
|
|
struct lookup_iterate *iter)
|
|
{
|
|
return rval_pcm_lookup1(iter, -1);
|
|
}
|
|
|
|
static struct lookup_iterate *rval_pcm_lookup_next(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
|
|
struct lookup_iterate *iter)
|
|
{
|
|
return rval_pcm_lookup1(iter, snd_pcm_info_get_device(iter->info));
|
|
}
|
|
|
|
static char *rval_pcm_lookup_return(struct lookup_iterate *iter,
|
|
snd_config_t *config ATTRIBUTE_UNUSED)
|
|
{
|
|
char num[16];
|
|
snprintf(num, sizeof(num), "%d", snd_pcm_info_get_device(iter->info));
|
|
return strdup(num);
|
|
}
|
|
|
|
static int rval_pcm_lookup_init(struct lookup_iterate *iter,
|
|
snd_config_t *config)
|
|
{
|
|
static struct lookup_fcn pcm_fcns[] = {
|
|
{ .name = "id", (lookup_fcn_t)snd_pcm_info_get_id },
|
|
{ .name = "name", (lookup_fcn_t)snd_pcm_info_get_name },
|
|
{ .name = "subname", (lookup_fcn_t)snd_pcm_info_get_subdevice_name },
|
|
{ 0 },
|
|
};
|
|
snd_config_t *d;
|
|
const char *s;
|
|
snd_pcm_info_t *pcminfo;
|
|
snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
|
|
|
|
if (snd_config_search(config, "stream", &d) == 0 &&
|
|
snd_config_get_string(d, &s) == 0) {
|
|
if (strcasecmp(s, "playback") == 0)
|
|
stream = SND_PCM_STREAM_PLAYBACK;
|
|
else if (strcasecmp(s, "capture") == 0)
|
|
stream = SND_PCM_STREAM_CAPTURE;
|
|
else {
|
|
snd_error(UCM, "Unknown stream type '%s'", s);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
if (snd_pcm_info_malloc(&pcminfo))
|
|
return -ENOMEM;
|
|
snd_pcm_info_set_device(pcminfo, 0);
|
|
snd_pcm_info_set_subdevice(pcminfo, 0);
|
|
snd_pcm_info_set_stream(pcminfo, stream);
|
|
iter->first = rval_pcm_lookup_first;
|
|
iter->next = rval_pcm_lookup_next;
|
|
iter->retfcn = rval_pcm_lookup_return;
|
|
iter->fcns = pcm_fcns;
|
|
iter->info = pcminfo;
|
|
return 0;
|
|
}
|
|
|
|
static int rval_device_lookup_init(snd_use_case_mgr_t *uc_mgr,
|
|
struct lookup_iterate *iter,
|
|
snd_config_t *config)
|
|
{
|
|
static struct {
|
|
const char *name;
|
|
int (*init)(struct lookup_iterate *iter, snd_config_t *config);
|
|
} *t, types[] = {
|
|
{ .name = "pcm", .init = rval_pcm_lookup_init },
|
|
{ 0 }
|
|
};
|
|
snd_config_t *d;
|
|
const char *s;
|
|
int err;
|
|
|
|
if (snd_config_search(config, "ctl", &d) || snd_config_get_string(d, &s)) {
|
|
iter->ctl_list = uc_mgr_get_master_ctl(uc_mgr);
|
|
if (iter->ctl_list == NULL) {
|
|
snd_error(UCM, "Control device is not defined!");
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
err = uc_mgr_open_ctl(uc_mgr, &iter->ctl_list, s, 1);
|
|
if (err < 0) {
|
|
snd_error(UCM, "Control device '%s' not found", s);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
if (snd_config_search(config, "type", &d) || snd_config_get_string(d, &s)) {
|
|
snd_error(UCM, "Missing device type!");
|
|
return -EINVAL;
|
|
}
|
|
for (t = types; t->name; t++)
|
|
if (strcasecmp(t->name, s) == 0)
|
|
return t->init(iter, config);
|
|
snd_error(UCM, "Device type '%s' is invalid", s);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void rval_device_lookup_done(struct lookup_iterate *iter)
|
|
{
|
|
free(iter->info);
|
|
}
|
|
|
|
static char *rval_device_lookup(snd_use_case_mgr_t *uc_mgr, const char *query)
|
|
{
|
|
struct lookup_iterate iter = {
|
|
.init = rval_device_lookup_init,
|
|
.done = rval_device_lookup_done,
|
|
};
|
|
return rval_lookup_main(uc_mgr, query, &iter);
|
|
}
|
|
|
|
static char *rval_env(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, const char *id)
|
|
{
|
|
char *e;
|
|
|
|
if (*id == '-') {
|
|
e = getenv(id + 1);
|
|
if (e == NULL)
|
|
e = "";
|
|
} else {
|
|
e = getenv(id);
|
|
}
|
|
if (e)
|
|
return strdup(e);
|
|
return NULL;
|
|
}
|
|
|
|
#define RANGE_TYPE_ASCII 0
|
|
#define RANGE_TYPE_HEX 1
|
|
|
|
static int parse_position(snd_config_t *config, const char *name, ssize_t *pos, bool optional)
|
|
{
|
|
snd_config_t *d;
|
|
const char *s;
|
|
long v;
|
|
|
|
if (snd_config_search(config, name, &d)) {
|
|
if (optional) {
|
|
*pos = -1;
|
|
return 0;
|
|
}
|
|
snd_error(UCM, "Unable to find field '%s'", name);
|
|
return -1;
|
|
}
|
|
if (!snd_config_get_integer(d, &v))
|
|
goto fin;
|
|
if (snd_config_get_string(d, &s))
|
|
return -1;
|
|
if (safe_strtol(s, &v)) {
|
|
snd_error(UCM, "Unable to parse position '%s'", s);
|
|
return -1;
|
|
}
|
|
fin:
|
|
*pos = v;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_range(const char *cfg, int *type, ssize_t *pos, ssize_t *size)
|
|
{
|
|
snd_config_t *config, *d;
|
|
int err, retval = 0;
|
|
const char *s;
|
|
|
|
err = snd_config_load_string(&config, cfg, 0);
|
|
if (err < 0) {
|
|
snd_error(UCM, "The range arguments '%s' are invalid", cfg);
|
|
return -1;
|
|
}
|
|
if (snd_config_search(config, "type", &d)) {
|
|
*type = RANGE_TYPE_ASCII;
|
|
} else {
|
|
if (snd_config_get_string(d, &s))
|
|
goto null;
|
|
if (strcasecmp(s, "ascii") == 0) {
|
|
*type = RANGE_TYPE_ASCII;
|
|
} else if (strcasecmp(s, "hex") == 0) {
|
|
*type = RANGE_TYPE_HEX;
|
|
} else {
|
|
snd_error(UCM, "Unknown range type '%s'", s);
|
|
}
|
|
}
|
|
*pos = 0;
|
|
*size = -1;
|
|
if (parse_position(config, "pos", pos, false) ||
|
|
parse_position(config, "size", size, true)) {
|
|
retval = -1;
|
|
goto null;
|
|
}
|
|
|
|
if (*size <= 0)
|
|
*size = 1;
|
|
if (*pos < 0) {
|
|
snd_error(UCM, "Invalid start position");
|
|
retval = -1;
|
|
goto null;
|
|
}
|
|
|
|
null:
|
|
snd_config_delete(config);
|
|
return retval;
|
|
}
|
|
|
|
static char *rval_sysfs_main(snd_use_case_mgr_t *uc_mgr, const char *top_path, const char *id)
|
|
{
|
|
char path[PATH_MAX], link[PATH_MAX + 1];
|
|
struct stat64 sb;
|
|
ssize_t len, range_start = -1, range_size = -1;
|
|
const char *e, *s;
|
|
int fd, type = RANGE_TYPE_ASCII;
|
|
|
|
e = uc_mgr_sysfs_root();
|
|
if (e == NULL)
|
|
return NULL;
|
|
if (id[0] == '[') {
|
|
if (uc_mgr->conf_format < 8) {
|
|
snd_error(UCM, "Sysfs ranges are supported in v8+ syntax");
|
|
return NULL;
|
|
}
|
|
s = strchr(id, ']');
|
|
if (s == NULL)
|
|
return NULL;
|
|
len = s - id - 1;
|
|
if ((size_t)len > sizeof(link) - 1)
|
|
return NULL;
|
|
strncpy(link, id + 1, len);
|
|
link[len] = '\0';
|
|
if (parse_range(link, &type, &range_start, &range_size)) {
|
|
snd_error(UCM, "sysfs: cannot parse hex range '%s'", link);
|
|
return NULL;
|
|
}
|
|
id = s + 1;
|
|
}
|
|
if (id[0] == '/')
|
|
id++;
|
|
if (top_path)
|
|
snprintf(path, sizeof(path), "%s/%s/%s", e, top_path, id);
|
|
else
|
|
snprintf(path, sizeof(path), "%s/%s", e, id);
|
|
if (lstat64(path, &sb) != 0)
|
|
return NULL;
|
|
if (S_ISLNK(sb.st_mode)) {
|
|
len = readlink(path, link, sizeof(link) - 1);
|
|
if (len <= 0) {
|
|
snd_error(UCM, "sysfs: cannot read link '%s' (%d)", path, errno);
|
|
return NULL;
|
|
}
|
|
link[len] = '\0';
|
|
e = strrchr(link, '/');
|
|
if (e)
|
|
return strdup(e + 1);
|
|
return NULL;
|
|
}
|
|
if (S_ISDIR(sb.st_mode))
|
|
return NULL;
|
|
if ((sb.st_mode & S_IRUSR) == 0)
|
|
return NULL;
|
|
|
|
fd = open(path, O_RDONLY);
|
|
if (fd < 0) {
|
|
snd_error(UCM, "sysfs open failed for '%s' (%d)", path, errno);
|
|
return NULL;
|
|
}
|
|
len = sizeof(path) - 1;
|
|
if (range_start > 0 && lseek(fd, range_start, SEEK_SET) != range_start) {
|
|
snd_error(UCM, "sysfs seek failed (%d)", errno);
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
if (range_size > 0) {
|
|
if (range_size > len) {
|
|
snd_error(UCM, "sysfs EOB for '%s'", path);
|
|
close(fd);
|
|
return NULL;
|
|
} else {
|
|
len = range_size;
|
|
}
|
|
}
|
|
len = read(fd, path, len);
|
|
close(fd);
|
|
if (len < 0) {
|
|
snd_error(UCM, "sysfs unable to read value '%s' (%d)", path, errno);
|
|
return NULL;
|
|
}
|
|
if (type == RANGE_TYPE_HEX && range_start >= 0) {
|
|
char *m = malloc(len * 2 + 1);
|
|
ssize_t idx;
|
|
if (m == NULL)
|
|
return NULL;
|
|
for (idx = 0; idx < len; idx++) {
|
|
m[(idx * 2) + 0] = _hex_table[((unsigned char)path[idx]) >> 4];
|
|
m[(idx * 2) + 1] = _hex_table[((unsigned char)path[idx]) & 0x0f];
|
|
}
|
|
m[len * 2] = '\0';
|
|
return m;
|
|
}
|
|
while (len > 0 && path[len-1] == '\n')
|
|
len--;
|
|
path[len] = '\0';
|
|
return strdup(path);
|
|
}
|
|
|
|
static char *rval_sysfs(snd_use_case_mgr_t *uc_mgr, const char *id)
|
|
{
|
|
return rval_sysfs_main(uc_mgr, NULL, id);
|
|
}
|
|
|
|
static char *rval_sysfs_card(snd_use_case_mgr_t *uc_mgr, const char *id)
|
|
{
|
|
char top_path[32], *s;
|
|
|
|
if (uc_mgr->conf_format < 8) {
|
|
snd_error(UCM, "sys-card is supported in v8+ syntax");
|
|
return NULL;
|
|
}
|
|
s = get_card_number(uc_mgr_get_master_ctl(uc_mgr));
|
|
snprintf(top_path, sizeof(top_path), "class/sound/card%s", s);
|
|
free(s);
|
|
return rval_sysfs_main(uc_mgr, top_path, id);
|
|
}
|
|
|
|
static char *rval_var(snd_use_case_mgr_t *uc_mgr, const char *id)
|
|
{
|
|
const char *v;
|
|
bool ignore_not_found = false;
|
|
|
|
if (uc_mgr->conf_format < 3) {
|
|
snd_error(UCM, "variable substitution is supported in v3+ syntax");
|
|
return NULL;
|
|
}
|
|
|
|
if (id[0] == '-') {
|
|
ignore_not_found = true;
|
|
id++;
|
|
} else if (id[0] == '@') {
|
|
ignore_not_found = true;
|
|
}
|
|
v = uc_mgr_get_variable(uc_mgr, id);
|
|
if (v == NULL && ignore_not_found)
|
|
v = "";
|
|
if (v)
|
|
return strdup(v);
|
|
return NULL;
|
|
}
|
|
|
|
int _snd_eval_string(snd_config_t **dst, const char *s,
|
|
snd_config_expand_fcn_t fcn, void *private_data);
|
|
|
|
static int rval_eval_var_cb(snd_config_t **dst, const char *s, void *private_data)
|
|
{
|
|
snd_use_case_mgr_t *uc_mgr = private_data;
|
|
const char *v;
|
|
|
|
v = uc_mgr_get_variable(uc_mgr, s);
|
|
if (v == NULL)
|
|
return -ENOENT;
|
|
return snd_config_imake_string(dst, NULL, v);
|
|
}
|
|
|
|
static char *rval_eval(snd_use_case_mgr_t *uc_mgr, const char *e)
|
|
{
|
|
snd_config_t *dst;
|
|
char *r;
|
|
int err;
|
|
|
|
if (uc_mgr->conf_format < 5) {
|
|
snd_error(UCM, "variable evaluation is supported in v5+ syntax");
|
|
return NULL;
|
|
}
|
|
err = _snd_eval_string(&dst, e, rval_eval_var_cb, uc_mgr);
|
|
if (err < 0) {
|
|
snd_error(UCM, "unable to evaluate '%s'", e);
|
|
return NULL;
|
|
}
|
|
err = snd_config_get_ascii(dst, &r);
|
|
snd_config_delete(dst);
|
|
if (err < 0)
|
|
return NULL;
|
|
return r;
|
|
}
|
|
|
|
static int rval_evali(snd_use_case_mgr_t *uc_mgr, snd_config_t *node, const char *e)
|
|
{
|
|
snd_config_t *dst;
|
|
const char *id;
|
|
char *s;
|
|
size_t l;
|
|
int err;
|
|
|
|
if (uc_mgr->conf_format < 6) {
|
|
snd_error(UCM, "variable evaluation is supported in v6+ syntax");
|
|
return -EINVAL;
|
|
}
|
|
err = snd_config_get_id(node, &id);
|
|
if (err < 0)
|
|
return err;
|
|
l = strlen(e);
|
|
if (e[l-1] != '}')
|
|
return -EINVAL;
|
|
s = malloc(l + 1);
|
|
if (s == NULL)
|
|
return -ENOMEM;
|
|
strcpy(s, e);
|
|
s[l-1] = '\0';
|
|
err = _snd_eval_string(&dst, s + 8, rval_eval_var_cb, uc_mgr);
|
|
free(s);
|
|
if (err < 0) {
|
|
snd_error(UCM, "unable to evaluate '%s'", e);
|
|
return err;
|
|
}
|
|
err = snd_config_set_id(dst, id);
|
|
if (err < 0)
|
|
return err;
|
|
return snd_config_substitute(node, dst);
|
|
}
|
|
|
|
#define MATCH_VARIABLE(name, id, fcn, empty_ok) \
|
|
if (strncmp((name), (id), sizeof(id) - 1) == 0) { \
|
|
rval = fcn(uc_mgr); \
|
|
idsize = sizeof(id) - 1; \
|
|
allow_empty = (empty_ok); \
|
|
goto __rval; \
|
|
}
|
|
|
|
#define MATCH_VARIABLE2(name, id, fcn, empty_ok) \
|
|
if (strncmp((name), (id), sizeof(id) - 1) == 0) { \
|
|
idsize = sizeof(id) - 1; \
|
|
allow_empty = (empty_ok); \
|
|
fcn2 = (fcn); \
|
|
goto __match2; \
|
|
}
|
|
|
|
/*
|
|
* skip escaped } character (simple version)
|
|
*/
|
|
static inline const char *strchr_with_escape(const char *str, char c)
|
|
{
|
|
const char *s;
|
|
|
|
while (1) {
|
|
s = strchr(str, c);
|
|
if (s && s != str) {
|
|
if (*(s - 1) == '\\') {
|
|
str = s + 1;
|
|
continue;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* remove escaped } character (simple version)
|
|
*/
|
|
static inline void strncpy_with_escape(char *dst, const char *src, size_t len)
|
|
{
|
|
char c;
|
|
|
|
c = *src++;
|
|
while (c != '\0' && len > 0) {
|
|
if (c == '\\' && *src == '}') {
|
|
c = *src++;
|
|
len--;
|
|
}
|
|
*dst++ = c;
|
|
len--;
|
|
c = *src++;
|
|
}
|
|
*dst = '\0';
|
|
}
|
|
|
|
int uc_mgr_get_substituted_value(snd_use_case_mgr_t *uc_mgr,
|
|
char **_rvalue,
|
|
const char *value)
|
|
{
|
|
size_t size, nsize, idsize, rvalsize, dpos = 0;
|
|
const char *tmp;
|
|
char *r, *nr, *rval, v2[128];
|
|
bool ignore_error, allow_empty;
|
|
char *(*fcn2)(snd_use_case_mgr_t *, const char *id);
|
|
int err;
|
|
|
|
if (value == NULL)
|
|
return -ENOENT;
|
|
|
|
size = strlen(value) + 1;
|
|
r = malloc(size);
|
|
if (r == NULL)
|
|
return -ENOMEM;
|
|
|
|
while (*value) {
|
|
if (*value != '$') {
|
|
__std:
|
|
r[dpos++] = *value;
|
|
value++;
|
|
continue;
|
|
}
|
|
ignore_error = false;
|
|
if (value[1] == '$' && value[2] == '{' && uc_mgr->conf_format >= 3) {
|
|
value++;
|
|
ignore_error = true;
|
|
} else if (value[1] != '{') {
|
|
goto __std;
|
|
}
|
|
fcn2 = NULL;
|
|
MATCH_VARIABLE(value, "${LibCaps}", rval_lib_caps, false);
|
|
MATCH_VARIABLE(value, "${OpenName}", rval_open_name, false);
|
|
MATCH_VARIABLE(value, "${ConfLibDir}", rval_conf_libdir, false);
|
|
MATCH_VARIABLE(value, "${ConfTopDir}", rval_conf_topdir, false);
|
|
MATCH_VARIABLE(value, "${ConfDir}", rval_conf_dir, false);
|
|
MATCH_VARIABLE(value, "${ConfName}", rval_conf_name, false);
|
|
MATCH_VARIABLE(value, "${CardNumber}", rval_card_number, true);
|
|
MATCH_VARIABLE(value, "${CardId}", rval_card_id, false);
|
|
MATCH_VARIABLE(value, "${CardDriver}", rval_card_driver, false);
|
|
MATCH_VARIABLE(value, "${CardName}", rval_card_name, false);
|
|
MATCH_VARIABLE(value, "${CardLongName}", rval_card_longname, false);
|
|
MATCH_VARIABLE(value, "${CardComponents}", rval_card_components, true);
|
|
MATCH_VARIABLE2(value, "${env:", rval_env, false);
|
|
MATCH_VARIABLE2(value, "${sys:", rval_sysfs, false);
|
|
MATCH_VARIABLE2(value, "${sys-card:", rval_sysfs_card, false);
|
|
MATCH_VARIABLE2(value, "${var:", rval_var, true);
|
|
MATCH_VARIABLE2(value, "${eval:", rval_eval, false);
|
|
MATCH_VARIABLE2(value, "${find-card:", rval_card_lookup, false);
|
|
MATCH_VARIABLE2(value, "${find-device:", rval_device_lookup, false);
|
|
MATCH_VARIABLE2(value, "${CardNumberByName:", rval_card_number_by_name, false);
|
|
MATCH_VARIABLE2(value, "${CardIdByName:", rval_card_id_by_name, false);
|
|
__merr:
|
|
err = -EINVAL;
|
|
tmp = strchr(value, '}');
|
|
if (tmp) {
|
|
strncpy(r, value, tmp + 1 - value);
|
|
r[tmp + 1 - value] = '\0';
|
|
snd_error(UCM, "variable '%s' is not known!", r);
|
|
} else {
|
|
snd_error(UCM, "variable reference '%s' is not complete", value);
|
|
}
|
|
goto __error;
|
|
__match2:
|
|
tmp = strchr_with_escape(value + idsize, '}');
|
|
if (tmp) {
|
|
rvalsize = tmp - (value + idsize);
|
|
if (rvalsize >= sizeof(v2)) {
|
|
err = -ENOMEM;
|
|
goto __error;
|
|
}
|
|
strncpy_with_escape(v2, value + idsize, rvalsize);
|
|
idsize += rvalsize + 1;
|
|
if (*v2 == '$' && uc_mgr->conf_format >= 3) {
|
|
if (strncmp(value, "${eval:", 7) == 0)
|
|
goto __direct_fcn2;
|
|
tmp = uc_mgr_get_variable(uc_mgr, v2 + 1);
|
|
if (tmp == NULL) {
|
|
snd_error(UCM, "define '%s' is not reachable in this context!", v2 + 1);
|
|
rval = NULL;
|
|
} else {
|
|
rval = fcn2(uc_mgr, tmp);
|
|
}
|
|
} else {
|
|
__direct_fcn2:
|
|
rval = fcn2(uc_mgr, v2);
|
|
}
|
|
goto __rval;
|
|
}
|
|
goto __merr;
|
|
__rval:
|
|
if (rval == NULL || (!allow_empty && rval[0] == '\0')) {
|
|
free(rval);
|
|
if (ignore_error) {
|
|
value += idsize;
|
|
continue;
|
|
}
|
|
strncpy(r, value, idsize);
|
|
r[idsize] = '\0';
|
|
snd_error(UCM, "variable '%s' is %s in this context!", r,
|
|
rval ? "empty" : "not defined");
|
|
|
|
err = -EINVAL;
|
|
goto __error;
|
|
}
|
|
value += idsize;
|
|
rvalsize = strlen(rval);
|
|
nsize = size + rvalsize - idsize;
|
|
if (nsize > size) {
|
|
nr = realloc(r, nsize);
|
|
if (nr == NULL) {
|
|
free(rval);
|
|
err = -ENOMEM;
|
|
goto __error;
|
|
}
|
|
size = nsize;
|
|
r = nr;
|
|
}
|
|
strcpy(r + dpos, rval);
|
|
dpos += rvalsize;
|
|
free(rval);
|
|
}
|
|
r[dpos] = '\0';
|
|
|
|
*_rvalue = r;
|
|
return 0;
|
|
|
|
__error:
|
|
free(r);
|
|
return err;
|
|
}
|
|
|
|
static inline int uc_mgr_substitute_check(const char *s)
|
|
{
|
|
return s && strstr(s, "${") != NULL;
|
|
}
|
|
|
|
int uc_mgr_substitute_tree(snd_use_case_mgr_t *uc_mgr, snd_config_t *node)
|
|
{
|
|
snd_config_iterator_t i, next;
|
|
snd_config_t *n;
|
|
const char *id, *s2;
|
|
char *s;
|
|
int err;
|
|
|
|
err = snd_config_get_id(node, &id);
|
|
if (err < 0)
|
|
return err;
|
|
if (uc_mgr_substitute_check(id)) {
|
|
err = uc_mgr_get_substituted_value(uc_mgr, &s, id);
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_config_set_id(node, s);
|
|
if (err < 0) {
|
|
snd_error(UCM, "unable to set substituted id '%s' (old id '%s')", s, id);
|
|
free(s);
|
|
return err;
|
|
}
|
|
free(s);
|
|
}
|
|
if (snd_config_get_type(node) != SND_CONFIG_TYPE_COMPOUND) {
|
|
if (snd_config_get_type(node) == SND_CONFIG_TYPE_STRING) {
|
|
err = snd_config_get_string(node, &s2);
|
|
if (err < 0)
|
|
return err;
|
|
if (!uc_mgr_substitute_check(s2))
|
|
return 0;
|
|
if (strncmp(s2, "${evali:", 8) == 0)
|
|
return rval_evali(uc_mgr, node, s2);
|
|
err = uc_mgr_get_substituted_value(uc_mgr, &s, s2);
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_config_set_string(node, s);
|
|
free(s);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
/* exception - macros are evaluated when instantied */
|
|
err = snd_config_get_id(node, &id);
|
|
if (err < 0)
|
|
return err;
|
|
if (id && strcmp(id, "DefineMacro") == 0)
|
|
return 0;
|
|
snd_config_for_each(i, next, node) {
|
|
n = snd_config_iterator_entry(i);
|
|
err = uc_mgr_substitute_tree(uc_mgr, n);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|