diff options
author | Jaroslav Kysela <perex@perex.cz> | 2021-05-11 14:48:16 +0200 |
---|---|---|
committer | Jaroslav Kysela <perex@perex.cz> | 2021-05-12 08:32:45 +0200 |
commit | 590df3a5b1d632dbdc86d0efaf418cea8b589686 (patch) | |
tree | 0dbe8d41b12d7446c9b26a88c37fc7de4b5532ff | |
parent | a468505c9653545cc709025b85ca2b124f768023 (diff) | |
download | alsa-lib-590df3a5b1d632dbdc86d0efaf418cea8b589686.tar.gz |
ucm: add exec sequence command
This change renames the original exec command to shell which
is more appropriate. Implement a light version of the exec
command which calls directly the specified executable without
the shell interaction (man 3 system).
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
-rw-r--r-- | src/ucm/Makefile.am | 2 | ||||
-rw-r--r-- | src/ucm/main.c | 10 | ||||
-rw-r--r-- | src/ucm/parser.c | 6 | ||||
-rw-r--r-- | src/ucm/ucm_exec.c | 285 | ||||
-rw-r--r-- | src/ucm/ucm_local.h | 15 | ||||
-rw-r--r-- | src/ucm/utils.c | 1 |
6 files changed, 312 insertions, 7 deletions
diff --git a/src/ucm/Makefile.am b/src/ucm/Makefile.am index feb3c0c1..1f1d8b1f 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 main.c + ucm_regex.c ucm_exec.c main.c noinst_HEADERS = ucm_local.h diff --git a/src/ucm/main.c b/src/ucm/main.c index 3df9b62a..e6be169f 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -717,6 +717,14 @@ static int execute_sequence(snd_use_case_mgr_t *uc_mgr, usleep(s->data.sleep); break; case SEQUENCE_ELEMENT_TYPE_EXEC: + err = uc_mgr_exec(s->data.exec); + if (err != 0) { + uc_error("exec '%s' failed (exit code %d)", s->data.exec, err); + goto __fail; + } + break; + case SEQUENCE_ELEMENT_TYPE_SHELL: +shell_retry: err = system(s->data.exec); if (WIFSIGNALED(err)) { err = -EINTR; @@ -727,6 +735,8 @@ static int execute_sequence(snd_use_case_mgr_t *uc_mgr, goto __fail; } } else if (err < 0) { + if (errno == EAGAIN) + goto shell_retry; err = -errno; goto __fail; } diff --git a/src/ucm/parser.c b/src/ucm/parser.c index c0a9c13a..33754803 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -910,6 +910,7 @@ cset: if (strcmp(cmd, "exec") == 0) { curr->type = SEQUENCE_ELEMENT_TYPE_EXEC; +exec: err = parse_string_substitute3(uc_mgr, n, &curr->data.exec); if (err < 0) { uc_error("error: exec requires a string!"); @@ -918,6 +919,11 @@ cset: continue; } + if (strcmp(cmd, "shell") == 0) { + curr->type = SEQUENCE_ELEMENT_TYPE_SHELL; + goto exec; + } + if (strcmp(cmd, "comment") == 0) goto skip; diff --git a/src/ucm/ucm_exec.c b/src/ucm/ucm_exec.c new file mode 100644 index 00000000..a22df8fe --- /dev/null +++ b/src/ucm/ucm_exec.c @@ -0,0 +1,285 @@ +/* + * Exec an external program + * Copyright (C) 2021 Jaroslav Kysela + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Support for the verb/device/modifier core logic and API, + * command line tool and file parser was kindly sponsored by + * Texas Instruments Inc. + * Support for multiple active modifiers and devices, + * transition sequences, multiple client access and user defined use + * cases was kindly sponsored by Wolfson Microelectronics PLC. + * + * Copyright (C) 2021 Red Hat Inc. + * Authors: Jaroslav Kysela <perex@perex.cz> + */ + +#include "ucm_local.h" +#include <sys/stat.h> +#include <sys/wait.h> +#include <dirent.h> + +static pthread_mutex_t fork_lock = PTHREAD_MUTEX_INITIALIZER; + +/* + * Search PATH for executable + */ +static int find_exec(const char *name, char *out, size_t len) +{ + int ret = 0; + char bin[PATH_MAX]; + char *path, *tmp, *tmp2 = NULL; + DIR *dir; + struct dirent *de; + struct stat st; + if (name[0] == '/') { + if (lstat(name, &st)) + return 0; + if (!S_ISREG(st.st_mode) || !(st.st_mode & S_IEXEC)) + return 0; + snd_strlcpy(out, name, len); + return 1; + } + if (!(tmp = getenv("PATH"))) + return 0; + path = alloca(strlen(tmp) + 1); + if (!path) + return 0; + strcpy(path, tmp); + tmp = strtok_r(path, ":", &tmp2); + while (tmp && !ret) { + if ((dir = opendir(tmp))) { + while ((de = readdir(dir))) { + if (strstr(de->d_name, name) != de->d_name) + continue; + snprintf(bin, sizeof(bin), "%s/%s", tmp, + de->d_name); + if (lstat(bin, &st)) + continue; + if (!S_ISREG(st.st_mode) + || !(st.st_mode & S_IEXEC)) + continue; + snd_strlcpy(out, bin, len); + return 1; + } + closedir(dir); + } + tmp = strtok_r(NULL, ":", &tmp2); + } + return ret; +} + +static void free_args(char **argv) +{ + char **a; + + for (a = argv; *a; a++) + free(*a); + free(argv); +} + +static int parse_args(char ***argv, int argc, const char *cmd) +{ + char *s, *f; + int i = 0, l, eow; + + if (!argv || !cmd) + return -1; + + s = alloca(strlen(cmd) + 1); + if (!s) + return -1; + strcpy(s, cmd); + *argv = calloc(argc, sizeof(char *)); + + while (*s && i < argc - 1) { + while (*s == ' ') + s++; + f = s; + eow = 0; + while (*s) { + if (*s == '\\') { + l = *(s + 1); + if (l == 'b') + l = '\b'; + else if (l == 'f') + l = '\f'; + else if (l == 'n') + l = '\n'; + else if (l == 'r') + l = '\r'; + else if (l == 't') + l = '\t'; + else + l = 0; + if (l) { + *s++ = l; + memmove(s, s + 1, strlen(s)); + } else { + memmove(s, s + 1, strlen(s)); + if (*s) + s++; + } + } else if (eow) { + if (*s == eow) { + memmove(s, s + 1, strlen(s)); + eow = 0; + } else { + s++; + } + } else if (*s == '\'' || *s == '"') { + eow = *s; + memmove(s, s + 1, strlen(s)); + } else if (*s == ' ') { + break; + } else { + s++; + } + } + if (f != s) { + if (*s) { + *(char *)s = '\0'; + s++; + } + (*argv)[i] = strdup(f); + if ((*argv)[i] == NULL) { + free_args(*argv); + return -ENOMEM; + } + i++; + } + } + (*argv)[i] = NULL; + return 0; +} + +/* + * execute a binary file + * + */ +int uc_mgr_exec(const char *prog) +{ + pid_t p, f, maxfd; + int err = 0, status; + char bin[PATH_MAX]; + struct sigaction sa; + struct sigaction intr, quit; + sigset_t omask; + char **argv; + + if (parse_args(&argv, 32, prog)) + return -EINVAL; + + prog = argv[0]; + if (argv[0][0] != '/' && argv[0][0] != '.') { + if (!find_exec(argv[0], bin, sizeof(bin))) { + err = -ENOEXEC; + goto __error; + } + prog = bin; + } + + maxfd = sysconf(_SC_OPEN_MAX); + + /* + * block SIGCHLD signal + * ignore SIGINT and SIGQUIT in parent + */ + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGCHLD); + + pthread_mutex_lock(&fork_lock); + + sigprocmask(SIG_BLOCK, &sa.sa_mask, &omask); + + sigaction(SIGINT, &sa, &intr); + sigaction(SIGQUIT, &sa, &quit); + + p = fork(); + + if (p == -1) { + err = -errno; + pthread_mutex_unlock(&fork_lock); + uc_error("Unable to fork() for \"%s\" -- %s", prog, + strerror(errno)); + goto __error; + } + + if (p == 0) { + f = open("/dev/null", O_RDWR); + if (f == -1) { + uc_error("pid %d cannot open /dev/null for redirect %s -- %s", + getpid(), prog, strerror(errno)); + exit(1); + } + + close(0); + close(1); + close(2); + + dup2(f, 0); + dup2(f, 1); + dup2(f, 2); + + close(f); + + for (f = 3; f < maxfd; f++) + close(f); + + /* install default handlers for the forked process */ + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + + execve(prog, argv, environ); + exit(1); + } + + sigaction(SIGINT, &intr, NULL); + sigaction(SIGQUIT, &quit, NULL); + sigprocmask(SIG_SETMASK, &omask, NULL); + + pthread_mutex_unlock(&fork_lock); + + /* make the spawned process a session leader so killing the + process group recursively kills any child process that + might have been spawned */ + setpgid(p, p); + + while (1) { + f = waitpid(p, &status, 0); + if (f == -1) { + if (errno == EAGAIN) + continue; + err = -errno; + goto __error; + } + if (WIFSIGNALED(status)) { + err = -EINTR; + break; + } + if (WIFEXITED(status)) { + err = WEXITSTATUS(status); + break; + } + } + + __error: + free_args(argv); + return err; +} diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 7dfd24b9..c0374148 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -49,12 +49,13 @@ #define SEQUENCE_ELEMENT_TYPE_CSET 2 #define SEQUENCE_ELEMENT_TYPE_SLEEP 3 #define SEQUENCE_ELEMENT_TYPE_EXEC 4 -#define SEQUENCE_ELEMENT_TYPE_CSET_BIN_FILE 5 -#define SEQUENCE_ELEMENT_TYPE_CSET_TLV 6 -#define SEQUENCE_ELEMENT_TYPE_CSET_NEW 7 -#define SEQUENCE_ELEMENT_TYPE_CTL_REMOVE 8 -#define SEQUENCE_ELEMENT_TYPE_CMPT_SEQ 9 -#define SEQUENCE_ELEMENT_TYPE_SYSSET 10 +#define SEQUENCE_ELEMENT_TYPE_SHELL 5 +#define SEQUENCE_ELEMENT_TYPE_CSET_BIN_FILE 6 +#define SEQUENCE_ELEMENT_TYPE_CSET_TLV 7 +#define SEQUENCE_ELEMENT_TYPE_CSET_NEW 8 +#define SEQUENCE_ELEMENT_TYPE_CTL_REMOVE 9 +#define SEQUENCE_ELEMENT_TYPE_CMPT_SEQ 10 +#define SEQUENCE_ELEMENT_TYPE_SYSSET 11 struct ucm_value { struct list_head list; @@ -356,6 +357,8 @@ int uc_mgr_define_regex(snd_use_case_mgr_t *uc_mgr, const char *name, snd_config_t *eval); +int uc_mgr_exec(const char *prog); + /** The name of the environment variable containing the UCM directory */ #define ALSA_CONFIG_UCM_VAR "ALSA_CONFIG_UCM" diff --git a/src/ucm/utils.c b/src/ucm/utils.c index 560c58dd..0eaf6c3c 100644 --- a/src/ucm/utils.c +++ b/src/ucm/utils.c @@ -509,6 +509,7 @@ void uc_mgr_free_sequence_element(struct sequence_element *seq) free(seq->data.sysw); break; case SEQUENCE_ELEMENT_TYPE_EXEC: + case SEQUENCE_ELEMENT_TYPE_SHELL: free(seq->data.exec); break; default: |