From 7887fbc6f0f660072d1405231703985b72d40cf1 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 26 Jan 2026 15:08:54 +0100 Subject: [PATCH 01/34] ucm: libconfig parser - fix pathname for substituted file 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 --- src/ucm/parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 60861213..baa19144 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -804,7 +804,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); From bc332f4211af98054e7c64aabbe59c7a16ac4e36 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 29 Jan 2026 15:33:45 +0100 Subject: [PATCH 02/34] control: ctlparse - make numid parsing more robust Also correct the last amixer stderr printf to snd_error(). Signed-off-by: Jaroslav Kysela --- src/control/ctlparse.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/control/ctlparse.c b/src/control/ctlparse.c index b23234d7..3bd86435 100644 --- a/src/control/ctlparse.c +++ b/src/control/ctlparse.c @@ -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; From 5f7fe33002d2d98d84f72e381ec2cccc0d5d3d40 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 29 Jan 2026 16:51:09 +0100 Subject: [PATCH 03/34] topology: decoder - add boundary check for channel mixer count Malicious binary topology file may cause heap corruption. CVE: CVE-2026-25068 Signed-off-by: Jaroslav Kysela --- src/topology/ctl.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/topology/ctl.c b/src/topology/ctl.c index a0c24518..322c461c 100644 --- a/src/topology/ctl.c +++ b/src/topology/ctl.c @@ -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; From 166407dae4c91583aff43624ce1eafb8695a4482 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 3 Feb 2026 17:46:02 +0100 Subject: [PATCH 04/34] control: remap - fix numid lookup issue 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 --- src/control/control_remap.c | 79 ++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/src/control/control_remap.c b/src/control/control_remap.c index 7d90d7ad..f7f118aa 100644 --- a/src/control/control_remap.c +++ b/src/control/control_remap.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -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,21 @@ 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) + return -ENOENT; + if (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 +352,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 +490,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 +506,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 +537,40 @@ 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); + if (err < 0) + free(list.pids); + return err; +} + #ifndef DOC_HIDDEN #define ACCESS_BITS(bits) \ (bits & (SNDRV_CTL_ELEM_ACCESS_READWRITE|\ @@ -1674,6 +1732,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) { From ebf2efae0a7a27296ccfd6a94e0fa1a39af7a6e4 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 4 Feb 2026 18:42:56 +0100 Subject: [PATCH 05/34] control: remap - cosmetic code reorganization Signed-off-by: Jaroslav Kysela --- src/control/control_remap.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/control/control_remap.c b/src/control/control_remap.c index f7f118aa..4d666bb4 100644 --- a/src/control/control_remap.c +++ b/src/control/control_remap.c @@ -314,9 +314,7 @@ _retry: if (numid) { id->numid = numid->numid_child; } else { - if (reloaded) - return -ENOENT; - if (priv->list_complete) + if (reloaded || priv->list_complete) return -ENOENT; /* build whole numid mapping */ err = remap_load_list(priv); From 541427761292ac75f2bca2200aacf5aab329729e Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 4 Feb 2026 18:43:57 +0100 Subject: [PATCH 06/34] ucm: fix invalid pointer dereference in parse_open_variables() When the string with variables is not parseable, do not try to free invalid cfg pointer. Signed-off-by: Jaroslav Kysela --- src/ucm/main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index 21a39a34..26b0ea20 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -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,7 +1739,8 @@ const char *parse_open_variables(snd_use_case_mgr_t *uc_mgr, const char *name) } skip: - snd_config_delete(cfg); + if (cfg) + snd_config_delete(cfg); return end + 3; } From e02e9dc6cfb45337829fc0053ec5e064fd8e7383 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 5 Feb 2026 17:33:07 +0100 Subject: [PATCH 07/34] 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 --- src/ucm/parser.c | 24 +++++++++++++++++++++--- src/ucm/ucm_local.h | 2 +- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/ucm/parser.c b/src/ucm/parser.c index baa19144..a7ef1b11 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -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; - err = snd_config_load_string(&a, s, 0); + 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) { diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 8b3da74f..957cb751 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -35,7 +35,7 @@ #include #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 From 2943b1e4120756cc862c33344276bc70037f4df0 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 6 Feb 2026 12:57:02 +0100 Subject: [PATCH 08/34] 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 --- src/ucm/ucm_cond.c | 77 +++++++++++++++++++++++++++++++++++++++++++ src/ucm/ucm_confdoc.h | 26 +++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/src/ucm/ucm_cond.c b/src/ucm/ucm_cond.c index b909c6b2..37118927 100644 --- a/src/ucm/ucm_cond.c +++ b/src/ucm/ucm_cond.c @@ -270,6 +270,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 +439,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; } diff --git a/src/ucm/ucm_confdoc.h b/src/ucm/ucm_confdoc.h index 5fac99e4..0a344b7e 100644 --- a/src/ucm/ucm_confdoc.h +++ b/src/ucm/ucm_confdoc.h @@ -878,6 +878,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 From 27aa3e41ef1b80a87800afd8b2e718d8e67ad54c Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 6 Feb 2026 13:43:11 +0100 Subject: [PATCH 09/34] 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 --- src/ucm/Makefile.am | 2 +- src/ucm/parser.c | 7 +- src/ucm/ucm_confdoc.h | 130 ++++++++++++++ src/ucm/ucm_local.h | 3 + src/ucm/ucm_repeat.c | 401 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 540 insertions(+), 3 deletions(-) create mode 100644 src/ucm/ucm_repeat.c diff --git a/src/ucm/Makefile.am b/src/ucm/Makefile.am index 7108968f..f13ec118 100644 --- a/src/ucm/Makefile.am +++ b/src/ucm/Makefile.am @@ -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 diff --git a/src/ucm/parser.c b/src/ucm/parser.c index a7ef1b11..3212fda0 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -729,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; @@ -768,6 +768,9 @@ int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr, err5 = evaluate_condition(uc_mgr, cfg); if (err5 < 0) return err5; + err6 = uc_mgr_evaluate_repeat(uc_mgr, cfg); + if (err6 < 0) + return err6; } return 0; } diff --git a/src/ucm/ucm_confdoc.h b/src/ucm/ucm_confdoc.h index 0a344b7e..d5bd3484 100644 --- a/src/ucm/ucm_confdoc.h +++ b/src/ucm/ucm_confdoc.h @@ -1012,6 +1012,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. + */ /** diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 957cb751..5ac4d3c4 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -384,6 +384,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); diff --git a/src/ucm/ucm_repeat.c b/src/ucm/ucm_repeat.c new file mode 100644 index 00000000..e3e08e0c --- /dev/null +++ b/src/ucm/ucm_repeat.c @@ -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 + */ + +#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; +} From 30d1ae7221fc4951d15887872650d04c8d7d2d59 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 6 Feb 2026 18:25:01 +0100 Subject: [PATCH 10/34] 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 --- src/ucm/ucm_cond.c | 132 ++++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/src/ucm/ucm_cond.c b/src/ucm/ucm_cond.c index 37118927..a8b85f1f 100644 --- a/src/ucm/ucm_cond.c +++ b/src/ucm/ucm_cond.c @@ -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 = 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) - 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); + 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, "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); + err = compare_strings(uc_mgr, eval, "Haystack", "Needle", string_contains); + if (err != -ENOENT) return err; - } snd_error(UCM, "Unknown String condition arguments"); return -EINVAL; From c41f795f5c35fcf71945f4f1c39889779bff717a Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 6 Feb 2026 18:41:30 +0100 Subject: [PATCH 11/34] 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 --- src/ucm/ucm_cond.c | 54 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/src/ucm/ucm_cond.c b/src/ucm/ucm_cond.c index a8b85f1f..f7147f59 100644 --- a/src/ucm/ucm_cond.c +++ b/src/ucm/ucm_cond.c @@ -452,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; @@ -467,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 From 22225a4ec64bc5c129828a7a647ab91d6cca388e Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 6 Feb 2026 18:59:30 +0100 Subject: [PATCH 12/34] ucm: evaluate Repeat block before If block (Syntax 9) Signed-off-by: Jaroslav Kysela --- src/ucm/parser.c | 4 ++-- src/ucm/ucm_confdoc.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 3212fda0..8d5428c2 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -765,10 +765,10 @@ 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 = uc_mgr_evaluate_repeat(uc_mgr, cfg); + err6 = evaluate_condition(uc_mgr, cfg); if (err6 < 0) return err6; } diff --git a/src/ucm/ucm_confdoc.h b/src/ucm/ucm_confdoc.h index d5bd3484..47a1e182 100644 --- a/src/ucm/ucm_confdoc.h +++ b/src/ucm/ucm_confdoc.h @@ -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 From 1823b4cd4b1469ec506fde5795fc0221144fb3f1 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 12 Feb 2026 11:15:55 +0100 Subject: [PATCH 13/34] control: remap - fix memory leak in remap_load_list() Signed-off-by: Jaroslav Kysela --- src/control/control_remap.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/control/control_remap.c b/src/control/control_remap.c index 4d666bb4..80a0f43b 100644 --- a/src/control/control_remap.c +++ b/src/control/control_remap.c @@ -564,8 +564,7 @@ static int remap_load_list(snd_ctl_remap_t *remap) if (err < 0) break; } while (list.count != list.used); - if (err < 0) - free(list.pids); + snd_ctl_elem_list_free_space(&list); return err; } From c5d903b0b4bcbe693096f124d993ec36a6856f86 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 9 Feb 2026 16:02:01 +0100 Subject: [PATCH 14/34] 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= card number or card ID (string identifier) field= 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 --- src/ucm/ucm_confdoc.h | 34 ++++++++++++++++ src/ucm/ucm_subs.c | 91 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/src/ucm/ucm_confdoc.h b/src/ucm/ucm_confdoc.h index 47a1e182..052f8e20 100644 --- a/src/ucm/ucm_confdoc.h +++ b/src/ucm/ucm_confdoc.h @@ -616,6 +616,7 @@ ${var:\} | UCM parser variable (set using a _Define_ block) ${eval:\} | Evaluate expression like *($var+2)/3* [**Syntax 5**] ${find-card:\} | Find a card - see _Find card substitution_ section ${find-device:\} | Find a device - see _Find device substitution_ section +${info-card:\} | 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 @@ -689,6 +690,39 @@ stream | stream type (playback, capture), playback is default field | field for the lookup (id, name, subname) regex | regex string for the field match +#### 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 diff --git a/src/ucm/ucm_subs.c b/src/ucm/ucm_subs.c index 73230a2a..34782795 100644 --- a/src/ucm/ucm_subs.c +++ b/src/ucm/ucm_subs.c @@ -204,6 +204,96 @@ 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); + if (card_str == NULL) { + snd_error(UCM, "info-card: variable '%s' not found", tmp); + 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); + if (field_str == NULL) { + snd_error(UCM, "info-card: variable '%s' not found", tmp); + 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); @@ -913,6 +1003,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: From a74521f37148796413797147e42d898f1a7d3d3f Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 18 Feb 2026 12:13:15 +0100 Subject: [PATCH 15/34] 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 --- src/ucm/ucm_confdoc.h | 14 ++++++++------ src/ucm/ucm_subs.c | 44 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/ucm/ucm_confdoc.h b/src/ucm/ucm_confdoc.h index 052f8e20..e5d8b82d 100644 --- a/src/ucm/ucm_confdoc.h +++ b/src/ucm/ucm_confdoc.h @@ -663,6 +663,7 @@ Usage example: ~~~{.html} ${find-card:field=name,regex='^acp$',return=number} +${find-card:field=$FieldName,regex=$Pattern,return=number} ~~~ Arguments: @@ -670,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 @@ -679,16 +680,17 @@ 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 diff --git a/src/ucm/ucm_subs.c b/src/ucm/ucm_subs.c index 34782795..5ee386ac 100644 --- a/src/ucm/ucm_subs.c +++ b/src/ucm/ucm_subs.c @@ -325,7 +325,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; @@ -349,6 +349,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); + if (s == NULL) + goto var_not_found; + } for (fcn = iter->fcns ; fcn; fcn++) { if (strcasecmp(fcn->name, s) == 0) { iter->fcn = fcn->fcn; @@ -365,6 +371,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); + if (s == NULL) + goto var_not_found; + } err = regcomp(&re, s, REG_EXTENDED | REG_ICASE); if (err) { snd_error(UCM, "Regex '%s' compilation failed (code %d)", s, err); @@ -387,6 +399,8 @@ fin: if (iter->done) iter->done(iter); return result; +var_not_found: + snd_error(UCM, "lookup: variable '%s' not found", tmp); null: result = NULL; goto fin; @@ -500,7 +514,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[] = { @@ -510,12 +525,20 @@ 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); + if (s == NULL) { + snd_error(UCM, "pcm lookup: variable '%s' not found", tmp); + return -EINVAL; + } + } if (strcasecmp(s, "playback") == 0) stream = SND_PCM_STREAM_PLAYBACK; else if (strcasecmp(s, "capture") == 0) @@ -544,13 +567,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)) { @@ -570,9 +594,17 @@ 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); + if (s == NULL) { + snd_error(UCM, "device lookup: variable '%s' not found", tmp); + 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; } From d8ca4a7cc42501b41dd32cbb1318af7a655c936b Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 18 Feb 2026 13:23:26 +0100 Subject: [PATCH 16/34] 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 --- src/ucm/main.c | 2 +- src/ucm/parser.c | 2 +- src/ucm/ucm_local.h | 3 ++- src/ucm/ucm_subs.c | 40 +++++++++++++++------------------------- src/ucm/utils.c | 4 +++- 5 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index 26b0ea20..72315d28 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -1781,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); diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 8d5428c2..2d7833e9 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -527,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; } diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 5ac4d3c4..3015cca5 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -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, diff --git a/src/ucm/ucm_subs.c b/src/ucm/ucm_subs.c index 5ee386ac..eb46ffcf 100644 --- a/src/ucm/ucm_subs.c +++ b/src/ucm/ucm_subs.c @@ -234,11 +234,9 @@ static char *rval_card_info(snd_use_case_mgr_t *uc_mgr, const char *query) if (card_str[0] == '$') { tmp = card_str + 1; - card_str = uc_mgr_get_variable(uc_mgr, tmp); - if (card_str == NULL) { - snd_error(UCM, "info-card: variable '%s' not found", tmp); + card_str = uc_mgr_get_variable(uc_mgr, tmp, true); + if (card_str == NULL) goto __error; - } } if (snd_config_search(config, "field", &d)) { @@ -250,11 +248,9 @@ static char *rval_card_info(snd_use_case_mgr_t *uc_mgr, const char *query) if (field_str[0] == '$') { tmp = field_str + 1; - field_str = uc_mgr_get_variable(uc_mgr, tmp); - if (field_str == NULL) { - snd_error(UCM, "info-card: variable '%s' not found", tmp); + field_str = uc_mgr_get_variable(uc_mgr, tmp, true); + if (field_str == NULL) goto __error; - } } if (safe_strtol(card_str, &card_num) == 0) @@ -351,9 +347,9 @@ static char *rval_lookup_main(snd_use_case_mgr_t *uc_mgr, goto null; if (s[0] == '$' && uc_mgr->conf_format >= 9) { tmp = s + 1; - s = uc_mgr_get_variable(uc_mgr, tmp); + s = uc_mgr_get_variable(uc_mgr, tmp, true); if (s == NULL) - goto var_not_found; + goto null; } for (fcn = iter->fcns ; fcn; fcn++) { if (strcasecmp(fcn->name, s) == 0) { @@ -373,9 +369,9 @@ static char *rval_lookup_main(snd_use_case_mgr_t *uc_mgr, goto null; if (s[0] == '$' && uc_mgr->conf_format >= 9) { tmp = s + 1; - s = uc_mgr_get_variable(uc_mgr, tmp); + s = uc_mgr_get_variable(uc_mgr, tmp, true); if (s == NULL) - goto var_not_found; + goto null; } err = regcomp(&re, s, REG_EXTENDED | REG_ICASE); if (err) { @@ -399,8 +395,6 @@ fin: if (iter->done) iter->done(iter); return result; -var_not_found: - snd_error(UCM, "lookup: variable '%s' not found", tmp); null: result = NULL; goto fin; @@ -533,11 +527,9 @@ static int rval_pcm_lookup_init(snd_use_case_mgr_t *uc_mgr, 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); - if (s == NULL) { - snd_error(UCM, "pcm lookup: variable '%s' not found", tmp); + s = uc_mgr_get_variable(uc_mgr, tmp, true); + if (s == NULL) return -EINVAL; - } } if (strcasecmp(s, "playback") == 0) stream = SND_PCM_STREAM_PLAYBACK; @@ -596,11 +588,9 @@ static int rval_device_lookup_init(snd_use_case_mgr_t *uc_mgr, } if (s[0] == '$' && uc_mgr->conf_format >= 9) { tmp = s + 1; - s = uc_mgr_get_variable(uc_mgr, tmp); - if (s == NULL) { - snd_error(UCM, "device lookup: variable '%s' not found", tmp); + 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) @@ -848,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) @@ -864,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); @@ -1062,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; diff --git a/src/ucm/utils.c b/src/ucm/utils.c index dcb5c6e1..eb6bb4ab 100644 --- a/src/ucm/utils.c +++ b/src/ucm/utils.c @@ -672,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; @@ -682,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; } From 62c24074ae2a39613b27028cde0bac2a20635ea1 Mon Sep 17 00:00:00 2001 From: Huang Yunxuan Date: Wed, 18 Mar 2026 22:36:24 +0800 Subject: [PATCH 17/34] ucm: fix optional include 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 Signed-off-by: Jaroslav Kysela --- src/ucm/ucm_include.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ucm/ucm_include.c b/src/ucm/ucm_include.c index b155a086..8a47e748 100644 --- a/src/ucm/ucm_include.c +++ b/src/ucm/ucm_include.c @@ -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 = 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; - } else { - err = uc_mgr_config_load_file(uc_mgr, s, result); } free(s); return err; From 96f23dda144e1f2b3f192022edc191af3c389243 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 1 May 2026 07:39:58 +0200 Subject: [PATCH 18/34] Revert "snd_tlv_convert_to_dB: Fix mute handling for MINMAX_MUTE type" This reverts commit 41039b49ad3e20506134864028c77aeaba4c0d51. SND_CTL_TLVT_DB_MINMAX_MUTE means that the hardware is muted on lowest hardware volume value (not on lowest dB value). Analysis: Range: min=0, max=7248 dB: min=-2837, max=-6 dB span: 2831 centibels (28.31 dB) Volume | After revert/origin | PR#478 behavior -------|---------------------|---------------------- 0 | MUTE | MUTE 1 | -2837 (-28.37 dB) | MUTE <- DIFFERS! 2 | -2837 (-28.37 dB) | MUTE <- DIFFERS! 5 | -2836 (-28.36 dB) | -2836 (-28.36 dB) 10 | -2834 (-28.34 dB) | -2834 (-28.34 dB) 50 | -2818 (-28.18 dB) | -2818 (-28.18 dB) Range: min=0, max=999 dB: min=0, max=390 dB span: 390 centibels (3.90 dB) Volume | After revert/origin | PR#478 behavior -------|---------------------|---------------------- 0 | MUTE | MUTE 1 | 0 (0.00 dB) | MUTE <- DIFFERS! 2 | 0 (0.00 dB) | MUTE <- DIFFERS! 5 | 1 (0.01 dB) | 1 (0.01 dB) 10 | 3 (0.03 dB) | 3 (0.03 dB) 50 | 19 (0.19 dB) | 19 (0.19 dB) Fixes: https://github.com/alsa-project/alsa-lib/issues/504 Link: https://github.com/alsa-project/alsa-lib/pull/478 Signed-off-by: Jaroslav Kysela --- src/control/tlv.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/control/tlv.c b/src/control/tlv.c index 271da984..cc60703d 100644 --- a/src/control/tlv.c +++ b/src/control/tlv.c @@ -246,17 +246,16 @@ int snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax, int mindb, maxdb; mindb = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN]; maxdb = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX]; - if (rangemax <= rangemin) - *db_gain = mindb; + if (volume <= rangemin || rangemax <= rangemin) { + if (type == SND_CTL_TLVT_DB_MINMAX_MUTE) + *db_gain = SND_CTL_TLV_DB_GAIN_MUTE; + else + *db_gain = mindb; + } else if (volume >= rangemax) + *db_gain = maxdb; else *db_gain = (maxdb - mindb) * (volume - rangemin) / (rangemax - rangemin) + mindb; - if (*db_gain < mindb) - *db_gain = mindb; - if (*db_gain > maxdb) - *db_gain = maxdb; - if (type == SND_CTL_TLVT_DB_MINMAX_MUTE && *db_gain == mindb) - *db_gain = SND_CTL_TLV_DB_GAIN_MUTE; return 0; } #ifndef HAVE_SOFT_FLOAT From 3592e5c7812bfddad932f176e257e8797d71a51b Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 4 May 2026 12:51:49 +0200 Subject: [PATCH 19/34] ucm: add DefineRegex 'all' scheme for multiple pattern matches (Syntax 9) Extend DefineRegex to support two matching schemes: - "first" (default): matches pattern once, extracts capture groups - "all": matches pattern multiple times, extracts all matches The "all" scheme creates variables with naming pattern nameN for Nth match and nameN_M for Nth match's Mth capture group, enabling extraction of repeating patterns from strings. Updated documentation with detailed examples and comprehensive description of regex flags, schemes, and variable naming conventions. Signed-off-by: Jaroslav Kysela --- src/ucm/ucm_confdoc.h | 130 +++++++++++++++++++++++++++++++++++++++--- src/ucm/ucm_regex.c | 103 ++++++++++++++++++++++++--------- 2 files changed, 198 insertions(+), 35 deletions(-) diff --git a/src/ucm/ucm_confdoc.h b/src/ucm/ucm_confdoc.h index e5d8b82d..7f507644 100644 --- a/src/ucm/ucm_confdoc.h +++ b/src/ucm/ucm_confdoc.h @@ -738,19 +738,135 @@ Define { } ~~~ -The *DefineRegex* allows substring extraction like: +The *DefineRegex* allows substring extraction using regular expressions (POSIX extended regex). +It can match patterns in strings and extract matched substrings into UCM variables. + +#### DefineRegex Structure ~~~{.html} -DefineRegex.rval { - Regex "(hello)|(regex)" - String "hello, it's my regex" +DefineRegex.name { + String "text to match against" + Regex "regex_pattern" + Flags "e" + Scheme "first" } ~~~ -The result will be stored to variables *rval1* as *hello* and *rval2* as *regex* (every matched -substrings are stored to a separate variable with the sequence number postfix. +Field | Description +---------------------|--------------------- +String | The input string to match the regex pattern against +Regex | POSIX extended regular expression pattern +Flags | Optional regex flags (see below) +Scheme | Matching scheme: "first" (default) or "all" [**Syntax 9**] -Variables can be substituted using the `${var:rval1}` reference for example. +#### Regex Flags + +The Flags field is optional and accepts the following characters: + +Flag | Description +-------|--------------------- +e | Extended POSIX regex (REG_EXTENDED) - default recommended +i | Case-insensitive matching (REG_ICASE) +s | Report only success/fail (REG_NOSUB) +n | Newline-sensitive matching (REG_NEWLINE) + +Multiple flags can be combined, e.g., "ei" for extended and case-insensitive. + +#### Matching Schemes + +**Scheme "first"** (default): Matches the pattern once and extracts capture groups + +The variables created are: +- `name` - the full matched string +- `name1` - first capture group (parentheses in regex) +- `name2` - second capture group +- `nameN` - Nth capture group + +Example with "first" scheme: + +~~~{.html} +DefineRegex.hwdev { + String "hw:2,0" + Regex "hw:([0-9]+),([0-9]+)" + Flags "e" + Scheme "first" +} +~~~ + +This creates variables: +- `hwdev` = "hw:2,0" (full match) +- `hwdev1` = "2" (first capture group - card number) +- `hwdev2` = "0" (second capture group - device number) + +**Scheme "all"** [**Syntax 9**]: Matches the pattern multiple times and extracts all matches + +The variables created are: +- `nameN` - Nth full match (N starts at 1) +- `nameN_1` - Nth match, first capture group +- `nameN_2` - Nth match, second capture group +- `nameN_M` - Nth match, Mth capture group + +Example with "all" scheme: + +~~~{.html} +DefineRegex.devices { + String "device1 device2 device3" + Regex "device([0-9]+)" + Flags "e" + Scheme "all" +} +~~~ + +This creates variables: +- `devices1` = "device1" (first full match) +- `devices1_1` = "1" (first match, capture group 1) +- `devices2` = "device2" (second full match) +- `devices2_1` = "2" (second match, capture group 1) +- `devices3` = "device3" (third full match) +- `devices3_1` = "3" (third match, capture group 1) + +#### Practical Examples + +Extract USB device vendor and product IDs: + +~~~{.html} +DefineRegex.usbids { + String "${sys:bus/usb/devices/1-1/uevent}" + Regex "PRODUCT=([0-9a-f]+)/([0-9a-f]+)" + Flags "e" + Scheme "first" +} +# Creates: usbids (full match), usbids1 (vendor), usbids2 (product) +~~~ + +Parse multiple key=value pairs: + +~~~{.html} +DefineRegex.params { + String "rate=48000,channels=2,format=S16_LE" + Regex "([a-z]+)=([^,]+)" + Flags "e" + Scheme "all" +} +# Creates: params1="rate=48000", params1_1="rate", params1_2="48000" +# params2="channels=2", params2_1="channels", params2_2="2" +# params3="format=S16_LE", params3_1="format", params3_2="S16_LE" +~~~ + +Extract text components: + +~~~{.html} +DefineRegex.model { + String "USB Audio Device Model XYZ123" + Regex "([A-Z]+).*Model ([A-Z0-9]+)" + Flags "e" + Scheme "first" +} +# Creates: model (full match), model1="USB", model2="XYZ123" +~~~ + +Variables can be substituted using `${var:name}` reference. For example, to use the extracted +card number: `PlaybackPCM "hw:${var:hwdev1},0"` ### Macros diff --git a/src/ucm/ucm_regex.c b/src/ucm/ucm_regex.c index 8c257a0f..db5d9463 100644 --- a/src/ucm/ucm_regex.c +++ b/src/ucm/ucm_regex.c @@ -54,47 +54,78 @@ static char *extract_substring(const char *data, regmatch_t *match) } static int set_variables(snd_use_case_mgr_t *uc_mgr, const char *data, - unsigned int match_size, regmatch_t *match, - const char *name) + regex_t *re, const char *name, int scheme_all) { - size_t name2_len = strlen(name) + 16; - char *name2 = alloca(name2_len); + size_t name_len = strlen(name) + 32; + char *var_name = alloca(name_len); + regmatch_t match[20]; char *s; + unsigned int match_idx = 1; unsigned int i; int err; + const char *pos; - if (match[0].rm_so < 0 || match[0].rm_eo < 0) - return 0; - s = extract_substring(data, &match[0]); - if (s == NULL) - return -ENOMEM; - err = uc_mgr_set_variable(uc_mgr, name, s); - free(s); - if (err < 0) - return err; - for (i = 1; i < match_size; i++) { - if (match[i].rm_so < 0 || match[i].rm_eo < 0) - return 0; - s = extract_substring(data, &match[i]); + pos = data; + while (1) { + err = regexec(re, pos, ARRAY_SIZE(match), match, 0); + if (err == REG_NOMATCH) + break; + if (err != 0) + return -EINVAL; + + if (match[0].rm_so < 0 || match[0].rm_eo < 0) + break; + + s = extract_substring(pos, &match[0]); if (s == NULL) return -ENOMEM; - snprintf(name2, name2_len, "%s%u", name, i); - err = uc_mgr_set_variable(uc_mgr, name2, s); + if (scheme_all) { + snprintf(var_name, name_len, "%s%u", name, match_idx); + err = uc_mgr_set_variable(uc_mgr, var_name, s); + } else { + err = uc_mgr_set_variable(uc_mgr, name, s); + } free(s); if (err < 0) return err; + + for (i = 1; i < ARRAY_SIZE(match); i++) { + if (match[i].rm_so < 0 || match[i].rm_eo < 0) + break; + s = extract_substring(pos, &match[i]); + if (s == NULL) + return -ENOMEM; + if (scheme_all) + snprintf(var_name, name_len, "%s%u_%u", name, match_idx, i); + else + snprintf(var_name, name_len, "%s%u", name, i); + err = uc_mgr_set_variable(uc_mgr, var_name, s); + free(s); + if (err < 0) + return err; + } + + if (!scheme_all) + break; + + pos += match[0].rm_eo; + match_idx++; + + if (*pos == '\0') + break; } + return 0; } int uc_mgr_define_regex(snd_use_case_mgr_t *uc_mgr, const char *name, snd_config_t *eval) { - const char *string, *regex_string, *flags_string; + const char *string, *regex_string, *flags_string, *scheme_string; char *s; regex_t re; int options = 0; - regmatch_t match[20]; + int use_scheme_all = 0; int err; if (uc_mgr->conf_format < 3) { @@ -119,6 +150,27 @@ int uc_mgr_define_regex(snd_use_case_mgr_t *uc_mgr, const char *name, return -EINVAL; } + err = get_string(eval, "Scheme", &scheme_string); + if (err == -ENOENT) { + use_scheme_all = 0; + } else if (err < 0) { + snd_error(UCM, "DefineRegex error (Scheme string)"); + return -EINVAL; + } else { + if (strcmp(scheme_string, "first") == 0) { + use_scheme_all = 0; + } else if (strcmp(scheme_string, "all") == 0) { + if (uc_mgr->conf_format < 9) { + snd_error(UCM, "DefineRegex 'all' scheme is supported in v9+ syntax"); + return -EINVAL; + } + use_scheme_all = 1; + } else { + snd_error(UCM, "DefineRegex error (unknown scheme '%s')", scheme_string); + return -EINVAL; + } + } + err = get_string(eval, "Flags", &flags_string); if (err == -ENOENT) { options = REG_EXTENDED; @@ -164,13 +216,8 @@ int uc_mgr_define_regex(snd_use_case_mgr_t *uc_mgr, const char *name, regfree(&re); return err; } - err = regexec(&re, s, ARRAY_SIZE(match), match, 0); - if (err < 0) - err = -errno; - else if (err == REG_NOMATCH) - err = 0; - else - err = set_variables(uc_mgr, s, ARRAY_SIZE(match), match, name); + + err = set_variables(uc_mgr, s, &re, name, use_scheme_all); free(s); regfree(&re); return err; From eebca4c0b0a7c2561255aa54b1e66b2e695b7c2b Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 23 Mar 2026 17:10:33 +0200 Subject: [PATCH 20/34] topology: decoder: fix wrong sizeof for enum control allocation in dapm The tplg_calloc() call for enum control in the dapm widget kcontrol decode loop used sizeof(*mt) (mixer template) instead of sizeof(*et) (enum template). On 64-bit systems, snd_tplg_mixer_template is 72 bytes while snd_tplg_enum_template is 80 bytes, causing an 8-byte heap buffer overflow when the enum fields (texts, values pointers) were written past the allocated block. This resulted in heap corruption and e.g. glibc malloc hit an assert. Closes: https://github.com/alsa-project/alsa-lib/pull/501 Signed-off-by: Kai Vehmanen Signed-off-by: Jaroslav Kysela --- src/topology/dapm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topology/dapm.c b/src/topology/dapm.c index d261b15b..04a57ddd 100644 --- a/src/topology/dapm.c +++ b/src/topology/dapm.c @@ -983,7 +983,7 @@ next: bin, size2); break; case SND_SOC_TPLG_TYPE_ENUM: - et = tplg_calloc(&heap, sizeof(*mt)); + et = tplg_calloc(&heap, sizeof(*et)); if (et == NULL) { err = -ENOMEM; goto retval; From 03494abce2d9c7b09b2550d22cda5b9f5b0f64e6 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Wed, 4 Mar 2026 13:10:09 +0200 Subject: [PATCH 21/34] Topology: Add high and extended rates from Linux 6.12 This patch adds to topologies build support for the very high sample rates 352.8 kHz, 384 kHz, 705,6 kHz, and 768 kHz. The added extended rates those were introduced in Linux kernel version 6.12 are 12 kHz, 24 kHz, and 128 kHz. Closes: https://github.com/alsa-project/alsa-lib/pull/497 Signed-off-by: Seppo Ingalsuo Signed-off-by: Jaroslav Kysela --- src/topology/pcm.c | 7 +++++++ src/topology/tplg_local.h | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/topology/pcm.c b/src/topology/pcm.c index 3cfc631e..f4539dff 100644 --- a/src/topology/pcm.c +++ b/src/topology/pcm.c @@ -25,16 +25,23 @@ static const char *const snd_pcm_rate_names[] = { RATE(5512), RATE(8000), RATE(11025), + RATE(12000), RATE(16000), RATE(22050), + RATE(24000), RATE(32000), RATE(44100), RATE(48000), RATE(64000), RATE(88200), RATE(96000), + RATE(128000), RATE(176400), RATE(192000), + RATE(352800), + RATE(384000), + RATE(705600), + RATE(768000), RATE(CONTINUOUS), RATE(KNOT), }; diff --git a/src/topology/tplg_local.h b/src/topology/tplg_local.h index 63639274..b1dc33f2 100644 --- a/src/topology/tplg_local.h +++ b/src/topology/tplg_local.h @@ -55,6 +55,13 @@ typedef enum _snd_pcm_rates { SND_PCM_RATE_96000, SND_PCM_RATE_176400, SND_PCM_RATE_192000, + SND_PCM_RATE_352800, + SND_PCM_RATE_384000, + SND_PCM_RATE_705600, + SND_PCM_RATE_768000, + SND_PCM_RATE_12000, + SND_PCM_RATE_24000, + SND_PCM_RATE_128000, SND_PCM_RATE_CONTINUOUS = 30, SND_PCM_RATE_KNOT = 31, SND_PCM_RATE_LAST = SND_PCM_RATE_KNOT, From 6f074eefb117fc935bc8d6f26b7ae0223f971e21 Mon Sep 17 00:00:00 2001 From: Rudi Heitbaum Date: Fri, 20 Feb 2026 11:31:02 +0000 Subject: [PATCH 22/34] conf, seq, ucm: fix discards const from pointer target 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 Closes: https://github.com/alsa-project/alsa-lib/pull/496 Signed-off-by: Rudi Heitbaum Signed-off-by: Jaroslav Kysela --- src/conf.c | 2 +- src/seq/seqmid.c | 4 ++-- src/ucm/main.c | 9 ++++++--- src/ucm/ucm_subs.c | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/conf.c b/src/conf.c index fb9f0658..b0dd6298 100644 --- a/src/conf.c +++ b/src/conf.c @@ -4443,7 +4443,7 @@ int snd_config_hook_load_for_all_cards(snd_config_t *root, snd_config_t *config, goto __err; } while (1) { - char *s = strchr(driver, '.'); + const char *s = strchr(driver, '.'); if (s == NULL) break; driver = s + 1; diff --git a/src/seq/seqmid.c b/src/seq/seqmid.c index 280a27bd..2bf6afa0 100644 --- a/src/seq/seqmid.c +++ b/src/seq/seqmid.c @@ -424,8 +424,8 @@ int snd_seq_sync_output_queue(snd_seq_t *seq) */ int snd_seq_parse_address(snd_seq_t *seq, snd_seq_addr_t *addr, const char *arg) { - char *p, *buf; - const char *s; + char *buf; + const char *p, *s; char c; long client, port = 0; int len; diff --git a/src/ucm/main.c b/src/ucm/main.c index 72315d28..d2b3c2d9 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -2379,7 +2379,8 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, const char *identifier, const char **list[]) { - char *str, *str1; + char *str; + const char *str1; int err, i; if (uc_mgr == NULL || identifier == NULL) { @@ -2712,7 +2713,8 @@ int snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, const char *identifier, long *value) { - char *str, *str1; + char *str; + const char *str1; int err; pthread_mutex_lock(&uc_mgr->mutex); @@ -3014,7 +3016,8 @@ int snd_use_case_set(snd_use_case_mgr_t *uc_mgr, const char *identifier, const char *value) { - char *str, *str1; + char *str; + const char *str1; int err = 0; snd_trace(UCM, "{API call} set '%s'='%s'", identifier, value); diff --git a/src/ucm/ucm_subs.c b/src/ucm/ucm_subs.c index eb46ffcf..c8b13648 100644 --- a/src/ucm/ucm_subs.c +++ b/src/ucm/ucm_subs.c @@ -938,7 +938,7 @@ static int rval_evali(snd_use_case_mgr_t *uc_mgr, snd_config_t *node, const char */ static inline const char *strchr_with_escape(const char *str, char c) { - char *s; + const char *s; while (1) { s = strchr(str, c); From 33c9949e75593c09a5e08fa3f867a0734c8d5ca3 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 25 May 2026 12:34:37 +0200 Subject: [PATCH 23/34] pcm: snd_pcm_slave_conf - fix C99 variable-length array allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The C standard (C99 ยง6.7.5.2p1) requires VLA bounds to be greater than zero; a bound of 0 is undefined behavior. Reported by UBSan. Fixes: https://github.com/alsa-project/alsa-lib/issues/505 Signed-off-by: Jaroslav Kysela --- src/pcm/pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pcm/pcm.c b/src/pcm/pcm.c index 11b15100..f2831ed7 100644 --- a/src/pcm/pcm.c +++ b/src/pcm/pcm.c @@ -7824,7 +7824,7 @@ int snd_pcm_slave_conf(snd_config_t *root, snd_config_t *conf, int flags; void *ptr; int present; - } fields[count]; + } fields[count > 0 ? count : 1]; unsigned int k; snd_config_t *pcm_conf = NULL; int err; From ef115c4e59a59547b87eaf15f0c2c84820dfcb90 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 27 May 2026 16:06:51 +0200 Subject: [PATCH 24/34] github: add pr-validation and label-automation workflows https://github.com/alsa-project/github-workflows Signed-off-by: Jaroslav Kysela --- .github/workflows/label-automation.yml | 29 +++++++++++++++++++++ .github/workflows/pr-validation.yml | 35 ++++++++++++++++++++++++++ .gitignore | 1 - 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/label-automation.yml create mode 100644 .github/workflows/pr-validation.yml diff --git a/.github/workflows/label-automation.yml b/.github/workflows/label-automation.yml new file mode 100644 index 00000000..092e75ce --- /dev/null +++ b/.github/workflows/label-automation.yml @@ -0,0 +1,29 @@ +# Example workflow for using the label commenter +# Place this file in your repository at: .github/workflows/label-automation.yml +# +# You also need to create .github/label-descriptions.yml in your repository +# Example config: https://github.com/alsa-project/github-workflows/blob/main/config/label-descriptions.yml + +name: Label Automation + +on: + issues: + types: [labeled, unlabeled] + pull_request: + types: [labeled, unlabeled] + pull_request_target: + types: [labeled, unlabeled] + +jobs: + handle-label: + uses: alsa-project/github-workflows/.github/workflows/label-commenter.yml@main + permissions: + contents: read + pull-requests: write + issues: write + with: + # Path to your local config file OR URL to remote config (required) + config-path: 'https://raw.githubusercontent.com/alsa-project/github-workflows/main/config/label-descriptions.yml' + # Local file example: '.github/label-descriptions.yml' + # Remote URL example: 'https://raw.githubusercontent.com/alsa-project/github-workflows/main/config/label-descriptions.yml' + secrets: inherit diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 00000000..79835cf7 --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -0,0 +1,35 @@ +# Example workflow for automatic Signed-off-by validation +# Place this file in your repository at: .github/workflows/pr-validation.yml +# +# You also need to create .github/label-descriptions.yml in your repository +# Example config: https://github.com/alsa-project/github-workflows/blob/main/config/label-descriptions.yml + +name: PR Validation + +on: + pull_request: + types: [opened, synchronize, reopened] + pull_request_target: + types: [opened, synchronize, reopened] + workflow_dispatch: + inputs: + pr_number: + description: 'Pull Request number to validate' + required: true + type: number + +jobs: + validate-commits: + uses: alsa-project/github-workflows/.github/workflows/sob-validator.yml@main + permissions: + contents: read + pull-requests: write + issues: write + with: + # Path to your local config file OR URL to remote config (required) + config-path: 'https://raw.githubusercontent.com/alsa-project/github-workflows/main/config/label-descriptions.yml' + # Local file example: '.github/label-descriptions.yml' + # Remote URL example: 'https://raw.githubusercontent.com/alsa-project/github-workflows/main/config/label-descriptions.yml' + sob-label: 'signed off by' + pr-number: ${{ github.event_name == 'workflow_dispatch' && format('{0}', inputs.pr_number) || '' }} + secrets: inherit diff --git a/.gitignore b/.gitignore index 80d3f8b7..58ff80ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.* !.travis.yml configure config.log From 58e4aa3e33317bb8c109f46b080d321dc7e9b9b5 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 28 May 2026 11:32:25 +0200 Subject: [PATCH 25/34] pcm: use new logging for hw_params dump, add pcm_params log interface This change was omitted. Signed-off-by: Jaroslav Kysela --- include/error.h | 3 ++- src/error.c | 1 + src/pcm/pcm_params.c | 35 ++++++++++++++++++++--------------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/include/error.h b/include/error.h index 042cf157..a525845c 100644 --- a/include/error.h +++ b/include/error.h @@ -68,7 +68,8 @@ const char *snd_strerror(int errnum); #define SND_ILOG_UCM 10 /**< UCM API */ #define SND_ILOG_TOPOLOGY 11 /**< topology API */ #define SND_ILOG_ASERVER 12 /**< aserver */ -#define SND_ILOG_LAST SND_ILOG_ASERVER /**< last known value for interface */ +#define SND_ILOG_PCM_PARAMS 13 /**< PCM hw_params operations */ +#define SND_ILOG_LAST SND_ILOG_PCM_PARAMS /**< last known value for interface */ /** * \brief Log handler callback. diff --git a/src/error.c b/src/error.c index df8ba522..ad874202 100644 --- a/src/error.c +++ b/src/error.c @@ -114,6 +114,7 @@ static const char *snd_ilog_interface_names[SND_ILOG_LAST + 1] = { [SND_ILOG_UCM] = "ucm", [SND_ILOG_TOPOLOGY] = "topology", [SND_ILOG_ASERVER] = "aserver", + [SND_ILOG_PCM_PARAMS] = "pcmpar", }; /** diff --git a/src/pcm/pcm_params.c b/src/pcm/pcm_params.c index 0536252e..e40c87df 100644 --- a/src/pcm/pcm_params.c +++ b/src/pcm/pcm_params.c @@ -22,37 +22,42 @@ #include "pcm_local.h" #ifndef NDEBUG -/* - * dump hw_params when $LIBASOUND_DEBUG is set to >= 1 - */ static void dump_hw_params(snd_pcm_hw_params_t *params, const char *type, snd_pcm_hw_param_t var, unsigned int val, int err) { - const char *verbose = getenv("LIBASOUND_DEBUG"); snd_output_t *out; + const char *s; + char *p, *buf; - if (! verbose || ! *verbose || atoi(verbose) < 1) + if (!snd_lib_log_filter(SND_LOG_DEBUG, SND_ILOG_PCM_PARAMS, NULL)) return; - if (snd_output_stdio_attach(&out, stderr, 0) < 0) - return; - fprintf(stderr, "ALSA ERROR hw_params: %s (%s)\n", - type, snd_pcm_hw_param_name(var)); - fprintf(stderr, " value = "); switch (var) { case SND_PCM_HW_PARAM_ACCESS: - fprintf(stderr, "%s", snd_pcm_access_name(val)); + s = snd_pcm_access_name(val); break; case SND_PCM_HW_PARAM_FORMAT: - fprintf(stderr, "%s", snd_pcm_format_name(val)); + s = snd_pcm_format_name(val); break; case SND_PCM_HW_PARAM_SUBFORMAT: - fprintf(stderr, "%s", snd_pcm_subformat_name(val)); + s = snd_pcm_subformat_name(val); break; default: - fprintf(stderr, "%u", val); + s = NULL; } - fprintf(stderr, " : %s\n", snd_strerror(err)); + if (snd_output_buffer_open(&out) < 0) + return; snd_pcm_hw_params_dump(params, out); + snd_output_putc(out, '\0'); + snd_output_buffer_string(out, &buf); + for (p = buf; *p; p++) + if (*p == '\n') + *p = '|'; + if (s) + snd_debug(PCM_PARAMS, "hw_params: %s (%s), value = %s : %s {%s}", + type, snd_pcm_hw_param_name(var), s, snd_strerror(err), buf); + else + snd_debug(PCM_PARAMS, "hw_params: %s (%s), value = %u : %s {%s}", + type, snd_pcm_hw_param_name(var), val, snd_strerror(err), buf); snd_output_close(out); } #else From 3c060c978ad3b1ac9c996a4eccc724dc661d859b Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 28 May 2026 11:37:05 +0200 Subject: [PATCH 26/34] pcm: fix leading space in snd_pcm_hw_param_dump for mask parameters Signed-off-by: Jaroslav Kysela --- src/pcm/pcm_params.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pcm/pcm_params.c b/src/pcm/pcm_params.c index e40c87df..a10555d0 100644 --- a/src/pcm/pcm_params.c +++ b/src/pcm/pcm_params.c @@ -1225,11 +1225,12 @@ void snd_pcm_hw_param_dump(const snd_pcm_hw_params_t *params, if (hw_is_mask(var)) { const snd_mask_t *mask = hw_param_mask_c(params, var); if (snd_mask_empty(mask)) - snd_output_puts(out, " NONE"); + snd_output_puts(out, "NONE"); else if (snd_mask_full(mask)) - snd_output_puts(out, " ALL"); + snd_output_puts(out, "ALL"); else { unsigned int k; + int sep = 0; for (k = 0; k <= SND_MASK_MAX; ++k) { if (snd_mask_test(mask, k)) { const char *s; @@ -1248,8 +1249,10 @@ void snd_pcm_hw_param_dump(const snd_pcm_hw_params_t *params, s = NULL; } if (s) { - snd_output_putc(out, ' '); + if (sep) + snd_output_putc(out, ' '); snd_output_puts(out, s); + sep = 1; } } } From d2499b16c624ab48ab33c6c49547ca0d62a8041a Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 28 May 2026 12:04:46 +0200 Subject: [PATCH 27/34] error: use SND_LOG_LAST/SND_ILOG_LAST in log priority/interface bounds checks Signed-off-by: Jaroslav Kysela --- src/error.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/error.c b/src/error.c index ad874202..e721bdc0 100644 --- a/src/error.c +++ b/src/error.c @@ -124,7 +124,7 @@ static const char *snd_ilog_interface_names[SND_ILOG_LAST + 1] = { */ const char *snd_lib_log_priority(int prio) { - if (prio >= 0 && prio <= SND_LOG_TRACE) + if (prio >= 0 && prio <= SND_LOG_LAST) return snd_log_prio_names[prio]; return NULL; } @@ -136,7 +136,7 @@ const char *snd_lib_log_priority(int prio) */ const char *snd_lib_log_interface(int interface) { - if (interface >= 0 && interface <= SND_ILOG_TOPOLOGY) + if (interface >= 0 && interface <= SND_ILOG_LAST) return snd_ilog_interface_names[interface]; return NULL; } From 29aa1a26b930824394ee175c05c672a2c8f89087 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 28 May 2026 12:16:04 +0200 Subject: [PATCH 28/34] rawmidi: clarify no_active_sensing doc: output-only, does not filter input Fixes: https://github.com/alsa-project/alsa-lib/issues/29 Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Jaroslav Kysela --- src/rawmidi/rawmidi.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/rawmidi/rawmidi.c b/src/rawmidi/rawmidi.c index 4728b08a..671287a1 100644 --- a/src/rawmidi/rawmidi.c +++ b/src/rawmidi/rawmidi.c @@ -811,11 +811,15 @@ size_t snd_rawmidi_params_get_avail_min(const snd_rawmidi_params_t *params) } /** - * \brief set no-active-sensing action on snd_rawmidi_close() + * \brief control whether an Active Sensing byte is sent on output close * \param rawmidi RawMidi handle * \param params pointer to snd_rawmidi_params_t structure - * \param val value: 0 = enable to send the active sensing message, 1 = disable + * \param val 0 = send Active Sensing (0xFE) when the output stream is closed, + * 1 = suppress it (default) * \return 0 on success otherwise a negative error code + * + * This setting applies to the \b output direction only. It does not filter + * or suppress incoming Active Sensing messages received on an input stream. */ #ifndef DOXYGEN int snd_rawmidi_params_set_no_active_sensing(snd_rawmidi_t *rawmidi ATTRIBUTE_UNUSED, snd_rawmidi_params_t *params, int val) @@ -831,7 +835,7 @@ int snd_rawmidi_params_set_no_active_sensing(snd_rawmidi_t *rawmidi, snd_rawmidi /** * \brief get no-active-sensing action status * \param params pointer to snd_rawmidi_params_t structure - * \return the current status (0 = enable, 1 = disable the active sensing message) + * \return 0 if an Active Sensing byte will be sent on output close, 1 if suppressed */ int snd_rawmidi_params_get_no_active_sensing(const snd_rawmidi_params_t *params) { From f7779c2a62a8de9c6a379d39d5d8d22819ed396b Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 1 Jun 2026 09:47:44 +0200 Subject: [PATCH 29/34] configure: update to autoconf v2.72 Signed-off-by: Jaroslav Kysela --- configure.ac | 43 ++++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/configure.ac b/configure.ac index 9810fd4a..fae5d864 100644 --- a/configure.ac +++ b/configure.ac @@ -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.3) +AC_PREREQ([2.72]) +AC_INIT([alsa-lib],[1.2.15.3]) AC_CONFIG_SRCDIR([src/control/control.c]) AC_CONFIG_MACRO_DIR([m4]) @@ -33,23 +33,25 @@ AC_USE_SYSTEM_EXTENSIONS AC_PROG_INSTALL AC_PROG_LN_S AC_DISABLE_STATIC -AC_LIBTOOL_DLOPEN -AM_PROG_LIBTOOL +_LT_SET_OPTION([LT_INIT],[dlopen]) +LT_INIT CC_NOUNDEFINED dnl Checks for header files. -AC_HEADER_STDC +AC_CHECK_INCLUDES_DEFAULT +AC_PROG_EGREP + AC_CONFIG_HEADERS(include/config.h) dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_C_INLINE -AC_HEADER_TIME +AC_CHECK_HEADERS_ONCE([sys/time.h]) + AC_CHECK_ATTRIBUTE_SYMVER dnl Checks for library functions. -AC_PROG_GCC_TRADITIONAL AC_CHECK_FUNCS([uselocale]) AC_CHECK_FUNCS([eaccess]) AC_CHECK_DECLS([closefrom]) @@ -251,10 +253,7 @@ fi dnl Check for scandir64 AC_MSG_CHECKING(for LFS calls) -AC_TRY_LINK([#include ], - [struct dirent64 a; ], - [have_lfs=yes], - [have_lfs=no]) +AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[struct dirent64 a; ]])],[have_lfs=yes],[have_lfs=no]) if test "$have_lfs" = "yes"; then AC_MSG_RESULT(yes) AC_DEFINE([HAVE_LFS], 1, [Have LFS]) @@ -358,7 +357,7 @@ AC_ARG_ENABLE(resmgr, AC_MSG_RESULT($resmgr) if test "$resmgr" = "yes"; then AC_CHECK_LIB(resmgr, rsm_open_device,, - AC_ERROR([Cannot find libresmgr])) + AC_MSG_ERROR(Cannot find libresmgr)) AC_DEFINE(SUPPORT_RESMGR, "1", [Support resmgr with alsa-lib]) fi @@ -482,7 +481,7 @@ AC_SUBST(PYTHON_INCLUDES) if test "$build_rawmidi" != "yes"; then if test "$build_seq" = "yes"; then - AC_ERROR([Cannot enable sequencer without rawmidi]) + AC_MSG_ERROR(Cannot enable sequencer without rawmidi) fi fi @@ -534,22 +533,16 @@ dnl check atomics for pcm_meter AC_MSG_CHECKING([whether GCC supports builtin atomic intrinsics]) if test -z "$gcc_have_atomics"; then gcc_have_atomics=no - AC_TRY_LINK([], - [int i; + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i; __atomic_load_n(&i, __ATOMIC_SEQ_CST); __atomic_add_fetch(&i, 0, __ATOMIC_SEQ_CST); - ], - [gcc_have_atomics=yes], - [gcc_have_atomics=no]) + ]])],[gcc_have_atomics=yes],[gcc_have_atomics=no]) fi AC_MSG_RESULT($gcc_have_atomics) dnl check mmx register for pcm_dmix_i386 -AC_TRY_LINK([], - [__asm__ volatile ("" : : : "mm0");], - [AC_DEFINE([HAVE_MMX], "1", [MMX technology is enabled])], - []) +AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[__asm__ volatile ("" : : : "mm0");]])],[AC_DEFINE([HAVE_MMX], "1", [MMX technology is enabled])],[]) PCM_PLUGIN_LIST="copy linear route mulaw alaw adpcm rate plug multi shm file null empty share meter hooks lfloat ladspa dmix dshare dsnoop asym iec958 softvol extplug ioplug mmap_emul" @@ -734,9 +727,9 @@ AC_ARG_WITH(max-cards, AC_MSG_RESULT([$max_cards]) if test "$max_cards" -lt 1; then - AC_ERROR([Invalid max cards $max_cards]) + AC_MSG_ERROR(Invalid max cards $max_cards) elif test "$max_cards" -gt 256; then - AC_ERROR([Invalid max cards $max_cards]) + AC_MSG_ERROR(Invalid max cards $max_cards) fi AC_DEFINE_UNQUOTED(SND_MAX_CARDS, $max_cards, [Max number of cards]) @@ -779,7 +772,7 @@ AC_CONFIG_FILES(Makefile doc/Makefile doc/pictures/Makefile doc/doxygen.cfg \ test/Makefile test/lsb/Makefile \ utils/Makefile utils/alsa-lib.spec utils/alsa.pc utils/alsa-topology.pc) -AC_OUTPUT() +AC_OUTPUT dnl Create asoundlib.h dynamically according to configure options echo "Creating asoundlib.h..." From 08b532cd3da9ac8f683bcb4e4beb9b74c39c1782 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 1 Jun 2026 10:04:24 +0200 Subject: [PATCH 30/34] Release v1.2.16 Signed-off-by: Jaroslav Kysela --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index fae5d864..73588b92 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ dnl Process this file with autoconf to produce a configure script. AC_PREREQ([2.72]) -AC_INIT([alsa-lib],[1.2.15.3]) +AC_INIT([alsa-lib], [1.2.16]) AC_CONFIG_SRCDIR([src/control/control.c]) AC_CONFIG_MACRO_DIR([m4]) From d983a9ccbc75d19e4a7b2cd51f42650bcfd84d91 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 5 Jun 2026 17:53:43 +0200 Subject: [PATCH 31/34] ucm: pass optional flag to config load functions to suppress spurious errors Fixes: https://github.com/alsa-project/alsa-lib/issues/510 Signed-off-by: Jaroslav Kysela --- src/ucm/parser.c | 21 +++++++++++---------- src/ucm/ucm_include.c | 2 +- src/ucm/ucm_local.h | 6 +++--- src/ucm/utils.c | 11 +++++++---- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 2d7833e9..4cde9194 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -66,7 +66,7 @@ static void ucm_filename(char *fn, size_t fn_len, long version, * */ int uc_mgr_config_load_file(snd_use_case_mgr_t *uc_mgr, - const char *file, snd_config_t **cfg) + const char *file, snd_config_t **cfg, bool optional) { char filename[PATH_MAX]; int err; @@ -74,9 +74,10 @@ int uc_mgr_config_load_file(snd_use_case_mgr_t *uc_mgr, ucm_filename(filename, sizeof(filename), uc_mgr->conf_format, file[0] == '/' ? NULL : uc_mgr->conf_dir_name, file); - err = uc_mgr_config_load(uc_mgr->conf_format, filename, cfg); + err = uc_mgr_config_load(uc_mgr->conf_format, filename, cfg, optional); if (err < 0) { - snd_error(UCM, "failed to open file %s: %d", filename, err); + if (!optional || (err != -ENOENT && err != -EACCES)) + snd_error(UCM, "failed to open file %s: %d", filename, err); return err; } return 0; @@ -825,7 +826,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_file(uc_mgr, file, &cfg); + err = uc_mgr_config_load_file(uc_mgr, file, &cfg, false); if (err < 0) return err; err = uc_mgr_substitute_tree(uc_mgr, cfg); @@ -844,7 +845,7 @@ static int parse_libconfig1(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) ucm_filename(filename, sizeof(filename), uc_mgr->conf_format, file[0] == '/' ? NULL : uc_mgr->conf_dir_name, file); - err = uc_mgr_config_load_into(uc_mgr->conf_format, filename, uc_mgr->local_config); + err = uc_mgr_config_load_into(uc_mgr->conf_format, filename, uc_mgr->local_config, false); if (err < 0) return err; } @@ -2949,7 +2950,7 @@ static int parse_master_section(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, if (file) { snd_config_t *cfg; /* load config from file */ - err = uc_mgr_config_load_file(uc_mgr, file, &cfg); + err = uc_mgr_config_load_file(uc_mgr, file, &cfg, false); if (err < 0) goto __error; /* parse the config */ @@ -3017,7 +3018,7 @@ static int parse_master_section(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, snd_config_t *cfg; const char *fname = vfile ? vfile : file; /* load config from file */ - err = uc_mgr_config_load_file(uc_mgr, fname, &cfg); + err = uc_mgr_config_load_file(uc_mgr, fname, &cfg, false); if (err >= 0) { err = parse_verb_config(uc_mgr, id, vcomment ? vcomment : comment, @@ -3576,7 +3577,7 @@ static int load_toplevel_config(snd_use_case_mgr_t *uc_mgr, return -ENOENT; } - err = uc_mgr_config_load(2, filename, &tcfg); + err = uc_mgr_config_load(2, filename, &tcfg, false); if (err < 0) goto __error; @@ -3586,7 +3587,7 @@ static int load_toplevel_config(snd_use_case_mgr_t *uc_mgr, if (err < 0) goto __error; - err = uc_mgr_config_load(uc_mgr->conf_format, filename, cfg); + err = uc_mgr_config_load(uc_mgr->conf_format, filename, cfg, false); if (err < 0) { snd_error(UCM, "could not parse configuration for card %s", uc_mgr->card_name); goto __error; @@ -3792,7 +3793,7 @@ int uc_mgr_scan_master_configs(const char **_list[]) #endif continue; - err = uc_mgr_config_load(2, filename, &cfg); + err = uc_mgr_config_load(2, filename, &cfg, false); if (err < 0) goto __err; err = snd_config_search(cfg, "Syntax", &c); diff --git a/src/ucm/ucm_include.c b/src/ucm/ucm_include.c index 8a47e748..d663ffc1 100644 --- a/src/ucm/ucm_include.c +++ b/src/ucm/ucm_include.c @@ -90,7 +90,7 @@ 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; - err = uc_mgr_config_load_file(uc_mgr, s, result); + err = uc_mgr_config_load_file(uc_mgr, s, result, opt_bool); if (opt_bool && (err == -ENOENT || err == -EACCES)) { snd_trace(UCM, "optional file '%s' not found or readable", s); err = 0; diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 3015cca5..c00dd6da 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -300,9 +300,9 @@ void uc_mgr_stdout(const char *fmt, ...); const char *uc_mgr_sysfs_root(void); const char *uc_mgr_config_dir(int format); -int uc_mgr_config_load_into(int format, const char *file, snd_config_t *cfg); -int uc_mgr_config_load(int format, const char *file, snd_config_t **cfg); -int uc_mgr_config_load_file(snd_use_case_mgr_t *uc_mgr, const char *file, snd_config_t **cfg); +int uc_mgr_config_load_into(int format, const char *file, snd_config_t *cfg, bool optional); +int uc_mgr_config_load(int format, const char *file, snd_config_t **cfg, bool optional); +int uc_mgr_config_load_file(snd_use_case_mgr_t *uc_mgr, const char *file, snd_config_t **cfg, bool optional); int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr); int uc_mgr_scan_master_configs(const char **_list[]); diff --git a/src/ucm/utils.c b/src/ucm/utils.c index eb6bb4ab..011bdab3 100644 --- a/src/ucm/utils.c +++ b/src/ucm/utils.c @@ -357,7 +357,7 @@ const char *uc_mgr_config_dir(int format) return path; } -int uc_mgr_config_load_into(int format, const char *file, snd_config_t *top) +int uc_mgr_config_load_into(int format, const char *file, snd_config_t *top, bool optional) { FILE *fp; snd_input_t *in; @@ -369,7 +369,10 @@ int uc_mgr_config_load_into(int format, const char *file, snd_config_t *top) if (!fp) { err = -errno; __err_open: - snd_error(UCM, "could not open configuration file %s", file); + if (!optional || (err != -ENOENT && err != -EACCES)) + snd_error(UCM, "could not open configuration file %s", file); + else + snd_trace(UCM, "could not open configuration file %s", file); return err; } err = snd_input_stdio_attach(&in, fp, 1); @@ -391,7 +394,7 @@ int uc_mgr_config_load_into(int format, const char *file, snd_config_t *top) return 0; } -int uc_mgr_config_load(int format, const char *file, snd_config_t **cfg) +int uc_mgr_config_load(int format, const char *file, snd_config_t **cfg, bool optional) { snd_config_t *top; int err; @@ -399,7 +402,7 @@ int uc_mgr_config_load(int format, const char *file, snd_config_t **cfg) err = snd_config_top(&top); if (err < 0) return err; - err = uc_mgr_config_load_into(format, file, top); + err = uc_mgr_config_load_into(format, file, top, optional); if (err < 0) { snd_config_delete(top); return err; From 536dd6f8affdf5197c12a63a71c92a70b2833cc0 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 8 Jun 2026 14:33:19 +0200 Subject: [PATCH 32/34] conf: add missing return value check in parse_def() A malformed configuration may cause SIGSEGV. Link: https://lore.kernel.org/alsa-devel/CAGt8pqBU0p2voB+qHxWGcNJrKHAcBhAyHUUBPLBN-Yj_SiV6MQ@mail.gmail.com/ Reported-by: Luigino Camastra Signed-off-by: Jaroslav Kysela --- src/conf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/conf.c b/src/conf.c index b0dd6298..e1dba23d 100644 --- a/src/conf.c +++ b/src/conf.c @@ -1485,6 +1485,10 @@ static int parse_def(snd_config_t *parent, input_t *input, int skip, int overrid endchr = ']'; } c = get_nonwhite(input); + if (c < 0) { + err = c; + goto __end; + } if (c != endchr) { if (n) snd_config_delete(n); From f453d783336167ee2714573f4352a72e3ce65094 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 12 Jun 2026 13:55:24 +0200 Subject: [PATCH 33/34] ucm: move optional file trace into uc_mgr_config_load_into Do not duplicate traces and identify correctly the optional include in first hit. Signed-off-by: Jaroslav Kysela --- src/ucm/ucm_include.c | 4 +--- src/ucm/utils.c | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ucm/ucm_include.c b/src/ucm/ucm_include.c index d663ffc1..1d2558b3 100644 --- a/src/ucm/ucm_include.c +++ b/src/ucm/ucm_include.c @@ -91,10 +91,8 @@ static int include_eval_one(snd_use_case_mgr_t *uc_mgr, if (err < 0) return err; err = uc_mgr_config_load_file(uc_mgr, s, result, opt_bool); - if (opt_bool && (err == -ENOENT || err == -EACCES)) { - snd_trace(UCM, "optional file '%s' not found or readable", s); + if (opt_bool && (err == -ENOENT || err == -EACCES)) err = 0; - } free(s); return err; } diff --git a/src/ucm/utils.c b/src/ucm/utils.c index 011bdab3..c386593b 100644 --- a/src/ucm/utils.c +++ b/src/ucm/utils.c @@ -372,7 +372,7 @@ int uc_mgr_config_load_into(int format, const char *file, snd_config_t *top, boo if (!optional || (err != -ENOENT && err != -EACCES)) snd_error(UCM, "could not open configuration file %s", file); else - snd_trace(UCM, "could not open configuration file %s", file); + snd_trace(UCM, "optional configuration file %s not found or readable", file); return err; } err = snd_input_stdio_attach(&in, fp, 1); From a7babcb8e6361719bf18fa96f11354d125447500 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 12 Jun 2026 14:59:43 +0200 Subject: [PATCH 34/34] Release v1.2.16.1 Signed-off-by: Jaroslav Kysela --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 73588b92..fec50372 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ dnl Process this file with autoconf to produce a configure script. AC_PREREQ([2.72]) -AC_INIT([alsa-lib], [1.2.16]) +AC_INIT([alsa-lib], [1.2.16.1]) AC_CONFIG_SRCDIR([src/control/control.c]) AC_CONFIG_MACRO_DIR([m4])