--- /dev/null
+/*
+ * 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;
+}