1 /*
2 * Exec an external program
3 * Copyright (C) 2021 Jaroslav Kysela
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Support for the verb/device/modifier core logic and API,
20 * command line tool and file parser was kindly sponsored by
21 * Texas Instruments Inc.
22 * Support for multiple active modifiers and devices,
23 * transition sequences, multiple client access and user defined use
24 * cases was kindly sponsored by Wolfson Microelectronics PLC.
25 *
26 * Copyright (C) 2021 Red Hat Inc.
27 * Authors: Jaroslav Kysela <perex@perex.cz>
28 */
29
30 #include "ucm_local.h"
31 #include <sys/stat.h>
32 #include <sys/wait.h>
33 #include <limits.h>
34 #include <dirent.h>
35
36 static pthread_mutex_t fork_lock = PTHREAD_MUTEX_INITIALIZER;
37
38 /*
39 * Search PATH for executable
40 */
find_exec(const char * name,char * out,size_t len)41 static int find_exec(const char *name, char *out, size_t len)
42 {
43 int ret = 0;
44 char bin[PATH_MAX];
45 char *path, *tmp, *tmp2 = NULL;
46 DIR *dir;
47 struct dirent *de;
48 struct stat st;
49 if (name[0] == '/') {
50 if (lstat(name, &st))
51 return 0;
52 if (!S_ISREG(st.st_mode) || !(st.st_mode & S_IEXEC))
53 return 0;
54 snd_strlcpy(out, name, len);
55 return 1;
56 }
57 if (!(tmp = getenv("PATH")))
58 return 0;
59 path = alloca(strlen(tmp) + 1);
60 if (!path)
61 return 0;
62 strcpy(path, tmp);
63 tmp = strtok_r(path, ":", &tmp2);
64 while (tmp && !ret) {
65 if ((dir = opendir(tmp))) {
66 while ((de = readdir(dir))) {
67 if (strstr(de->d_name, name) != de->d_name)
68 continue;
69 snprintf(bin, sizeof(bin), "%s/%s", tmp,
70 de->d_name);
71 if (lstat(bin, &st))
72 continue;
73 if (!S_ISREG(st.st_mode)
74 || !(st.st_mode & S_IEXEC))
75 continue;
76 snd_strlcpy(out, bin, len);
77 closedir(dir);
78 return 1;
79 }
80 closedir(dir);
81 }
82 tmp = strtok_r(NULL, ":", &tmp2);
83 }
84 return ret;
85 }
86
free_args(char ** argv)87 static void free_args(char **argv)
88 {
89 char **a;
90
91 for (a = argv; *a; a++)
92 free(*a);
93 free(argv);
94 }
95
parse_args(char *** argv,int argc,const char * cmd)96 static int parse_args(char ***argv, int argc, const char *cmd)
97 {
98 char *s, *f;
99 int i = 0, l, eow;
100
101 if (!argv || !cmd)
102 return -1;
103
104 s = alloca(strlen(cmd) + 1);
105 if (!s)
106 return -1;
107 strcpy(s, cmd);
108 *argv = calloc(argc, sizeof(char *));
109
110 while (*s && i < argc - 1) {
111 while (*s == ' ')
112 s++;
113 f = s;
114 eow = 0;
115 while (*s) {
116 if (*s == '\\') {
117 l = *(s + 1);
118 if (l == 'b')
119 l = '\b';
120 else if (l == 'f')
121 l = '\f';
122 else if (l == 'n')
123 l = '\n';
124 else if (l == 'r')
125 l = '\r';
126 else if (l == 't')
127 l = '\t';
128 else
129 l = 0;
130 if (l) {
131 *s++ = l;
132 memmove(s, s + 1, strlen(s));
133 } else {
134 memmove(s, s + 1, strlen(s));
135 if (*s)
136 s++;
137 }
138 } else if (eow) {
139 if (*s == eow) {
140 memmove(s, s + 1, strlen(s));
141 eow = 0;
142 } else {
143 s++;
144 }
145 } else if (*s == '\'' || *s == '"') {
146 eow = *s;
147 memmove(s, s + 1, strlen(s));
148 } else if (*s == ' ') {
149 break;
150 } else {
151 s++;
152 }
153 }
154 if (f != s) {
155 if (*s) {
156 *(char *)s = '\0';
157 s++;
158 }
159 (*argv)[i] = strdup(f);
160 if ((*argv)[i] == NULL) {
161 free_args(*argv);
162 return -ENOMEM;
163 }
164 i++;
165 }
166 }
167 (*argv)[i] = NULL;
168 return 0;
169 }
170
171 /*
172 * execute a binary file
173 *
174 */
uc_mgr_exec(const char * prog)175 int uc_mgr_exec(const char *prog)
176 {
177 pid_t p, f, maxfd;
178 int err = 0, status;
179 char bin[PATH_MAX];
180 struct sigaction sa;
181 struct sigaction intr, quit;
182 sigset_t omask;
183 char **argv;
184
185 if (parse_args(&argv, 32, prog))
186 return -EINVAL;
187
188 prog = argv[0];
189 if (prog == NULL) {
190 err = -EINVAL;
191 goto __error;
192 }
193 if (prog[0] != '/' && prog[0] != '.') {
194 if (!find_exec(argv[0], bin, sizeof(bin))) {
195 err = -ENOEXEC;
196 goto __error;
197 }
198 prog = bin;
199 }
200
201 maxfd = sysconf(_SC_OPEN_MAX);
202
203 /*
204 * block SIGCHLD signal
205 * ignore SIGINT and SIGQUIT in parent
206 */
207
208 memset(&sa, 0, sizeof(sa));
209 sa.sa_handler = SIG_IGN;
210 sigemptyset(&sa.sa_mask);
211 sigaddset(&sa.sa_mask, SIGCHLD);
212
213 pthread_mutex_lock(&fork_lock);
214
215 sigprocmask(SIG_BLOCK, &sa.sa_mask, &omask);
216
217 sigaction(SIGINT, &sa, &intr);
218 sigaction(SIGQUIT, &sa, &quit);
219
220 p = fork();
221
222 if (p == -1) {
223 err = -errno;
224 pthread_mutex_unlock(&fork_lock);
225 uc_error("Unable to fork() for \"%s\" -- %s", prog,
226 strerror(errno));
227 goto __error;
228 }
229
230 if (p == 0) {
231 f = open("/dev/null", O_RDWR);
232 if (f == -1) {
233 uc_error("pid %d cannot open /dev/null for redirect %s -- %s",
234 getpid(), prog, strerror(errno));
235 exit(1);
236 }
237
238 close(0);
239 close(1);
240 close(2);
241
242 dup2(f, 0);
243 dup2(f, 1);
244 dup2(f, 2);
245
246 close(f);
247
248 for (f = 3; f < maxfd; f++)
249 close(f);
250
251 /* install default handlers for the forked process */
252 signal(SIGINT, SIG_DFL);
253 signal(SIGQUIT, SIG_DFL);
254
255 execve(prog, argv, environ);
256 exit(1);
257 }
258
259 sigaction(SIGINT, &intr, NULL);
260 sigaction(SIGQUIT, &quit, NULL);
261 sigprocmask(SIG_SETMASK, &omask, NULL);
262
263 pthread_mutex_unlock(&fork_lock);
264
265 /* make the spawned process a session leader so killing the
266 process group recursively kills any child process that
267 might have been spawned */
268 setpgid(p, p);
269
270 while (1) {
271 f = waitpid(p, &status, 0);
272 if (f == -1) {
273 if (errno == EAGAIN)
274 continue;
275 err = -errno;
276 goto __error;
277 }
278 if (WIFSIGNALED(status)) {
279 err = -EINTR;
280 break;
281 }
282 if (WIFEXITED(status)) {
283 err = WEXITSTATUS(status);
284 break;
285 }
286 }
287
288 __error:
289 free_args(argv);
290 return err;
291 }
292