From 82caf6e7f35f85d64ce0c2049ea47db1133e61e7 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 27 Nov 2025 17:04:46 +0100 Subject: [PATCH] alsactl: add -Y option to export card states as key=value pairs Add export.c with support for exporting card states as key=value pairs. Exports ALSA_CARD_NUMBER/ALSA_CARD_STATE for single cards, or ALSA_CARD#_STATE for multiple cards. States: active, skip, waiting. Add -Y (--export) option to restore command for state export. This feature is designed for udev IMPORT{program} use. Signed-off-by: Jaroslav Kysela --- alsactl/Makefile.am | 2 +- alsactl/alsactl.c | 7 +++ alsactl/alsactl.h | 6 ++ alsactl/export.c | 133 ++++++++++++++++++++++++++++++++++++++++++++ alsactl/state.c | 6 +- 5 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 alsactl/export.c diff --git a/alsactl/Makefile.am b/alsactl/Makefile.am index 1f6b712..6725c9c 100644 --- a/alsactl/Makefile.am +++ b/alsactl/Makefile.am @@ -13,7 +13,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/include alsactl_SOURCES=alsactl.c state.c lock.c utils.c \ init_parse.c init_ucm.c boot_params.c \ - daemon.c monitor.c clean.c info.c + daemon.c monitor.c clean.c info.c export.c alsactl_CFLAGS=$(AM_CFLAGS) -D__USE_GNU \ -DSYS_ASOUNDRC=\"$(ASOUND_STATE_DIR)/asound.state\" \ diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index adde783..8ea4a84 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -53,6 +53,7 @@ int force_restore = 1; int ignore_nocards = 0; int do_lock = 0; int use_syslog = 0; +int do_export = 0; char *command; char *statefile = NULL; char *groupfile = SYS_CARD_GROUP; @@ -97,6 +98,7 @@ static struct arg args[] = { { FILEARG | 'r', "runstate", "save restore and init state to this file (only errors)" }, { 0, NULL, " default settings is 'no file set'" }, { 'R', "remove", "remove runstate file at first, otherwise append errors" }, +{ 'Y', "export", "export card state as key=value pairs (restore command only)" }, { INTARG | 'p', "period", "store period in seconds for the daemon command" }, { FILEARG | 'e', "pid-file", "pathname for the process id (daemon mode)" }, { HEADER, NULL, "Available init options:" }, @@ -362,6 +364,9 @@ int main(int argc, char *argv[]) case 'R': removestate = 1; break; + case 'Y': + do_export = 1; + break; case 'P': force_restore = 0; break; @@ -470,6 +475,8 @@ int main(int argc, char *argv[]) if (removestate) remove(statefile); res = load_state(cfgdir, cfgfile, initfile, initflags, cardname, init_fallback); + if (do_export && res >= 0) + res = export_cards(cardname); if (!strcmp(cmd, "rdaemon")) { do_nice(use_nice, sched_idle); res = state_daemon(cfgfile, cardname, period, pidfile); diff --git a/alsactl/alsactl.h b/alsactl/alsactl.h index 6f7b3a2..aeee114 100644 --- a/alsactl/alsactl.h +++ b/alsactl/alsactl.h @@ -8,6 +8,7 @@ extern int force_restore; extern int ignore_nocards; extern int do_lock; extern int use_syslog; +extern int do_export; extern char *command; extern char *statefile; extern char *groupfile; @@ -96,6 +97,11 @@ int state_daemon_kill(const char *pidfile, const char *cmd); int clean(const char *cardname, char *const *extra_args); int snd_card_clean_cfgdir(const char *cfgdir, int cardno); +/* export */ + +int export_card_state_set(int card, int state); +int export_cards(const char *cardname); + /* utils */ int file_map(const char *filename, char **buf, size_t *bufsize); diff --git a/alsactl/export.c b/alsactl/export.c new file mode 100644 index 0000000..422a92c --- /dev/null +++ b/alsactl/export.c @@ -0,0 +1,133 @@ +/* + * Advanced Linux Sound Architecture Control Program - Export + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/** + * Export variable syntax: + * + * For single card: + * + * ALSA_CARD_NUMBER= + * ALSA_CARD_STATE= + * + * For multiple cards: + * + * ALSA_CARD#_STATE= # where # is replaced with the card number + * + * State list: + * + * active # card was initialized and active + * skip # card was skipped (other card in group) + * waiting # card is waiting for the initial configuration + */ + +#include "aconfig.h" +#include "version.h" +#include +#include +#include +#include "alsactl.h" + +/* Global array to store card states (0 = active, 1 = waiting) */ +static int export_card_state[32]; + +/** + * export_card_state_set - Set card state + * @card: Card number + * @state: Card state (0 = active, 1 = waiting) + * + * Returns 0 on success, negative error code on failure + */ +int export_card_state_set(int card, int state) +{ + /* Check bounds */ + if (card < 0 || (unsigned long)card >= ARRAY_SIZE(export_card_state)) + return -EINVAL; + + export_card_state[card] = state; + return 0; +} + +/** + * export_card_state_print - Export and print card state information + * @iter: Card iterator containing current card information + * + * Prints key=value pairs based on iterator's single flag and export_card_state array. + * Returns 0 on success, negative error code on failure + */ +static int export_card_state_print(struct snd_card_iterator *iter) +{ + const char *state; + int istate; + + if (!iter) + return -EINVAL; + + /* Check bounds */ + if (iter->card < 0 || (unsigned long)iter->card >= ARRAY_SIZE(export_card_state)) + return -EINVAL; + + /* Determine state from export_card_state array */ + istate = export_card_state[iter->card]; + switch (istate) { + case CARD_STATE_WAIT: state = "waiting"; break; + case CARD_STATE_SKIP: state = "skip"; break; + default: state = "active"; break; + } + + if (iter->single) { + /* Single card export format */ + printf("ALSA_CARD_NUMBER=%d\n", iter->card); + printf("ALSA_CARD_STATE=%s\n", state); + } else { + /* Multiple cards export format */ + printf("ALSA_CARD%d_STATE=%s\n", iter->card, state); + } + + return 0; +} + +/** + * export_cards - Export state for all cards + * @cardname: Card name or NULL for all cards + * + * Returns 0 on success, negative error code on failure + */ +int export_cards(const char *cardname) +{ + struct snd_card_iterator iter; + const char *name; + int ret; + + ret = snd_card_iterator_sinit(&iter, cardname); + if (ret < 0) + return ret; + + while ((name = snd_card_iterator_next(&iter)) != NULL) { + ret = export_card_state_print(&iter); + if (ret < 0) + break; + } + + if (ret == 0) + ret = snd_card_iterator_error(&iter); + + return ret; +} diff --git a/alsactl/state.c b/alsactl/state.c index f9b9260..4d326a1 100644 --- a/alsactl/state.c +++ b/alsactl/state.c @@ -1729,6 +1729,8 @@ int load_state(const char *cfgdir, const char *file, } err = init(cfgdir, initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname1); card_unlock(lock_fd, iter.card); + if (card_state_is_okay(err)) + export_card_state_set(iter.card, err); if (err < 0) { finalerr = err; initfailed(iter.card, "init", err); @@ -1754,8 +1756,10 @@ int load_state(const char *cfgdir, const char *file, /* error is ignored */ err = init_ucm(initflags | FLAG_UCM_FBOOT, iter.card); /* return code 1 and 2 -> postpone initialization */ - if (card_state_is_okay(err)) + if (card_state_is_okay(err)) { + export_card_state_set(iter.card, err); goto unlock_card; + } /* do a check if controls matches state file */ if (do_init && set_controls(iter.card, config, 0)) { err = init(cfgdir, initfile, initflags | FLAG_UCM_BOOT, cardname1); -- 2.47.3