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 6020122c..a908abd1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.* !.travis.yml configure config.log diff --git a/Android.bp b/Android.bp index aab81aa8..f2139b12 100644 --- a/Android.bp +++ b/Android.bp @@ -15,19 +15,19 @@ cc_library_shared { ], exclude_srcs: [ "src/control/ctl_symbols_list.c", - "src/pcm/scopes/*.c", "src/pcm/pcm_dmix_generic.c", "src/pcm/pcm_dmix_i386.c", "src/pcm/pcm_dmix_x86_64.c", "src/pcm/pcm_symbols_list.c", + "src/pcm/scopes/*.c", ], cflags: [ - "-O2", "-DPIC", - "-fPIC", + "-O2", "-Wno-pointer-arith", "-Wno-pointer-bool-conversion", "-Wno-single-bit-bitfield-constant-conversion", + "-fPIC", ], required: ["libasound-config"], } diff --git a/configure.ac b/configure.ac index 9810fd4a..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.59) -AC_INIT(alsa-lib, 1.2.15.3) +AC_PREREQ([2.72]) +AC_INIT([alsa-lib], [1.2.16.1]) 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..." 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/include/version.h b/include/version.h index 99db197b..b49335a0 100644 --- a/include/version.h +++ b/include/version.h @@ -4,10 +4,10 @@ #define SND_LIB_MAJOR 1 /**< major number of library version */ #define SND_LIB_MINOR 2 /**< minor number of library version */ -#define SND_LIB_SUBMINOR 15 /**< subminor number of library version */ +#define SND_LIB_SUBMINOR 16 /**< subminor number of library version */ #define SND_LIB_EXTRAVER 1000000 /**< extra version number, used mainly for betas */ /** library version */ #define SND_LIB_VER(maj, min, sub) (((maj)<<16)|((min)<<8)|(sub)) #define SND_LIB_VERSION SND_LIB_VER(SND_LIB_MAJOR, SND_LIB_MINOR, SND_LIB_SUBMINOR) /** library version (string) */ -#define SND_LIB_VERSION_STR "1.2.15.3" +#define SND_LIB_VERSION_STR "1.2.16.1" diff --git a/src/conf.c b/src/conf.c index fb9f0658..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); @@ -4443,7 +4447,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/control/control_remap.c b/src/control/control_remap.c index 7d90d7ad..80a0f43b 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,19 @@ static int remap_id_to_child(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id, snd_c } *id = rid->id_child; } else { - if (remap_find_id_child(priv, id)) - return -ENOENT; numid = remap_find_numid_app(priv, id->numid); - if (numid) + if (numid) { id->numid = numid->numid_child; - else - id->numid = 0; + } else { + if (reloaded || priv->list_complete) + return -ENOENT; + /* build whole numid mapping */ + err = remap_load_list(priv); + if (err < 0) + return err; + reloaded = true; + goto _retry; + } } *_rid = rid; debug_id(id, "%s leave\n", __func__); @@ -329,6 +350,7 @@ static int remap_id_to_app(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id, snd_ctl id->numid = numid->numid_app; } } + debug_id(id, "%s rid %p\n", __func__, rid); return err; } @@ -466,9 +488,8 @@ static int snd_ctl_remap_card_info(snd_ctl_t *ctl, snd_ctl_card_info_t *info) return snd_ctl_card_info(priv->child, info); } -static int snd_ctl_remap_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list) +static int _snd_ctl_remap_elem_list(snd_ctl_remap_t *priv, snd_ctl_elem_list_t *list) { - snd_ctl_remap_t *priv = ctl->private_data; snd_ctl_elem_id_t *id; snd_ctl_remap_id_t *rid; snd_ctl_numid_t *numid; @@ -483,13 +504,17 @@ static int snd_ctl_remap_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list) id = &list->pids[index]; rid = remap_find_id_child(priv, id); if (rid) { - rid->id_app.numid = id->numid; - *id = rid->id_app; + assert(id->numid > 0); + rid->id_child.numid = id->numid; } numid = remap_find_numid_child(priv, id->numid); if (numid == NULL) return -EIO; id->numid = numid->numid_app; + if (rid) { + rid->id_app.numid = id->numid; + *id = rid->id_app; + } } if (list->offset >= list->count + priv->map_items + priv->sync_switch_items) return 0; @@ -510,9 +535,39 @@ static int snd_ctl_remap_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list) } } list->count += priv->map_items + priv->sync_switch_items; + if (list->offset < priv->list_first) + priv->list_first = list->offset; + if (list->offset == priv->list_last && list->offset + list->used > priv->list_last) + priv->list_last = list->offset + list->used; + priv->list_complete = priv->list_first == 0 && list->count == priv->list_last; return 0; } +static int snd_ctl_remap_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list) +{ + snd_ctl_remap_t *priv = ctl->private_data; + + return _snd_ctl_remap_elem_list(priv, list); +} + +static int remap_load_list(snd_ctl_remap_t *remap) +{ + snd_ctl_elem_list_t list; + int err = 0; + + memset(&list, 0, sizeof(list)); + do { + err = _snd_ctl_remap_elem_list(remap, &list); + if (err < 0) + break; + err = snd_ctl_elem_list_alloc_space(&list, list.count); + if (err < 0) + break; + } while (list.count != list.used); + snd_ctl_elem_list_free_space(&list); + return err; +} + #ifndef DOC_HIDDEN #define ACCESS_BITS(bits) \ (bits & (SNDRV_CTL_ELEM_ACCESS_READWRITE|\ @@ -1674,6 +1729,7 @@ int snd_ctl_remap_open(snd_ctl_t **handlep, const char *name, snd_config_t *rema priv->numid_remap_active = priv->map_items > 0 || priv->sync_items; + priv->list_first = UINT_MAX; priv->child = child; err = snd_ctl_new(&ctl, SND_CTL_TYPE_REMAP, name, mode); if (err < 0) { 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; 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 diff --git a/src/error.c b/src/error.c index df8ba522..e721bdc0 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", }; /** @@ -123,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; } @@ -135,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; } 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; diff --git a/src/pcm/pcm_params.c b/src/pcm/pcm_params.c index 0536252e..a10555d0 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 @@ -1220,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; @@ -1243,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; } } } 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) { 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/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; 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; 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, 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/main.c b/src/ucm/main.c index 21a39a34..d2b3c2d9 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; } @@ -1780,7 +1781,7 @@ int snd_use_case_mgr_open(snd_use_case_mgr_t **uc_mgr, card_name = parse_open_variables(mgr, card_name); /* Application developers: This argument is not supposed to be set for standard applications. */ - if (uc_mgr_get_variable(mgr, "@InBoot")) + if (uc_mgr_get_variable(mgr, "@InBoot", false)) mgr->in_boot = true; err = uc_mgr_card_open(mgr); @@ -2378,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) { @@ -2711,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); @@ -3013,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/parser.c b/src/ucm/parser.c index 60861213..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; @@ -391,7 +392,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 +428,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 +506,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) { @@ -509,7 +528,7 @@ static int evaluate_macro1(snd_use_case_mgr_t *uc_mgr, if (err < 0) goto __err_path; snprintf(name, sizeof(name), "__%s", id); - if (uc_mgr_get_variable(uc_mgr, name)) { + if (uc_mgr_get_variable(uc_mgr, name, false)) { snd_error(UCM, "Macro argument '%s' is already defined", name); goto __err_path; } @@ -711,9 +730,9 @@ int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) { long iterations = 10000; - int err1 = 0, err2 = 0, err3 = 0, err4 = 0, err5 = 0; + int err1 = 0, err2 = 0, err3 = 0, err4 = 0, err5 = 0, err6 = 0; - while (err1 == 0 || err2 == 0 || err3 == 0 || err4 == 0 || err5 == 0) { + while (err1 == 0 || err2 == 0 || err3 == 0 || err4 == 0 || err5 == 0 || err6 == 0) { if (iterations == 0) { snd_error(UCM, "Maximal inplace evaluation iterations number reached (recursive references?)"); return -EINVAL; @@ -747,9 +766,12 @@ int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr, return err4; if (err4 == 0) continue; - err5 = evaluate_condition(uc_mgr, cfg); + err5 = uc_mgr_evaluate_repeat(uc_mgr, cfg); if (err5 < 0) return err5; + err6 = evaluate_condition(uc_mgr, cfg); + if (err6 < 0) + return err6; } return 0; } @@ -804,7 +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(uc_mgr->conf_format, 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); @@ -823,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; } @@ -2928,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 */ @@ -2996,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, @@ -3555,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; @@ -3565,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; @@ -3771,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_cond.c b/src/ucm/ucm_cond.c index b909c6b2..f7147f59 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; @@ -270,6 +268,80 @@ static int if_eval_control_exists(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval return 1; } +static int if_eval_integer(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval) +{ + const char *value1_str = NULL, *value2_str = NULL, *operation = NULL; + char *s1, *s2; + long long val1, val2; + int err, err1, err2; + + if (uc_mgr->conf_format < 9) { + snd_error(UCM, "Integer condition is supported in v9+ syntax"); + return -EINVAL; + } + + err = get_string(eval, "Operation", &operation); + if (err < 0) { + snd_error(UCM, "Integer error (If.Condition.Operation)"); + return -EINVAL; + } + + err = get_string(eval, "Value1", &value1_str); + if (err < 0) { + snd_error(UCM, "Integer error (If.Condition.Value1)"); + return -EINVAL; + } + + err = get_string(eval, "Value2", &value2_str); + if (err < 0) { + snd_error(UCM, "Integer error (If.Condition.Value2)"); + return -EINVAL; + } + + err = uc_mgr_get_substituted_value(uc_mgr, &s1, value1_str); + if (err < 0) + return err; + + err = uc_mgr_get_substituted_value(uc_mgr, &s2, value2_str); + if (err < 0) { + free(s1); + return err; + } + + err1 = safe_strtoll(s1, &val1); + err2 = safe_strtoll(s2, &val2); + + if (err1 < 0 || err2 < 0) { + if (err1 < 0) + snd_error(UCM, "Integer conversion error for Value1 '%s'", s1); + if (err2 < 0) + snd_error(UCM, "Integer conversion error for Value2 '%s'", s2); + free(s2); + free(s1); + return -EINVAL; + } + + free(s2); + free(s1); + + if (strcmp(operation, "==") == 0) { + return val1 == val2; + } else if (strcmp(operation, "!=") == 0) { + return val1 != val2; + } else if (strcmp(operation, "<") == 0) { + return val1 < val2; + } else if (strcmp(operation, ">") == 0) { + return val1 > val2; + } else if (strcmp(operation, "<=") == 0) { + return val1 <= val2; + } else if (strcmp(operation, ">=") == 0) { + return val1 >= val2; + } else { + snd_error(UCM, "Integer unknown operation '%s'", operation); + return -EINVAL; + } +} + static int if_eval_path(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval) { const char *path, *mode = ""; @@ -365,6 +437,9 @@ static int if_eval(snd_use_case_mgr_t *uc_mgr, snd_config_t *eval) if (strcmp(type, "Path") == 0) return if_eval_path(uc_mgr, eval); + if (strcmp(type, "Integer") == 0) + return if_eval_integer(uc_mgr, eval); + snd_error(UCM, "unknown If.Condition.Type"); return -EINVAL; } @@ -377,7 +452,9 @@ static int if_eval_one(snd_use_case_mgr_t *uc_mgr, snd_config_t **prepend, snd_config_t **append) { - snd_config_t *expr, *_true = NULL, *_false = NULL; + snd_config_t *expr, *expr_eval = NULL, *_true = NULL, *_false = NULL; + const char *s; + char *s1; int err, has_condition; *result = NULL; @@ -392,73 +469,99 @@ static int if_eval_one(snd_use_case_mgr_t *uc_mgr, /* For syntax v8+, Condition is optional if Prepend or Append is present */ has_condition = snd_config_search(cond, "Condition", &expr) >= 0; + if (has_condition && uc_mgr->conf_format >= 9 && + snd_config_get_type(expr) == SND_CONFIG_TYPE_STRING) { + err = snd_config_get_string(expr, &s); + if (err < 0) { + snd_error(UCM, "Condition string error (If)"); + return -EINVAL; + } + err = uc_mgr_get_substituted_value(uc_mgr, &s1, s); + if (err >= 0) { + err = snd_config_load_string(&expr_eval, s1, 0); + free(s1); + } + if (err < 0) { + snd_error(UCM, "Condition string parse error (If)"); + return err; + } + expr = expr_eval; + } + if (uc_mgr->conf_format >= 8) { /* Check for Prepend block */ err = snd_config_search(cond, "Prepend", prepend); if (err < 0 && err != -ENOENT) { snd_error(UCM, "prepend block error (If)"); - return -EINVAL; + goto __error; } /* Check for Append block */ err = snd_config_search(cond, "Append", append); if (err < 0 && err != -ENOENT) { snd_error(UCM, "append block error (If)"); - return -EINVAL; + goto __error; } /* If Prepend or Append is present, Condition can be omitted */ if (!has_condition && (*prepend == NULL && *append == NULL)) { snd_error(UCM, "condition block expected (If)"); - return -EINVAL; + goto __error; } } else { if (!has_condition) { snd_error(UCM, "condition block expected (If)"); - return -EINVAL; + goto __error; } } err = snd_config_search(cond, "True", &_true); if (err < 0 && err != -ENOENT) { snd_error(UCM, "true block error (If)"); - return -EINVAL; + goto __error; } err = snd_config_search(cond, "False", &_false); if (err < 0 && err != -ENOENT) { snd_error(UCM, "false block error (If)"); - return -EINVAL; + goto __error; } err = snd_config_search(cond, "Before", before); if (err < 0 && err != -ENOENT) { snd_error(UCM, "before block identifier error"); - return -EINVAL; + goto __error; } err = snd_config_search(cond, "After", after); if (err < 0 && err != -ENOENT) { snd_error(UCM, "before block identifier error"); - return -EINVAL; + goto __error; } /* Evaluate condition if present */ if (has_condition) { err = if_eval(uc_mgr, expr); + if (err < 0) + goto __error; if (err > 0) { *result = _true; - return 0; + goto __return; } else if (err == 0) { *result = _false; - return 0; - } else { - return err; + goto __return; } } /* If no condition (v8+ with Prepend/Append only), no result block */ return 0; + +__error: + err = -EINVAL; +__return: + if (expr_eval) + snd_config_delete(expr_eval); + return err; } #if 0 diff --git a/src/ucm/ucm_confdoc.h b/src/ucm/ucm_confdoc.h index 5fac99e4..7f507644 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 @@ -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 @@ -662,6 +663,7 @@ Usage example: ~~~{.html} ${find-card:field=name,regex='^acp$',return=number} +${find-card:field=$FieldName,regex=$Pattern,return=number} ~~~ Arguments: @@ -669,8 +671,8 @@ Arguments: Argument | Description ---------------------|----------------------- return | return value type (id, number), id is the default -field | field for the lookup (id, driver, name, longname, mixername, components) -regex | regex string for the field match +field | field for the lookup (id, driver, name, longname, mixername, components) or variable name ($var) [**Syntax 9**] +regex | regex string for the field match or variable name ($var) [**Syntax 9**] #### Find device substitution @@ -678,16 +680,50 @@ Usage example: ~~~{.html} ${find-device:type=pcm,field=name,regex='DMIC'} +${find-device:type=$DevType,stream=$StreamType,field=$FieldName,regex=$Pattern} ~~~ Arguments: Argument | Description ---------------------|----------------------- -type | device type (pcm) -stream | stream type (playback, capture), playback is default -field | field for the lookup (id, name, subname) -regex | regex string for the field match +type | device type (pcm) or variable name ($var) [**Syntax 9**] +stream | stream type (playback, capture), playback is default; variable name ($var) supported in **Syntax 9** +field | field for the lookup (id, name, subname) or variable name ($var) [**Syntax 9**] +regex | regex string for the field match or variable name ($var) [**Syntax 9**] + +#### Card info substitution + +This substitution retrieves information about a specific ALSA card by card number +or card ID and returns the requested field value. + +Usage examples: + +~~~{.html} +${info-card:card=0,field=name} +${info-card:card=acp,field=driver} +${info-card:card=PCH,field=longname} +${info-card:card=$MyCard,field=$MyField} +~~~ + +Arguments: + +Argument | Description +---------------------|-------------------------------------------------- +card | card number (integer), card ID (string), or variable name ($var) +field | field to retrieve (number, id, driver, name, longname, mixername, components) or variable name ($var) + +The **card** parameter can be either a card number (e.g., 0, 1, 2), a card ID string (e.g., "PCH", "acp", "Intel"), +or a variable name prefixed with $ (e.g., $CardId). + +The **field** parameter specifies which card information to return or can be a variable name prefixed with $ (e.g., $FieldName): +- **number**: Card number (integer as string) +- **id**: Card identifier +- **driver**: Card driver name +- **name**: Card short name +- **longname**: Card long name +- **mixername**: Mixer name +- **components**: Card components ### Variable defines @@ -702,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 @@ -878,6 +1030,32 @@ If.fmic { } ~~~ +#### Integer comparison (Type Integer) + +Field | Description +---------------------|----------------------- +Operation | comparison operator (==, !=, <, >, <=, >=) +Value1 | first integer value (string converted to long long) +Value2 | second integer value (string converted to long long) + +Note: Integer condition is supported in *Syntax* version *9*+. + +Example: + +~~~{.html} +If.check_channels { + Condition { + Type Integer + Operation ">" + Value1 "${var:channels}" + Value2 "2" + } + True { + ... + } +} +~~~ + ### Variants To avoid duplication of the many configuration files for the cases with @@ -986,6 +1164,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_include.c b/src/ucm/ucm_include.c index b155a086..1d2558b3 100644 --- a/src/ucm/ucm_include.c +++ b/src/ucm/ucm_include.c @@ -90,12 +90,9 @@ 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, opt_bool); + if (opt_bool && (err == -ENOENT || err == -EACCES)) err = 0; - } else { - err = uc_mgr_config_load_file(uc_mgr, s, result); - } free(s); return err; } diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 8b3da74f..c00dd6da 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 @@ -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[]); @@ -352,7 +352,8 @@ int uc_mgr_add_value(struct list_head *base, const char *key, char *val); int uc_mgr_check_value(struct list_head *value_list, const char *identifier); const char *uc_mgr_get_variable(snd_use_case_mgr_t *uc_mgr, - const char *name); + const char *name, + bool show_err); int uc_mgr_set_variable(snd_use_case_mgr_t *uc_mgr, const char *name, @@ -384,6 +385,9 @@ int uc_mgr_evaluate_condition(snd_use_case_mgr_t *uc_mgr, snd_config_t *parent, snd_config_t *cond); +int uc_mgr_evaluate_repeat(snd_use_case_mgr_t *uc_mgr, + snd_config_t *cfg); + int uc_mgr_define_regex(snd_use_case_mgr_t *uc_mgr, const char *name, snd_config_t *eval); 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; 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; +} diff --git a/src/ucm/ucm_subs.c b/src/ucm/ucm_subs.c index 73230a2a..c8b13648 100644 --- a/src/ucm/ucm_subs.c +++ b/src/ucm/ucm_subs.c @@ -204,6 +204,92 @@ static char *rval_card_id_by_name(snd_use_case_mgr_t *uc_mgr, const char *id) return strdup(snd_ctl_card_info_get_id(ctl_list->ctl_info)); } +static char *rval_card_info(snd_use_case_mgr_t *uc_mgr, const char *query) +{ + snd_config_t *config, *d; + const char *card_str, *field_str, *tmp; + struct ctl_list *ctl_list = NULL; + snd_ctl_card_info_t *info; + char *result = NULL; + long card_num; + int err; + + if (uc_mgr->conf_format < 9) { + snd_error(UCM, "info-card substitution is supported in v9+ syntax"); + return NULL; + } + + err = snd_config_load_string(&config, query, 0); + if (err < 0) { + snd_error(UCM, "info-card: invalid arguments '%s'", query); + return NULL; + } + + if (snd_config_search(config, "card", &d)) { + snd_error(UCM, "info-card: 'card' parameter is required"); + goto __error; + } + if (snd_config_get_string(d, &card_str)) + goto __error; + + if (card_str[0] == '$') { + tmp = card_str + 1; + card_str = uc_mgr_get_variable(uc_mgr, tmp, true); + if (card_str == NULL) + goto __error; + } + + if (snd_config_search(config, "field", &d)) { + snd_error(UCM, "info-card: 'field' parameter is required"); + goto __error; + } + if (snd_config_get_string(d, &field_str)) + goto __error; + + if (field_str[0] == '$') { + tmp = field_str + 1; + field_str = uc_mgr_get_variable(uc_mgr, tmp, true); + if (field_str == NULL) + goto __error; + } + + if (safe_strtol(card_str, &card_num) == 0) + ctl_list = uc_mgr_get_ctl_by_card(uc_mgr, (int)card_num); + if (ctl_list == NULL) + ctl_list = get_ctl_list_by_name(uc_mgr, card_str); + if (ctl_list == NULL) { + snd_error(UCM, "info-card: card '%s' not found", card_str); + goto __error; + } + + info = ctl_list->ctl_info; + + if (strcasecmp(field_str, "number") == 0) { + char num[16]; + snprintf(num, sizeof(num), "%d", snd_ctl_card_info_get_card(info)); + result = strdup(num); + } else if (strcasecmp(field_str, "id") == 0) { + result = strdup(snd_ctl_card_info_get_id(info)); + } else if (strcasecmp(field_str, "driver") == 0) { + result = strdup(snd_ctl_card_info_get_driver(info)); + } else if (strcasecmp(field_str, "name") == 0) { + result = strdup(snd_ctl_card_info_get_name(info)); + } else if (strcasecmp(field_str, "longname") == 0) { + result = strdup(snd_ctl_card_info_get_longname(info)); + } else if (strcasecmp(field_str, "mixername") == 0) { + result = strdup(snd_ctl_card_info_get_mixername(info)); + } else if (strcasecmp(field_str, "components") == 0) { + result = strdup(snd_ctl_card_info_get_components(info)); + } else { + snd_error(UCM, "info-card: unknown field '%s'", field_str); + result = NULL; + } + +__error: + snd_config_delete(config); + return result; +} + #ifndef DOC_HIDDEN typedef struct lookup_iterate *(*lookup_iter_fcn_t) (snd_use_case_mgr_t *uc_mgr, struct lookup_iterate *iter); @@ -235,7 +321,7 @@ static char *rval_lookup_main(snd_use_case_mgr_t *uc_mgr, snd_config_t *config, *d; struct lookup_fcn *fcn; struct lookup_iterate *curr; - const char *s; + const char *s, *tmp; char *result; regmatch_t match[1]; regex_t re; @@ -259,6 +345,12 @@ static char *rval_lookup_main(snd_use_case_mgr_t *uc_mgr, } if (snd_config_get_string(d, &s)) goto null; + if (s[0] == '$' && uc_mgr->conf_format >= 9) { + tmp = s + 1; + s = uc_mgr_get_variable(uc_mgr, tmp, true); + if (s == NULL) + goto null; + } for (fcn = iter->fcns ; fcn; fcn++) { if (strcasecmp(fcn->name, s) == 0) { iter->fcn = fcn->fcn; @@ -275,6 +367,12 @@ static char *rval_lookup_main(snd_use_case_mgr_t *uc_mgr, } if (snd_config_get_string(d, &s)) goto null; + if (s[0] == '$' && uc_mgr->conf_format >= 9) { + tmp = s + 1; + s = uc_mgr_get_variable(uc_mgr, tmp, true); + if (s == NULL) + goto null; + } err = regcomp(&re, s, REG_EXTENDED | REG_ICASE); if (err) { snd_error(UCM, "Regex '%s' compilation failed (code %d)", s, err); @@ -410,7 +508,8 @@ static char *rval_pcm_lookup_return(struct lookup_iterate *iter, return strdup(num); } -static int rval_pcm_lookup_init(struct lookup_iterate *iter, +static int rval_pcm_lookup_init(snd_use_case_mgr_t *uc_mgr, + struct lookup_iterate *iter, snd_config_t *config) { static struct lookup_fcn pcm_fcns[] = { @@ -420,12 +519,18 @@ static int rval_pcm_lookup_init(struct lookup_iterate *iter, { 0 }, }; snd_config_t *d; - const char *s; + const char *s, *tmp; snd_pcm_info_t *pcminfo; snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; if (snd_config_search(config, "stream", &d) == 0 && snd_config_get_string(d, &s) == 0) { + if (s[0] == '$' && uc_mgr->conf_format >= 9) { + tmp = s + 1; + s = uc_mgr_get_variable(uc_mgr, tmp, true); + if (s == NULL) + return -EINVAL; + } if (strcasecmp(s, "playback") == 0) stream = SND_PCM_STREAM_PLAYBACK; else if (strcasecmp(s, "capture") == 0) @@ -454,13 +559,14 @@ static int rval_device_lookup_init(snd_use_case_mgr_t *uc_mgr, { static struct { const char *name; - int (*init)(struct lookup_iterate *iter, snd_config_t *config); + int (*init)(snd_use_case_mgr_t *uc_mgr, struct lookup_iterate *iter, + snd_config_t *config); } *t, types[] = { { .name = "pcm", .init = rval_pcm_lookup_init }, { 0 } }; snd_config_t *d; - const char *s; + const char *s, *tmp; int err; if (snd_config_search(config, "ctl", &d) || snd_config_get_string(d, &s)) { @@ -480,9 +586,15 @@ static int rval_device_lookup_init(snd_use_case_mgr_t *uc_mgr, snd_error(UCM, "Missing device type!"); return -EINVAL; } + if (s[0] == '$' && uc_mgr->conf_format >= 9) { + tmp = s + 1; + s = uc_mgr_get_variable(uc_mgr, tmp, true); + if (s == NULL) + return -EINVAL; + } for (t = types; t->name; t++) if (strcasecmp(t->name, s) == 0) - return t->init(iter, config); + return t->init(uc_mgr, iter, config); snd_error(UCM, "Device type '%s' is invalid", s); return -EINVAL; } @@ -726,7 +838,7 @@ static char *rval_var(snd_use_case_mgr_t *uc_mgr, const char *id) } else if (id[0] == '@') { ignore_not_found = true; } - v = uc_mgr_get_variable(uc_mgr, id); + v = uc_mgr_get_variable(uc_mgr, id, false); if (v == NULL && ignore_not_found) v = ""; if (v) @@ -742,7 +854,7 @@ static int rval_eval_var_cb(snd_config_t **dst, const char *s, void *private_dat snd_use_case_mgr_t *uc_mgr = private_data; const char *v; - v = uc_mgr_get_variable(uc_mgr, s); + v = uc_mgr_get_variable(uc_mgr, s, false); if (v == NULL) return -ENOENT; return snd_config_imake_string(dst, NULL, v); @@ -826,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); @@ -913,6 +1025,7 @@ __std: MATCH_VARIABLE2(value, "${eval:", rval_eval, false); MATCH_VARIABLE2(value, "${find-card:", rval_card_lookup, false); MATCH_VARIABLE2(value, "${find-device:", rval_device_lookup, false); + MATCH_VARIABLE2(value, "${info-card:", rval_card_info, false); MATCH_VARIABLE2(value, "${CardNumberByName:", rval_card_number_by_name, false); MATCH_VARIABLE2(value, "${CardIdByName:", rval_card_id_by_name, false); __merr: @@ -939,7 +1052,7 @@ __match2: if (*v2 == '$' && uc_mgr->conf_format >= 3) { if (strncmp(value, "${eval:", 7) == 0) goto __direct_fcn2; - tmp = uc_mgr_get_variable(uc_mgr, v2 + 1); + tmp = uc_mgr_get_variable(uc_mgr, v2 + 1, false); if (tmp == NULL) { snd_error(UCM, "define '%s' is not reachable in this context!", v2 + 1); rval = NULL; diff --git a/src/ucm/utils.c b/src/ucm/utils.c index dcb5c6e1..c386593b 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, "optional configuration file %s not found or readable", 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; @@ -672,7 +675,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 +685,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; }