Compare commits

..

25 commits

Author SHA1 Message Date
Huang Yunxuan
62c24074ae ucm: fix optional include
Some checks are pending
Build alsa-lib / fedora_latest_build (push) Waiting to run
Build alsa-lib / ubuntu_last_build (push) Waiting to run
The path is not an absolute path and can't be used with `access()`.
Let's call `uc_mgr_config_load_file()` directly and mask acceptable
error numbers.

Closes: https://github.com/alsa-project/alsa-lib/pull/499
Signed-off-by: Huang Yunxuan <hyx0329@outlook.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-03-18 15:42:20 +01:00
Jaroslav Kysela
d8ca4a7cc4 ucm: add show_err parameter to uc_mgr_get_variable
Add bool show_err parameter to uc_mgr_get_variable() to control whether
an error message is displayed when a variable is not defined. This
centralizes error reporting and eliminates redundant error messages
in callers.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-18 13:30:29 +01:00
Jaroslav Kysela
a74521f371 ucm: find-card,find-device - add UCM variable support for arguments (Syntax 9)
Add variable support for all lookup arguments in find-card and find-device
substitutions. Variables are identified by $ prefix and only enabled for
Syntax 9+ to maintain backward compatibility.

Modified arguments with variable support:
- find-card: field, regex
- find-device: type, field, stream, regex

Example usage:
  ${find-card:field=$FieldName,regex=$Pattern,return=number}
  ${find-device:type=$DevType,stream=$StreamType,field=$FieldName,regex=$Pattern}

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-18 13:29:33 +01:00
Jaroslav Kysela
c5d903b0b4 ucm: add info-card substitution (Syntax 9)
Implement ${info-card:} substitution to retrieve specific fields from
a card's information structure by card number or ID. Unlike ${find-card:}
which searches through cards using regex, ${info-card:} directly queries
a specific card.

Arguments:
  card=<STR>   card number or card ID (string identifier)
  field=<STR>  number, id, driver, name, longname, mixername, components

Allow card and field parameters to reference UCM variables by prefixing
with $ character. When the first character is $, the value is resolved
using uc_mgr_get_variable().

Example usage:
  ${info-card:card=$MyCard,field=$MyField}

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-18 12:21:52 +01:00
Jaroslav Kysela
1823b4cd4b control: remap - fix memory leak in remap_load_list()
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-12 11:15:55 +01:00
Jaroslav Kysela
22225a4ec6 ucm: evaluate Repeat block before If block (Syntax 9)
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-06 19:33:08 +01:00
Jaroslav Kysela
c41f795f5c ucm: allow string with substitution for If.Condition block (Syntax 9)
When If.Condition is a string type instead of compound, parse it using
snd_config_load_string with variable substitution support for syntax v9+.
This allows more flexible condition definitions using variable references.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-06 19:32:31 +01:00
Jaroslav Kysela
30d1ae7221 ucm: optimize if_eval_string with common comparison helper
Refactor if_eval_string() to eliminate code duplication by introducing
a compare_strings() helper function that handles the common pattern of
retrieving, substituting, and comparing string pairs.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-06 19:32:31 +01:00
Jaroslav Kysela
27aa3e41ef ucm: add Repeat block - repetitive pattern substitution (Syntax 9)
Implements Repeat blocks for generating repetitive configuration patterns
with variable substitution. This feature allows applying a configuration
block multiple times with different variable values, significantly reducing
duplication in UCM configuration files.

iterator abstraction allows easy extension for future pattern types.

Example:

  Repeat.VolumeInit {
    Pattern {
      Variable 'ch'
      Type Integer
      First 0
      Last 7
      Step 1
    }
    Apply {
      cset "name='PCM Channel ${var:ch} Volume' 100%"
    }
  }

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-06 19:32:28 +01:00
Jaroslav Kysela
2943b1e412 ucm: add integer comparison condition (Syntax 9)
Adds support for integer comparison operations in If.Condition blocks.
Supports operators: ==, !=, <, >, <=, >= for comparing integer values.
Both values are substituted and converted from strings to 64-bit integers.
Hexadecimal (C like) strings are also accepted (like 0x1234).

Example usage:
  If.check_channels {
    Condition {
      Type Integer
      Operation ">"
      Value1 "${var:channels}"
      Value2 "2"
    }
    True { ... }
  }

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-06 18:27:37 +01:00
Jaroslav Kysela
e02e9dc6cf ucm: substitute define IDs and macro arguments (Syntax 9)
It is useful to substitute the variable names and string
macro arguments. It may simplify the UCM configurations.

E.g.:

  Define."${var:Name} Suffix" "Value"
  Macro.a.DoIt "Channels=${var:PlaybackChannels}"

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-05 17:35:55 +01:00
Jaroslav Kysela
5414277612 ucm: fix invalid pointer dereference in parse_open_variables()
Some checks failed
Build alsa-lib / fedora_latest_build (push) Has been cancelled
Build alsa-lib / ubuntu_last_build (push) Has been cancelled
When the string with variables is not parseable, do not
try to free invalid cfg pointer.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-04 18:43:59 +01:00
Jaroslav Kysela
ebf2efae0a control: remap - cosmetic code reorganization
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-04 18:42:56 +01:00
Jaroslav Kysela
166407dae4 control: remap - fix numid lookup issue
Some checks are pending
Build alsa-lib / fedora_latest_build (push) Waiting to run
Build alsa-lib / ubuntu_last_build (push) Waiting to run
The 'amixer controls' and 'amixer cget numid=' combo was not working
correctly when the remapping was active. This assert was trigerred:

amixer: control.c:427: snd_ctl_elem_info: Assertion `ctl && info && (info->id.name[0] || info->id.numid)' failed

All elements must be loaded and mapping created to build
the numid -> fullid link.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-02-03 17:46:02 +01:00
Jaroslav Kysela
5f7fe33002 topology: decoder - add boundary check for channel mixer count
Some checks failed
Build alsa-lib / fedora_latest_build (push) Has been cancelled
Build alsa-lib / ubuntu_last_build (push) Has been cancelled
Malicious binary topology file may cause heap corruption.

CVE: CVE-2026-25068

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-01-29 16:55:49 +01:00
Jaroslav Kysela
bc332f4211 control: ctlparse - make numid parsing more robust
Also correct the last amixer stderr printf to snd_error().

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-01-29 15:34:23 +01:00
Jaroslav Kysela
7887fbc6f0 ucm: libconfig parser - fix pathname for substituted file
Some checks failed
Build alsa-lib / fedora_latest_build (push) Has been cancelled
Build alsa-lib / ubuntu_last_build (push) Has been cancelled
The path name substituted file contents and normal file contents
should be handled similary. Use correct function determining
the right base directory name.

Fixes: 8f5779eb ("ucm: add LibraryConfig support")
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-01-26 15:10:46 +01:00
Jaroslav Kysela
75ed5f05ba Release v1.2.15.3
Some checks failed
Build alsa-lib / fedora_latest_build (push) Has been cancelled
Build alsa-lib / ubuntu_last_build (push) Has been cancelled
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-01-13 08:12:40 +01:00
Jaroslav Kysela
805464c7bd seq: return back old snd_seq_drain_output behaviour for -EAGAIN
It seems that many applications did not follow the documentation
including pyalsa sequencer module, thus return the previous
behaviour and correct documentation.

Closes: https://github.com/alsa-project/alsa-lib/issues/493
Fixes: b97a11ec ("seq: fix snd_seq_drain_output return value for partial drain")
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-01-13 08:09:47 +01:00
Jaroslav Kysela
63a981865a Release v1.2.15.2
Some checks failed
Build alsa-lib / fedora_latest_build (push) Has been cancelled
Build alsa-lib / ubuntu_last_build (push) Has been cancelled
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-01-08 15:27:57 +01:00
Jaroslav Kysela
b3bc2b4fee Makefile: remove dist-hook and remove tar option 'follow symlinks'
Some checks are pending
Build alsa-lib / fedora_latest_build (push) Waiting to run
Build alsa-lib / ubuntu_last_build (push) Waiting to run
The dist-hook is not required for latest automake. It was
introduced in 2000, so the things were hopefully fixed now.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-01-08 14:51:15 +01:00
Jaroslav Kysela
50b532de79 ucm: add some traces for the config filenames
It is handy to see the path names in the loading chain.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-01-08 13:56:48 +01:00
Jaroslav Kysela
8bea4b13d1 error: fix the "return old snd_lib_error_set_handler() behaviour"
The variable arguments must be handled differently. Add a conversion
routine which use the original argument call convention.

Fixes: f55eece8 ("error: return old snd_lib_error_set_handler() behaviour")
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-01-08 13:19:45 +01:00
Jaroslav Kysela
a37b8b9377 error: fix indendation in snd_lib_log_filter()
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-01-07 17:48:40 +01:00
Jaroslav Kysela
f55eece85c error: return old snd_lib_error_set_handler() behaviour
Unfortunately, the case when the old application sets
own error callback using the old method, was not handled
properly in snd_lib_vlog_default() function.

Make sure that only error messages are passed to this
old callback and silent other log priorities.

Link: https://bugs.gentoo.org/968131
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2026-01-07 17:48:39 +01:00
17 changed files with 1082 additions and 150 deletions

View file

@ -1,3 +1,6 @@
# remove -h for tar (follow symlinks) to avoid endless include/alsa/alsa/...
am__tar = $${TAR-tar} cof - "$$tardir"
ACLOCAL_AMFLAGS = -I m4
SUBDIRS=doc include src
@ -20,14 +23,6 @@ AM_CPPFLAGS=-I$(top_srcdir)/include
rpm: dist
$(MAKE) -C utils rpm
dist-hook:
-chmod -R a+r $(distdir)
@if ! test -z "$(AMTAR)"; then \
$(AMTAR) --create --verbose --file=- $(distdir) | bzip2 -c -9 > $(distdir).tar.bz2 ; \
else \
$(TAR) --create --verbose --file=- $(distdir) | bzip2 -c -9 > $(distdir).tar.bz2 ; \
fi
doc-dummy:
doc: doc-dummy

View file

@ -1,6 +1,6 @@
dnl Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(alsa-lib, 1.2.15.1)
AC_INIT(alsa-lib, 1.2.15.3)
AC_CONFIG_SRCDIR([src/control/control.c])
AC_CONFIG_MACRO_DIR([m4])

View file

@ -31,6 +31,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <limits.h>
#include <unistd.h>
#include <string.h>
@ -97,8 +98,11 @@ typedef struct {
typedef struct {
snd_ctl_t *child;
int numid_remap_active;
bool list_complete;
bool numid_remap_active;
unsigned int numid_app_last;
unsigned int list_first;
unsigned int list_last;
size_t numid_items;
size_t numid_alloc;
@ -125,6 +129,8 @@ typedef struct {
} snd_ctl_remap_t;
#endif
static int remap_load_list(snd_ctl_remap_t *priv);
static snd_ctl_numid_t *remap_numid_temp(snd_ctl_remap_t *priv, unsigned int numid)
{
priv->numid_temp.numid_child = numid;
@ -137,6 +143,8 @@ static snd_ctl_numid_t *remap_find_numid_app(snd_ctl_remap_t *priv, unsigned int
snd_ctl_numid_t *numid;
size_t count;
if (numid_app == 0)
return NULL;
if (!priv->numid_remap_active)
return remap_numid_temp(priv, numid_app);
numid = priv->numid;
@ -151,6 +159,8 @@ static snd_ctl_numid_t *remap_numid_new(snd_ctl_remap_t *priv, unsigned int numi
{
snd_ctl_numid_t *numid;
if (numid_app == 0)
return NULL;
if (priv->numid_alloc == priv->numid_items) {
numid = realloc(priv->numid, (priv->numid_alloc + 16) * sizeof(*numid));
if (numid == NULL)
@ -187,6 +197,8 @@ static snd_ctl_numid_t *remap_find_numid_child(snd_ctl_remap_t *priv, unsigned i
snd_ctl_numid_t *numid;
size_t count;
if (numid_child == 0)
return NULL;
if (!priv->numid_remap_active)
return remap_numid_temp(priv, numid_child);
numid = priv->numid;
@ -282,8 +294,11 @@ static int remap_id_to_child(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id, snd_c
{
snd_ctl_remap_id_t *rid;
snd_ctl_numid_t *numid;
bool reloaded = false;
int err;
debug_id(id, "%s enter\n", __func__);
_retry:
rid = remap_find_id_app(priv, id);
if (rid) {
if (rid->id_app.numid == 0) {
@ -295,13 +310,19 @@ static int remap_id_to_child(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id, snd_c
}
*id = rid->id_child;
} else {
if (remap_find_id_child(priv, id))
return -ENOENT;
numid = remap_find_numid_app(priv, id->numid);
if (numid)
if (numid) {
id->numid = numid->numid_child;
else
id->numid = 0;
} else {
if (reloaded || priv->list_complete)
return -ENOENT;
/* build whole numid mapping */
err = remap_load_list(priv);
if (err < 0)
return err;
reloaded = true;
goto _retry;
}
}
*_rid = rid;
debug_id(id, "%s leave\n", __func__);
@ -329,6 +350,7 @@ static int remap_id_to_app(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id, snd_ctl
id->numid = numid->numid_app;
}
}
debug_id(id, "%s rid %p\n", __func__, rid);
return err;
}
@ -466,9 +488,8 @@ static int snd_ctl_remap_card_info(snd_ctl_t *ctl, snd_ctl_card_info_t *info)
return snd_ctl_card_info(priv->child, info);
}
static int snd_ctl_remap_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list)
static int _snd_ctl_remap_elem_list(snd_ctl_remap_t *priv, snd_ctl_elem_list_t *list)
{
snd_ctl_remap_t *priv = ctl->private_data;
snd_ctl_elem_id_t *id;
snd_ctl_remap_id_t *rid;
snd_ctl_numid_t *numid;
@ -483,13 +504,17 @@ static int snd_ctl_remap_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list)
id = &list->pids[index];
rid = remap_find_id_child(priv, id);
if (rid) {
rid->id_app.numid = id->numid;
*id = rid->id_app;
assert(id->numid > 0);
rid->id_child.numid = id->numid;
}
numid = remap_find_numid_child(priv, id->numid);
if (numid == NULL)
return -EIO;
id->numid = numid->numid_app;
if (rid) {
rid->id_app.numid = id->numid;
*id = rid->id_app;
}
}
if (list->offset >= list->count + priv->map_items + priv->sync_switch_items)
return 0;
@ -510,9 +535,39 @@ static int snd_ctl_remap_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list)
}
}
list->count += priv->map_items + priv->sync_switch_items;
if (list->offset < priv->list_first)
priv->list_first = list->offset;
if (list->offset == priv->list_last && list->offset + list->used > priv->list_last)
priv->list_last = list->offset + list->used;
priv->list_complete = priv->list_first == 0 && list->count == priv->list_last;
return 0;
}
static int snd_ctl_remap_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list)
{
snd_ctl_remap_t *priv = ctl->private_data;
return _snd_ctl_remap_elem_list(priv, list);
}
static int remap_load_list(snd_ctl_remap_t *remap)
{
snd_ctl_elem_list_t list;
int err = 0;
memset(&list, 0, sizeof(list));
do {
err = _snd_ctl_remap_elem_list(remap, &list);
if (err < 0)
break;
err = snd_ctl_elem_list_alloc_space(&list, list.count);
if (err < 0)
break;
} while (list.count != list.used);
snd_ctl_elem_list_free_space(&list);
return err;
}
#ifndef DOC_HIDDEN
#define ACCESS_BITS(bits) \
(bits & (SNDRV_CTL_ELEM_ACCESS_READWRITE|\
@ -1674,6 +1729,7 @@ int snd_ctl_remap_open(snd_ctl_t **handlep, const char *name, snd_config_t *rema
priv->numid_remap_active = priv->map_items > 0 || priv->sync_items;
priv->list_first = UINT_MAX;
priv->child = child;
err = snd_ctl_new(&ctl, SND_CTL_TYPE_REMAP, name, mode);
if (err < 0) {

View file

@ -156,8 +156,10 @@ char *snd_ctl_ascii_elem_id_get(snd_ctl_elem_id_t *id)
int __snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str,
const char **ret_ptr)
{
int c, size, numid;
char buf[64];
int c, size;
int err = -EINVAL;
long l;
char *ptr;
while (isspace(*str))
@ -168,12 +170,23 @@ int __snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str,
while (*str) {
if (!strncasecmp(str, "numid=", 6)) {
str += 6;
numid = atoi(str);
if (numid <= 0) {
fprintf(stderr, "amixer: Invalid numid %d\n", numid);
ptr = buf;
size = 0;
while (*str && *str != ',') {
if (size < (int)sizeof(buf)) {
*ptr++ = *str;
size++;
}
str++;
}
*ptr = '\0';
if (safe_strtol(buf, &l) < 0)
l = -1;
if (l <= 0 || l >= INT32_MAX) {
snd_error(CONTROL, "Invalid numid %ld (%s)", l, buf);
goto out;
}
snd_ctl_elem_id_set_numid(dst, atoi(str));
snd_ctl_elem_id_set_numid(dst, (int)l);
while (isdigit(*str))
str++;
} else if (!strncasecmp(str, "iface=", 6)) {
@ -200,7 +213,6 @@ int __snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str,
goto out;
}
} else if (!strncasecmp(str, "name=", 5)) {
char buf[64];
str += 5;
ptr = buf;
size = 0;

View file

@ -34,6 +34,8 @@
#include <stdarg.h>
#include <string.h>
static void snd_lib_error_default(const char *file, int line, const char *function, int errcode, const char *fmt, ...);
/**
* Array of error codes in US ASCII.
*/
@ -281,7 +283,8 @@ int snd_lib_log_filter(int prio, int interface, const char *configstr)
if (interface > 0 && interface <= SND_ILOG_LAST && debug_config.interface_levels[interface] > 0) {
level = debug_config.interface_levels[interface];
} else {
level = debug_config.global_level; }
level = debug_config.global_level;
}
if (level == 0)
level = SND_LOG_ERROR;
@ -290,6 +293,8 @@ int snd_lib_log_filter(int prio, int interface, const char *configstr)
return prio <= (int)level;
}
static void snd_lib_error_vdefault(const char *file, int line, const char *function, int errcode, const char *fmt, va_list arg);
/**
* \brief The default log handler function.
* \param prio Priority value (SND_LOG_*).
@ -317,6 +322,12 @@ static void snd_lib_vlog_default(int prio, int interface, const char *file, int
local_error(file, line, function, errcode, fmt, arg);
return;
}
if (snd_lib_error != snd_lib_error_default) {
if (prio == SND_LOG_ERROR)
snd_lib_error_vdefault(file, line, function, errcode, fmt, arg);
/* ignore other priorities - restore old behaviour */
return;
}
if (!snd_lib_log_filter(prio, interface, NULL))
return;
@ -443,6 +454,25 @@ static void snd_lib_error_default(const char *file, int line, const char *functi
va_end(arg);
}
/**
* \brief The default error handler function.
* \param file The filename where the error was hit.
* \param line The line number.
* \param function The function name.
* \param errcode The error code.
* \param fmt The message (including the format characters).
* \param arg Optional arguments.
* \deprecated Since 1.2.15
*
* Use snd_lib_vlog handler to print error message for anonymous interface.
*/
static void snd_lib_error_vdefault(const char *file, int line, const char *function, int errcode, const char *fmt, va_list arg)
{
char msg[512];
vsnprintf(msg, sizeof(msg), fmt, arg);
snd_lib_error(file, line, function, errcode, "%s", msg);
}
/**
* \ingroup Error
* \deprecated Since 1.2.15

View file

@ -4431,8 +4431,7 @@ int snd_seq_event_output_pending(snd_seq_t *seq)
* \brief drain output buffer to sequencer
* \param seq sequencer handle
* \return 0 when all events are drained and sent to sequencer.
* When events still remain on the buffer, the byte size of remaining
* events are returned. On error a negative error code is returned.
* On error a negative error code is returned (including -EAGAIN).
*
* This function drains all pending events on the output buffer.
* The function returns immediately after the events are sent to the queues
@ -4444,19 +4443,15 @@ int snd_seq_event_output_pending(snd_seq_t *seq)
*/
int snd_seq_drain_output(snd_seq_t *seq)
{
ssize_t result, processed = 0;
ssize_t result;
assert(seq);
while (seq->obufused > 0) {
result = seq->ops->write(seq, seq->obuf, seq->obufused);
if (result < 0) {
if (result == -EAGAIN && processed > 0)
return seq->obufused;
if (result < 0)
return result;
}
if ((size_t)result < seq->obufused)
memmove(seq->obuf, seq->obuf + result, seq->obufused - result);
seq->obufused -= result;
processed += result;
}
return 0;
}

View file

@ -1250,6 +1250,11 @@ int tplg_decode_control_mixer1(snd_tplg_t *tplg,
if (mc->num_channels > 0) {
map = tplg_calloc(heap, sizeof(*map));
map->num_channels = mc->num_channels;
if (map->num_channels > SND_TPLG_MAX_CHAN ||
map->num_channels > SND_SOC_TPLG_MAX_CHAN) {
snd_error(TOPOLOGY, "mixer: unexpected channel count %d", map->num_channels);
return -EINVAL;
}
for (i = 0; i < map->num_channels; i++) {
map->channel[i].reg = mc->channel[i].reg;
map->channel[i].shift = mc->channel[i].shift;

View file

@ -1,7 +1,7 @@
EXTRA_LTLIBRARIES = libucm.la
libucm_la_SOURCES = utils.c parser.c ucm_cond.c ucm_subs.c ucm_include.c \
ucm_regex.c ucm_exec.c main.c
ucm_regex.c ucm_repeat.c ucm_exec.c main.c
noinst_HEADERS = ucm_local.h ucm_confdoc.h

View file

@ -1702,7 +1702,7 @@ const char *parse_open_variables(snd_use_case_mgr_t *uc_mgr, const char *name)
{
const char *end, *id;
char *args, *var;
snd_config_t *cfg, *n;
snd_config_t *cfg = NULL, *n;
snd_config_iterator_t i, next;
char vname[128];
size_t l;
@ -1739,6 +1739,7 @@ const char *parse_open_variables(snd_use_case_mgr_t *uc_mgr, const char *name)
}
skip:
if (cfg)
snd_config_delete(cfg);
return end + 3;
}
@ -1780,7 +1781,7 @@ int snd_use_case_mgr_open(snd_use_case_mgr_t **uc_mgr,
card_name = parse_open_variables(mgr, card_name);
/* Application developers: This argument is not supposed to be set for standard applications. */
if (uc_mgr_get_variable(mgr, "@InBoot"))
if (uc_mgr_get_variable(mgr, "@InBoot", false))
mgr->in_boot = true;
err = uc_mgr_card_open(mgr);

View file

@ -391,7 +391,7 @@ static int evaluate_define(snd_use_case_mgr_t *uc_mgr,
snd_config_iterator_t i, next;
snd_config_t *d, *n;
const char *id;
char *var, *s;
char *var, *s, *sid;
int err;
err = snd_config_search(cfg, "Define", &d);
@ -427,8 +427,18 @@ static int evaluate_define(snd_use_case_mgr_t *uc_mgr,
snd_error(UCM, "value names starting with '@' are reserved for application variables");
return -EINVAL;
}
err = uc_mgr_set_variable(uc_mgr, id, s);
sid = (char *)id;
if (uc_mgr->conf_format >= 9) {
err = uc_mgr_get_substituted_value(uc_mgr, &sid, id);
if (err < 0) {
free(s);
return err;
}
}
err = uc_mgr_set_variable(uc_mgr, sid, s);
free(s);
if (id != sid)
free(sid);
if (err < 0)
return err;
}
@ -495,7 +505,15 @@ static int evaluate_macro1(snd_use_case_mgr_t *uc_mgr,
err = snd_config_get_string(args, &s);
if (err < 0)
return err;
if (uc_mgr->conf_format < 9) {
err = snd_config_load_string(&a, s, 0);
} else {
err = uc_mgr_get_substituted_value(uc_mgr, &var2, s);
if (err >= 0) {
err = snd_config_load_string(&a, var2, 0);
free(var2);
}
}
if (err < 0)
return err;
} else if (snd_config_get_type(args) != SND_CONFIG_TYPE_COMPOUND) {
@ -509,7 +527,7 @@ static int evaluate_macro1(snd_use_case_mgr_t *uc_mgr,
if (err < 0)
goto __err_path;
snprintf(name, sizeof(name), "__%s", id);
if (uc_mgr_get_variable(uc_mgr, name)) {
if (uc_mgr_get_variable(uc_mgr, name, false)) {
snd_error(UCM, "Macro argument '%s' is already defined", name);
goto __err_path;
}
@ -711,9 +729,9 @@ int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg)
{
long iterations = 10000;
int err1 = 0, err2 = 0, err3 = 0, err4 = 0, err5 = 0;
int err1 = 0, err2 = 0, err3 = 0, err4 = 0, err5 = 0, err6 = 0;
while (err1 == 0 || err2 == 0 || err3 == 0 || err4 == 0 || err5 == 0) {
while (err1 == 0 || err2 == 0 || err3 == 0 || err4 == 0 || err5 == 0 || err6 == 0) {
if (iterations == 0) {
snd_error(UCM, "Maximal inplace evaluation iterations number reached (recursive references?)");
return -EINVAL;
@ -747,9 +765,12 @@ int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr,
return err4;
if (err4 == 0)
continue;
err5 = evaluate_condition(uc_mgr, cfg);
err5 = uc_mgr_evaluate_repeat(uc_mgr, cfg);
if (err5 < 0)
return err5;
err6 = evaluate_condition(uc_mgr, cfg);
if (err6 < 0)
return err6;
}
return 0;
}
@ -804,7 +825,7 @@ static int parse_libconfig1(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
if (file) {
if (substfile) {
snd_config_t *cfg;
err = uc_mgr_config_load(uc_mgr->conf_format, file, &cfg);
err = uc_mgr_config_load_file(uc_mgr, file, &cfg);
if (err < 0)
return err;
err = uc_mgr_substitute_tree(uc_mgr, cfg);
@ -3417,6 +3438,7 @@ static int parse_toplevel_path(snd_use_case_mgr_t *uc_mgr,
}
ucm_filename(fn, sizeof(fn), version, dir, file);
snd_trace(UCM, "probing configuration file '%s'", fn);
if (access(fn, R_OK) == 0 && lstat64(fn, &st) == 0) {
if (S_ISLNK(st.st_mode)) {
ssize_t r;
@ -3448,6 +3470,7 @@ static int parse_toplevel_path(snd_use_case_mgr_t *uc_mgr,
}
free(link);
}
snd_trace(UCM, "using directory '%s' and file '%s'", dir, file);
if (replace_string(&uc_mgr->conf_dir_name, dir) == NULL)
goto __enomem;
if (replace_string(&uc_mgr->conf_file_name, file) == NULL)

View file

@ -38,10 +38,70 @@ static int get_string(snd_config_t *compound, const char *key, const char **str)
return snd_config_get_string(node, str);
}
static int if_eval_string(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval)
typedef int (*string_compare_t)(const char *s1, const char *s2);
static int compare_strings(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval,
const char *key1, const char *key2,
string_compare_t compare)
{
const char *string1 = NULL, *string2 = NULL;
char *s1, *s2;
int err, result;
err = get_string(eval, key1, &string1);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "String error (If.Condition.%s)", key1);
return -EINVAL;
}
err = get_string(eval, key2, &string2);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "String error (If.Condition.%s)", key2);
return -EINVAL;
}
if (!string1 && !string2)
return -ENOENT; /* not found */
if (!string1) {
snd_error(UCM, "If.Condition.%s not defined", key1);
return -EINVAL;
}
if (!string2) {
snd_error(UCM, "If.Condition.%s not defined", key2);
return -EINVAL;
}
err = uc_mgr_get_substituted_value(uc_mgr, &s1, string1);
if (err < 0)
return err;
err = uc_mgr_get_substituted_value(uc_mgr, &s2, string2);
if (err < 0) {
free(s1);
return err;
}
result = compare(s1, s2);
free(s2);
free(s1);
return result;
}
static int string_equal(const char *s1, const char *s2)
{
return strcasecmp(s1, s2) == 0;
}
static int string_contains(const char *s1, const char *s2)
{
return strstr(s1, s2) != NULL;
}
static int if_eval_string(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval)
{
const char *string1 = NULL;
char *s1;
int err;
if (uc_mgr->conf_format >= 3) {
@ -61,75 +121,13 @@ static int if_eval_string(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval)
}
}
err = get_string(eval, "String1", &string1);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "String error (If.Condition.String1)");
return -EINVAL;
}
err = compare_strings(uc_mgr, eval, "String1", "String2", string_equal);
if (err != -ENOENT) /* -ENOENT means not found, continue checking */
return err;
err = get_string(eval, "String2", &string2);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "String error (If.Condition.String2)");
return -EINVAL;
}
if (string1 || string2) {
if (string1 == NULL) {
snd_error(UCM, "If.Condition.String1 not defined");
return -EINVAL;
}
if (string2 == NULL) {
snd_error(UCM, "If.Condition.String2 not defined");
return -EINVAL;
}
err = uc_mgr_get_substituted_value(uc_mgr, &s1, string1);
if (err < 0)
err = compare_strings(uc_mgr, eval, "Haystack", "Needle", string_contains);
if (err != -ENOENT)
return err;
err = uc_mgr_get_substituted_value(uc_mgr, &s2, string2);
if (err < 0) {
free(s1);
return err;
}
err = strcasecmp(s1, s2) == 0;
free(s2);
free(s1);
return err;
}
err = get_string(eval, "Haystack", &string1);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "String error (If.Condition.Haystack)");
return -EINVAL;
}
err = get_string(eval, "Needle", &string2);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "String error (If.Condition.Needle)");
return -EINVAL;
}
if (string1 || string2) {
if (string1 == NULL) {
snd_error(UCM, "If.Condition.Haystack not defined");
return -EINVAL;
}
if (string2 == NULL) {
snd_error(UCM, "If.Condition.Needle not defined");
return -EINVAL;
}
err = uc_mgr_get_substituted_value(uc_mgr, &s1, string1);
if (err < 0)
return err;
err = uc_mgr_get_substituted_value(uc_mgr, &s2, string2);
if (err < 0) {
free(s1);
return err;
}
err = strstr(s1, s2) != NULL;
free(s2);
free(s1);
return err;
}
snd_error(UCM, "Unknown String condition arguments");
return -EINVAL;
@ -270,6 +268,80 @@ static int if_eval_control_exists(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval
return 1;
}
static int if_eval_integer(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval)
{
const char *value1_str = NULL, *value2_str = NULL, *operation = NULL;
char *s1, *s2;
long long val1, val2;
int err, err1, err2;
if (uc_mgr->conf_format < 9) {
snd_error(UCM, "Integer condition is supported in v9+ syntax");
return -EINVAL;
}
err = get_string(eval, "Operation", &operation);
if (err < 0) {
snd_error(UCM, "Integer error (If.Condition.Operation)");
return -EINVAL;
}
err = get_string(eval, "Value1", &value1_str);
if (err < 0) {
snd_error(UCM, "Integer error (If.Condition.Value1)");
return -EINVAL;
}
err = get_string(eval, "Value2", &value2_str);
if (err < 0) {
snd_error(UCM, "Integer error (If.Condition.Value2)");
return -EINVAL;
}
err = uc_mgr_get_substituted_value(uc_mgr, &s1, value1_str);
if (err < 0)
return err;
err = uc_mgr_get_substituted_value(uc_mgr, &s2, value2_str);
if (err < 0) {
free(s1);
return err;
}
err1 = safe_strtoll(s1, &val1);
err2 = safe_strtoll(s2, &val2);
if (err1 < 0 || err2 < 0) {
if (err1 < 0)
snd_error(UCM, "Integer conversion error for Value1 '%s'", s1);
if (err2 < 0)
snd_error(UCM, "Integer conversion error for Value2 '%s'", s2);
free(s2);
free(s1);
return -EINVAL;
}
free(s2);
free(s1);
if (strcmp(operation, "==") == 0) {
return val1 == val2;
} else if (strcmp(operation, "!=") == 0) {
return val1 != val2;
} else if (strcmp(operation, "<") == 0) {
return val1 < val2;
} else if (strcmp(operation, ">") == 0) {
return val1 > val2;
} else if (strcmp(operation, "<=") == 0) {
return val1 <= val2;
} else if (strcmp(operation, ">=") == 0) {
return val1 >= val2;
} else {
snd_error(UCM, "Integer unknown operation '%s'", operation);
return -EINVAL;
}
}
static int if_eval_path(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval)
{
const char *path, *mode = "";
@ -365,6 +437,9 @@ static int if_eval(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval)
if (strcmp(type, "Path") == 0)
return if_eval_path(uc_mgr, eval);
if (strcmp(type, "Integer") == 0)
return if_eval_integer(uc_mgr, eval);
snd_error(UCM, "unknown If.Condition.Type");
return -EINVAL;
}
@ -377,7 +452,9 @@ static int if_eval_one(snd_use_case_mgr_t *uc_mgr,
snd_config_t **prepend,
snd_config_t **append)
{
snd_config_t *expr, *_true = NULL, *_false = NULL;
snd_config_t *expr, *expr_eval = NULL, *_true = NULL, *_false = NULL;
const char *s;
char *s1;
int err, has_condition;
*result = NULL;
@ -392,73 +469,99 @@ static int if_eval_one(snd_use_case_mgr_t *uc_mgr,
/* For syntax v8+, Condition is optional if Prepend or Append is present */
has_condition = snd_config_search(cond, "Condition", &expr) >= 0;
if (has_condition && uc_mgr->conf_format >= 9 &&
snd_config_get_type(expr) == SND_CONFIG_TYPE_STRING) {
err = snd_config_get_string(expr, &s);
if (err < 0) {
snd_error(UCM, "Condition string error (If)");
return -EINVAL;
}
err = uc_mgr_get_substituted_value(uc_mgr, &s1, s);
if (err >= 0) {
err = snd_config_load_string(&expr_eval, s1, 0);
free(s1);
}
if (err < 0) {
snd_error(UCM, "Condition string parse error (If)");
return err;
}
expr = expr_eval;
}
if (uc_mgr->conf_format >= 8) {
/* Check for Prepend block */
err = snd_config_search(cond, "Prepend", prepend);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "prepend block error (If)");
return -EINVAL;
goto __error;
}
/* Check for Append block */
err = snd_config_search(cond, "Append", append);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "append block error (If)");
return -EINVAL;
goto __error;
}
/* If Prepend or Append is present, Condition can be omitted */
if (!has_condition && (*prepend == NULL && *append == NULL)) {
snd_error(UCM, "condition block expected (If)");
return -EINVAL;
goto __error;
}
} else {
if (!has_condition) {
snd_error(UCM, "condition block expected (If)");
return -EINVAL;
goto __error;
}
}
err = snd_config_search(cond, "True", &_true);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "true block error (If)");
return -EINVAL;
goto __error;
}
err = snd_config_search(cond, "False", &_false);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "false block error (If)");
return -EINVAL;
goto __error;
}
err = snd_config_search(cond, "Before", before);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "before block identifier error");
return -EINVAL;
goto __error;
}
err = snd_config_search(cond, "After", after);
if (err < 0 && err != -ENOENT) {
snd_error(UCM, "before block identifier error");
return -EINVAL;
goto __error;
}
/* Evaluate condition if present */
if (has_condition) {
err = if_eval(uc_mgr, expr);
if (err < 0)
goto __error;
if (err > 0) {
*result = _true;
return 0;
goto __return;
} else if (err == 0) {
*result = _false;
return 0;
} else {
return err;
goto __return;
}
}
/* If no condition (v8+ with Prepend/Append only), no result block */
return 0;
__error:
err = -EINVAL;
__return:
if (expr_eval)
snd_config_delete(expr_eval);
return err;
}
#if 0

View file

@ -587,8 +587,8 @@ Evaluation order | Configuration block | Evaluation restart
2 | Include | Yes
3 | Variant | Yes
4 | Macro | Yes
5 | If | Yes
5 | Repeat | Yes
6 | If | Yes
### Substitutions
@ -616,6 +616,7 @@ ${var:\<str\>} | UCM parser variable (set using a _Define_ block)
${eval:\<str\>} | Evaluate expression like *($var+2)/3* [**Syntax 5**]
${find-card:\<str\>} | Find a card - see _Find card substitution_ section
${find-device:\<str\>} | Find a device - see _Find device substitution_ section
${info-card:\<str\>} | Get card information - see _Card info substitution_ section [**Syntax 9**]
General note: If two dollars '$$' instead one dolar '$' are used for the
substitution identification, the error is ignored (e.g. file does not
@ -662,6 +663,7 @@ Usage example:
~~~{.html}
${find-card:field=name,regex='^acp$',return=number}
${find-card:field=$FieldName,regex=$Pattern,return=number}
~~~
Arguments:
@ -669,8 +671,8 @@ Arguments:
Argument | Description
---------------------|-----------------------
return | return value type (id, number), id is the default
field | field for the lookup (id, driver, name, longname, mixername, components)
regex | regex string for the field match
field | field for the lookup (id, driver, name, longname, mixername, components) or variable name ($var) [**Syntax 9**]
regex | regex string for the field match or variable name ($var) [**Syntax 9**]
#### Find device substitution
@ -678,16 +680,50 @@ Usage example:
~~~{.html}
${find-device:type=pcm,field=name,regex='DMIC'}
${find-device:type=$DevType,stream=$StreamType,field=$FieldName,regex=$Pattern}
~~~
Arguments:
Argument | Description
---------------------|-----------------------
type | device type (pcm)
stream | stream type (playback, capture), playback is default
field | field for the lookup (id, name, subname)
regex | regex string for the field match
type | device type (pcm) or variable name ($var) [**Syntax 9**]
stream | stream type (playback, capture), playback is default; variable name ($var) supported in **Syntax 9**
field | field for the lookup (id, name, subname) or variable name ($var) [**Syntax 9**]
regex | regex string for the field match or variable name ($var) [**Syntax 9**]
#### Card info substitution
This substitution retrieves information about a specific ALSA card by card number
or card ID and returns the requested field value.
Usage examples:
~~~{.html}
${info-card:card=0,field=name}
${info-card:card=acp,field=driver}
${info-card:card=PCH,field=longname}
${info-card:card=$MyCard,field=$MyField}
~~~
Arguments:
Argument | Description
---------------------|--------------------------------------------------
card | card number (integer), card ID (string), or variable name ($var)
field | field to retrieve (number, id, driver, name, longname, mixername, components) or variable name ($var)
The **card** parameter can be either a card number (e.g., 0, 1, 2), a card ID string (e.g., "PCH", "acp", "Intel"),
or a variable name prefixed with $ (e.g., $CardId).
The **field** parameter specifies which card information to return or can be a variable name prefixed with $ (e.g., $FieldName):
- **number**: Card number (integer as string)
- **id**: Card identifier
- **driver**: Card driver name
- **name**: Card short name
- **longname**: Card long name
- **mixername**: Mixer name
- **components**: Card components
### Variable defines
@ -878,6 +914,32 @@ If.fmic {
}
~~~
#### Integer comparison (Type Integer)
Field | Description
---------------------|-----------------------
Operation | comparison operator (==, !=, <, >, <=, >=)
Value1 | first integer value (string converted to long long)
Value2 | second integer value (string converted to long long)
Note: Integer condition is supported in *Syntax* version *9*+.
Example:
~~~{.html}
If.check_channels {
Condition {
Type Integer
Operation ">"
Value1 "${var:channels}"
Value2 "2"
}
True {
...
}
}
~~~
### Variants
To avoid duplication of the many configuration files for the cases with
@ -986,6 +1048,136 @@ SectionDevice."HDMI:LowRate" {
This creates two devices: **HDMI:LowRate** (48kHz) and **HDMI:HighRate** (192kHz).
### Repetitive Pattern Substitution
Starting with **Syntax 9**, the UCM configuration supports the **Repeat** block for generating
repetitive configuration patterns. This feature allows you to apply a configuration block multiple
times with different variable values, reducing duplication in configuration files.
The **Repeat** block contains two main components:
1. **Pattern**: Defines the iteration pattern (how many times to repeat and what values to use)
2. **Apply**: The configuration block to be applied on each iteration
#### Pattern Types
The **Pattern** block supports two types: **Integer** and **Array**.
**Integer Pattern**: Iterates over a range of integer values
~~~{.html}
Repeat.MyRepeat {
Pattern {
Variable 'ChannelNum'
Type Integer
First 0
Last 15
Step 2
}
Apply {
... configuration using ${var:ChannelNum} ...
}
}
~~~
Fields for Integer pattern:
- **Variable**: Name of the variable to substitute (without ${var:} prefix)
- **Type**: Must be "Integer"
- **First**: Starting value (integer)
- **Last**: Ending value (integer)
- **Step**: Increment value (integer, default 1)
The iteration supports reverse order automatically when First is greater than Last.
**Array Pattern**: Iterates over a list of string values
~~~{.html}
Repeat.DeviceList {
Pattern {
Variable 'DevName'
Type Array
Array [
"Speaker"
"Headphones"
"HDMI"
]
}
Apply {
... configuration using ${var:DevName} ...
}
}
~~~
Fields for Array pattern:
- **Variable**: Name of the variable to substitute (without ${var:} prefix)
- **Type**: Must be "Array"
- **Array**: A compound node containing string values to iterate over
**String Pattern**: Pattern can also be specified as a string that will be parsed as a
configuration block. This allows for dynamic pattern generation.
~~~{.html}
Repeat.Dynamic {
Pattern "
Variable 'Index'
Type Integer
First 1
Last 4
"
Apply {
... configuration using ${var:Index} ...
}
}
~~~
#### Complete Example
Example using Integer pattern to create multiple similar control settings:
~~~{.html}
EnableSequence [
Repeat.VolumeInit {
Pattern {
Variable 'ch'
Type Integer
First 0
Last 7
}
Apply {
cset "name='PCM Channel ${var:ch} Volume' 100%"
}
}
]
~~~
This generates 8 cset commands for channels 0 through 7.
Example using Array pattern for different device configurations:
~~~{.html}
Repeat.Devices {
Pattern {
Variable 'output'
Type Array
Array [
"Speaker"
"Headphones"
"LineOut"
]
}
Apply {
SectionDevice."${var:output}" {
Comment "${var:output} Output"
EnableSequence [
cset "name='${var:output} Switch' on"
]
}
}
}
~~~
This creates three SectionDevice blocks for Speaker, Headphones, and LineOut.
*/
/**

View file

@ -90,11 +90,10 @@ static int include_eval_one(snd_use_case_mgr_t *uc_mgr,
err = uc_mgr_get_substituted_value(uc_mgr, &s, file);
if (err < 0)
return err;
if (opt_bool && access(s, R_OK) != 0) {
snd_trace(UCM, "optional file '%s' not found", s);
err = 0;
} else {
err = uc_mgr_config_load_file(uc_mgr, s, result);
if (opt_bool && (err == -ENOENT || err == -EACCES)) {
snd_trace(UCM, "optional file '%s' not found or readable", s);
err = 0;
}
free(s);
return err;

View file

@ -35,7 +35,7 @@
#include <stdbool.h>
#include "use-case.h"
#define SYNTAX_VERSION_MAX 8
#define SYNTAX_VERSION_MAX 9
#define MAX_CARD_SHORT_NAME 32
#define MAX_CARD_LONG_NAME 80
@ -352,7 +352,8 @@ int uc_mgr_add_value(struct list_head *base, const char *key, char *val);
int uc_mgr_check_value(struct list_head *value_list, const char *identifier);
const char *uc_mgr_get_variable(snd_use_case_mgr_t *uc_mgr,
const char *name);
const char *name,
bool show_err);
int uc_mgr_set_variable(snd_use_case_mgr_t *uc_mgr,
const char *name,
@ -384,6 +385,9 @@ int uc_mgr_evaluate_condition(snd_use_case_mgr_t *uc_mgr,
snd_config_t *parent,
snd_config_t *cond);
int uc_mgr_evaluate_repeat(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg);
int uc_mgr_define_regex(snd_use_case_mgr_t *uc_mgr,
const char *name,
snd_config_t *eval);

401
src/ucm/ucm_repeat.c Normal file
View file

@ -0,0 +1,401 @@
/*
* 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
*
* Copyright (C) 2026 Red Hat Inc.
* Authors: Jaroslav Kysela <perex@perex.cz>
*/
#include "ucm_local.h"
/*
* get_string helper
*/
static int get_string(snd_config_t *compound, const char *key, const char **str)
{
snd_config_t *node;
int err;
err = snd_config_search(compound, key, &node);
if (err < 0)
return err;
return snd_config_get_string(node, str);
}
/*
* get_integer helper
*/
static int get_integer(snd_config_t *compound, const char *key, long long *val)
{
snd_config_type_t t;
snd_config_t *node;
const char *str;
int err;
err = snd_config_search(compound, key, &node);
if (err < 0)
return err;
t = snd_config_get_type(node);
if (t == SND_CONFIG_TYPE_INTEGER) {
long i;
err = snd_config_get_integer(node, &i);
if (err >= 0)
*val = i;
} else if (t == SND_CONFIG_TYPE_INTEGER64) {
err = snd_config_get_integer64(node, val);
} else {
err = snd_config_get_string(node, &str);
if (err < 0)
return err;
err = safe_strtoll(str, val);
}
if (err < 0)
return -EINVAL;
return 0;
}
/*
* Repeat pattern iterator
*/
struct repeat_iterator {
const char *var_name;
union {
struct {
long long current;
long long last;
long long step;
int iteration;
char value_buf[32];
} integer;
struct {
snd_config_iterator_t pos;
snd_config_iterator_t end;
snd_config_t *array;
char *value_str;
} array;
} u;
int (*init)(struct repeat_iterator *it, snd_config_t *pattern);
int (*next)(struct repeat_iterator *it, const char **value);
void (*done)(struct repeat_iterator *it);
};
/*
* Integer pattern iterator - initialization
*/
static int repeat_integer_init(struct repeat_iterator *it, snd_config_t *pattern)
{
long long first;
int err;
err = get_integer(pattern, "First", &first);
if (err < 0) {
snd_error(UCM, "Repeat.Pattern.First is required for Integer type");
return -EINVAL;
}
err = get_integer(pattern, "Last", &it->u.integer.last);
if (err < 0) {
snd_error(UCM, "Repeat.Pattern.Last is required for Integer type");
return -EINVAL;
}
err = get_integer(pattern, "Step", &it->u.integer.step);
if (err == -ENOENT) {
it->u.integer.step = 1;
} else if (err < 0) {
snd_error(UCM, "Repeat.Pattern.Step parse error");
return -EINVAL;
}
if (it->u.integer.step == 0) {
snd_error(UCM, "Repeat.Pattern.Step cannot be zero");
return -EINVAL;
}
it->u.integer.current = first;
it->u.integer.iteration = 0;
return 0;
}
/*
* Integer pattern iterator - get next value
* Returns: 1 if value available, 0 if end of iteration, negative on error
*/
static int repeat_integer_next(struct repeat_iterator *it, const char **value)
{
const int max_iterations = 10000;
int has_value;
if (it->u.integer.iteration++ > max_iterations) {
snd_error(UCM, "Repeat iteration limit exceeded");
return -EINVAL;
}
if (it->u.integer.step > 0)
has_value = (it->u.integer.current <= it->u.integer.last);
else
has_value = (it->u.integer.current >= it->u.integer.last);
if (!has_value)
return 0;
snprintf(it->u.integer.value_buf, sizeof(it->u.integer.value_buf), "%lld", it->u.integer.current);
*value = it->u.integer.value_buf;
it->u.integer.current += it->u.integer.step;
return 1;
}
/*
* Array pattern iterator - initialization
*/
static int repeat_array_init(struct repeat_iterator *it, snd_config_t *pattern)
{
int err;
err = snd_config_search(pattern, "Array", &it->u.array.array);
if (err < 0) {
snd_error(UCM, "Repeat.Pattern.Array is required for Array type");
return -EINVAL;
}
if (snd_config_get_type(it->u.array.array) != SND_CONFIG_TYPE_COMPOUND) {
snd_error(UCM, "Repeat.Pattern.Array must be a compound");
return -EINVAL;
}
it->u.array.pos = snd_config_iterator_first(it->u.array.array);
it->u.array.end = snd_config_iterator_end(it->u.array.array);
it->u.array.value_str = NULL;
return 0;
}
/*
* Array pattern iterator - get next value
* Returns: 1 if value available, 0 if end of iteration, negative on error
*/
static int repeat_array_next(struct repeat_iterator *it, const char **value)
{
snd_config_t *n;
int err;
/* Free previous value string */
free(it->u.array.value_str);
it->u.array.value_str = NULL;
if (it->u.array.pos == it->u.array.end)
return 0;
n = snd_config_iterator_entry(it->u.array.pos);
it->u.array.pos = snd_config_iterator_next(it->u.array.pos);
err = snd_config_get_ascii(n, &it->u.array.value_str);
if (err < 0) {
snd_error(UCM, "Repeat.Pattern.Array element conversion error");
return -EINVAL;
}
*value = it->u.array.value_str;
return 1;
}
/*
* Array pattern iterator - cleanup
*/
static void repeat_array_done(struct repeat_iterator *it)
{
free(it->u.array.value_str);
it->u.array.value_str = NULL;
}
/*
* Evaluate repeat pattern using iterator
*/
static int evaluate_repeat_pattern(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg,
snd_config_t *pattern,
snd_config_t *apply,
struct repeat_iterator *it)
{
snd_config_t *apply_copy;
const char *value;
int err, ret;
err = it->init(it, pattern);
if (err < 0)
return err;
while ((ret = it->next(it, &value)) > 0) {
err = uc_mgr_set_variable(uc_mgr, it->var_name, value);
if (err < 0)
goto __error;
err = snd_config_copy(&apply_copy, apply);
if (err < 0)
goto __var_error;
err = uc_mgr_evaluate_inplace(uc_mgr, apply_copy);
if (err < 0)
goto __copy_error;
err = uc_mgr_config_tree_merge(uc_mgr, cfg, apply_copy, NULL, NULL);
snd_config_delete(apply_copy);
if (err < 0)
goto __var_error;
}
if (ret < 0) {
err = ret;
goto __var_error;
}
uc_mgr_delete_variable(uc_mgr, it->var_name);
if (it->done)
it->done(it);
return 0;
__copy_error:
snd_config_delete(apply_copy);
__var_error:
uc_mgr_delete_variable(uc_mgr, it->var_name);
__error:
if (it->done)
it->done(it);
return err;
}
/*
* Evaluate repeat (in-place)
*/
int uc_mgr_evaluate_repeat(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
snd_config_iterator_t i, next;
snd_config_t *repeat_blocks, *n, *pattern = NULL, *pattern_cfg = NULL;
const char *id;
int err;
err = snd_config_search(cfg, "Repeat", &repeat_blocks);
if (err == -ENOENT)
return 1;
if (err < 0)
return err;
if (uc_mgr->conf_format < 9) {
snd_error(UCM, "Repeat is supported in v9+ syntax");
err = -EINVAL;
goto __error;
}
if (snd_config_get_type(repeat_blocks) != SND_CONFIG_TYPE_COMPOUND) {
snd_error(UCM, "Repeat must be a compound");
err = -EINVAL;
goto __error;
}
snd_config_for_each(i, next, repeat_blocks) {
snd_config_t *apply;
struct repeat_iterator it;
const char *var_name, *type_str;
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
err = snd_config_search(n, "Pattern", &pattern);
if (err < 0) {
snd_error(UCM, "Repeat.%s.Pattern is required", id);
goto __error;
}
if (snd_config_get_type(pattern) == SND_CONFIG_TYPE_STRING) {
const char *pattern_str;
char *pattern_subst = NULL;
err = snd_config_get_string(pattern, &pattern_str);
if (err < 0)
goto __error;
err = uc_mgr_get_substituted_value(uc_mgr, &pattern_subst, pattern_str);
if (err < 0)
goto __error;
err = snd_config_load_string(&pattern_cfg, pattern_subst, 0);
free(pattern_subst);
if (err < 0) {
snd_error(UCM, "Repeat.%s.Pattern string parse error", id);
goto __error;
}
} else {
pattern_cfg = pattern;
}
err = get_string(pattern_cfg, "Variable", &var_name);
if (err < 0) {
snd_error(UCM, "Repeat.%s.Pattern.Variable is required", id);
goto __pattern_error;
}
err = get_string(pattern_cfg, "Type", &type_str);
if (err < 0) {
snd_error(UCM, "Repeat.%s.Pattern.Type is required", id);
goto __pattern_error;
}
err = snd_config_search(n, "Apply", &apply);
if (err < 0) {
snd_error(UCM, "Repeat.%s.Apply is required", id);
goto __pattern_error;
}
memset(&it, 0, sizeof(it));
it.var_name = var_name;
if (strcmp(type_str, "Integer") == 0) {
it.init = repeat_integer_init;
it.next = repeat_integer_next;
it.done = NULL;
} else if (strcmp(type_str, "Array") == 0) {
it.init = repeat_array_init;
it.next = repeat_array_next;
it.done = repeat_array_done;
} else {
snd_error(UCM, "Repeat.%s.Pattern.Type must be 'Integer' or 'Array'", id);
err = -EINVAL;
goto __pattern_error;
}
err = evaluate_repeat_pattern(uc_mgr, cfg, pattern_cfg, apply, &it);
if (err < 0)
goto __pattern_error;
if (pattern_cfg != pattern) {
snd_config_delete(pattern_cfg);
pattern_cfg = NULL;
}
}
err = 0;
__pattern_error:
if (pattern_cfg && pattern_cfg != pattern)
snd_config_delete(pattern_cfg);
__error:
snd_config_delete(repeat_blocks);
return err;
}

View file

@ -204,6 +204,92 @@ static char *rval_card_id_by_name(snd_use_case_mgr_t *uc_mgr, const char *id)
return strdup(snd_ctl_card_info_get_id(ctl_list->ctl_info));
}
static char *rval_card_info(snd_use_case_mgr_t *uc_mgr, const char *query)
{
snd_config_t *config, *d;
const char *card_str, *field_str, *tmp;
struct ctl_list *ctl_list = NULL;
snd_ctl_card_info_t *info;
char *result = NULL;
long card_num;
int err;
if (uc_mgr->conf_format < 9) {
snd_error(UCM, "info-card substitution is supported in v9+ syntax");
return NULL;
}
err = snd_config_load_string(&config, query, 0);
if (err < 0) {
snd_error(UCM, "info-card: invalid arguments '%s'", query);
return NULL;
}
if (snd_config_search(config, "card", &d)) {
snd_error(UCM, "info-card: 'card' parameter is required");
goto __error;
}
if (snd_config_get_string(d, &card_str))
goto __error;
if (card_str[0] == '$') {
tmp = card_str + 1;
card_str = uc_mgr_get_variable(uc_mgr, tmp, true);
if (card_str == NULL)
goto __error;
}
if (snd_config_search(config, "field", &d)) {
snd_error(UCM, "info-card: 'field' parameter is required");
goto __error;
}
if (snd_config_get_string(d, &field_str))
goto __error;
if (field_str[0] == '$') {
tmp = field_str + 1;
field_str = uc_mgr_get_variable(uc_mgr, tmp, true);
if (field_str == NULL)
goto __error;
}
if (safe_strtol(card_str, &card_num) == 0)
ctl_list = uc_mgr_get_ctl_by_card(uc_mgr, (int)card_num);
if (ctl_list == NULL)
ctl_list = get_ctl_list_by_name(uc_mgr, card_str);
if (ctl_list == NULL) {
snd_error(UCM, "info-card: card '%s' not found", card_str);
goto __error;
}
info = ctl_list->ctl_info;
if (strcasecmp(field_str, "number") == 0) {
char num[16];
snprintf(num, sizeof(num), "%d", snd_ctl_card_info_get_card(info));
result = strdup(num);
} else if (strcasecmp(field_str, "id") == 0) {
result = strdup(snd_ctl_card_info_get_id(info));
} else if (strcasecmp(field_str, "driver") == 0) {
result = strdup(snd_ctl_card_info_get_driver(info));
} else if (strcasecmp(field_str, "name") == 0) {
result = strdup(snd_ctl_card_info_get_name(info));
} else if (strcasecmp(field_str, "longname") == 0) {
result = strdup(snd_ctl_card_info_get_longname(info));
} else if (strcasecmp(field_str, "mixername") == 0) {
result = strdup(snd_ctl_card_info_get_mixername(info));
} else if (strcasecmp(field_str, "components") == 0) {
result = strdup(snd_ctl_card_info_get_components(info));
} else {
snd_error(UCM, "info-card: unknown field '%s'", field_str);
result = NULL;
}
__error:
snd_config_delete(config);
return result;
}
#ifndef DOC_HIDDEN
typedef struct lookup_iterate *(*lookup_iter_fcn_t)
(snd_use_case_mgr_t *uc_mgr, struct lookup_iterate *iter);
@ -235,7 +321,7 @@ static char *rval_lookup_main(snd_use_case_mgr_t *uc_mgr,
snd_config_t *config, *d;
struct lookup_fcn *fcn;
struct lookup_iterate *curr;
const char *s;
const char *s, *tmp;
char *result;
regmatch_t match[1];
regex_t re;
@ -259,6 +345,12 @@ static char *rval_lookup_main(snd_use_case_mgr_t *uc_mgr,
}
if (snd_config_get_string(d, &s))
goto null;
if (s[0] == '$' && uc_mgr->conf_format >= 9) {
tmp = s + 1;
s = uc_mgr_get_variable(uc_mgr, tmp, true);
if (s == NULL)
goto null;
}
for (fcn = iter->fcns ; fcn; fcn++) {
if (strcasecmp(fcn->name, s) == 0) {
iter->fcn = fcn->fcn;
@ -275,6 +367,12 @@ static char *rval_lookup_main(snd_use_case_mgr_t *uc_mgr,
}
if (snd_config_get_string(d, &s))
goto null;
if (s[0] == '$' && uc_mgr->conf_format >= 9) {
tmp = s + 1;
s = uc_mgr_get_variable(uc_mgr, tmp, true);
if (s == NULL)
goto null;
}
err = regcomp(&re, s, REG_EXTENDED | REG_ICASE);
if (err) {
snd_error(UCM, "Regex '%s' compilation failed (code %d)", s, err);
@ -410,7 +508,8 @@ static char *rval_pcm_lookup_return(struct lookup_iterate *iter,
return strdup(num);
}
static int rval_pcm_lookup_init(struct lookup_iterate *iter,
static int rval_pcm_lookup_init(snd_use_case_mgr_t *uc_mgr,
struct lookup_iterate *iter,
snd_config_t *config)
{
static struct lookup_fcn pcm_fcns[] = {
@ -420,12 +519,18 @@ static int rval_pcm_lookup_init(struct lookup_iterate *iter,
{ 0 },
};
snd_config_t *d;
const char *s;
const char *s, *tmp;
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 (s[0] == '$' && uc_mgr->conf_format >= 9) {
tmp = s + 1;
s = uc_mgr_get_variable(uc_mgr, tmp, true);
if (s == NULL)
return -EINVAL;
}
if (strcasecmp(s, "playback") == 0)
stream = SND_PCM_STREAM_PLAYBACK;
else if (strcasecmp(s, "capture") == 0)
@ -454,13 +559,14 @@ static int rval_device_lookup_init(snd_use_case_mgr_t *uc_mgr,
{
static struct {
const char *name;
int (*init)(struct lookup_iterate *iter, snd_config_t *config);
int (*init)(snd_use_case_mgr_t *uc_mgr, 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;
const char *s, *tmp;
int err;
if (snd_config_search(config, "ctl", &d) || snd_config_get_string(d, &s)) {
@ -480,9 +586,15 @@ static int rval_device_lookup_init(snd_use_case_mgr_t *uc_mgr,
snd_error(UCM, "Missing device type!");
return -EINVAL;
}
if (s[0] == '$' && uc_mgr->conf_format >= 9) {
tmp = s + 1;
s = uc_mgr_get_variable(uc_mgr, tmp, true);
if (s == NULL)
return -EINVAL;
}
for (t = types; t->name; t++)
if (strcasecmp(t->name, s) == 0)
return t->init(iter, config);
return t->init(uc_mgr, iter, config);
snd_error(UCM, "Device type '%s' is invalid", s);
return -EINVAL;
}
@ -726,7 +838,7 @@ static char *rval_var(snd_use_case_mgr_t *uc_mgr, const char *id)
} else if (id[0] == '@') {
ignore_not_found = true;
}
v = uc_mgr_get_variable(uc_mgr, id);
v = uc_mgr_get_variable(uc_mgr, id, false);
if (v == NULL && ignore_not_found)
v = "";
if (v)
@ -742,7 +854,7 @@ static int rval_eval_var_cb(snd_config_t **dst, const char *s, void *private_dat
snd_use_case_mgr_t *uc_mgr = private_data;
const char *v;
v = uc_mgr_get_variable(uc_mgr, s);
v = uc_mgr_get_variable(uc_mgr, s, false);
if (v == NULL)
return -ENOENT;
return snd_config_imake_string(dst, NULL, v);
@ -913,6 +1025,7 @@ __std:
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, "${info-card:", rval_card_info, false);
MATCH_VARIABLE2(value, "${CardNumberByName:", rval_card_number_by_name, false);
MATCH_VARIABLE2(value, "${CardIdByName:", rval_card_id_by_name, false);
__merr:
@ -939,7 +1052,7 @@ __match2:
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);
tmp = uc_mgr_get_variable(uc_mgr, v2 + 1, false);
if (tmp == NULL) {
snd_error(UCM, "define '%s' is not reachable in this context!", v2 + 1);
rval = NULL;

View file

@ -364,6 +364,7 @@ int uc_mgr_config_load_into(int format, const char *file, snd_config_t *top)
const char *default_paths[2];
int err;
snd_trace(UCM, "loading config '%s'", file);
fp = fopen(file, "r");
if (!fp) {
err = -errno;
@ -671,7 +672,7 @@ int uc_mgr_remove_device(struct use_case_verb *verb, const char *name)
return found == 0 ? -ENODEV : 0;
}
const char *uc_mgr_get_variable(snd_use_case_mgr_t *uc_mgr, const char *name)
const char *uc_mgr_get_variable(snd_use_case_mgr_t *uc_mgr, const char *name, bool show_err)
{
struct list_head *pos;
struct ucm_value *value;
@ -681,6 +682,8 @@ const char *uc_mgr_get_variable(snd_use_case_mgr_t *uc_mgr, const char *name)
if (strcmp(value->name, name) == 0)
return value->data;
}
if (show_err)
snd_error(UCM, "variable '%s' is not defined", name);
return NULL;
}