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 #if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
37 #include <signal.h>
38 #if defined(__DragonFly__)
39 #define environ NULL /* XXX */
40 #else
41 extern char **environ;
42 #endif
43 #endif
44 
45 static pthread_mutex_t fork_lock = PTHREAD_MUTEX_INITIALIZER;
46 
47 /*
48  * Search PATH for executable
49  */
find_exec(const char *name, char *out, size_t len)50 static int find_exec(const char *name, char *out, size_t len)
51 {
52 	int ret = 0;
53 	char bin[PATH_MAX];
54 	char *path, *tmp, *tmp2 = NULL;
55 	DIR *dir;
56 	struct dirent64 *de;
57 	struct stat64 st;
58 	if (name[0] == '/') {
59 		if (lstat64(name, &st))
60 			return 0;
61 		if (!S_ISREG(st.st_mode) || !(st.st_mode & S_IEXEC))
62 			return 0;
63 		snd_strlcpy(out, name, len);
64 		return 1;
65 	}
66 	if (!(tmp = getenv("PATH")))
67 		return 0;
68 	path = alloca(strlen(tmp) + 1);
69 	if (!path)
70 		return 0;
71 	strcpy(path, tmp);
72 	tmp = strtok_r(path, ":", &tmp2);
73 	while (tmp && !ret) {
74 		if ((dir = opendir(tmp))) {
75 			while ((de = readdir64(dir))) {
76 				if (strstr(de->d_name, name) != de->d_name)
77 					continue;
78 				snprintf(bin, sizeof(bin), "%s/%s", tmp,
79 					 de->d_name);
80 				if (lstat64(bin, &st))
81 					continue;
82 				if (!S_ISREG(st.st_mode)
83 				    || !(st.st_mode & S_IEXEC))
84 					continue;
85 				snd_strlcpy(out, bin, len);
86 				closedir(dir);
87 				return 1;
88 			}
89 			closedir(dir);
90 		}
91 		tmp = strtok_r(NULL, ":", &tmp2);
92 	}
93 	return ret;
94 }
95 
free_args(char **argv)96 static void free_args(char **argv)
97 {
98 	char **a;
99 
100 	for (a = argv; *a; a++)
101 		free(*a);
102 	free(argv);
103 }
104 
parse_args(char ***argv, int argc, const char *cmd)105 static int parse_args(char ***argv, int argc, const char *cmd)
106 {
107 	char *s, *f;
108 	int i = 0, l, eow;
109 
110 	if (!argv || !cmd)
111 		return -1;
112 
113 	s = alloca(strlen(cmd) + 1);
114 	if (!s)
115 		return -1;
116 	strcpy(s, cmd);
117 	*argv = calloc(argc, sizeof(char *));
118 
119 	while (*s && i < argc - 1) {
120 		while (*s == ' ')
121 			s++;
122 		f = s;
123 		eow = 0;
124 		while (*s) {
125 			if (*s == '\\') {
126 				l = *(s + 1);
127 				if (l == 'b')
128 					l = '\b';
129 				else if (l == 'f')
130 					l = '\f';
131 				else if (l == 'n')
132 					l = '\n';
133 				else if (l == 'r')
134 					l = '\r';
135 				else if (l == 't')
136 					l = '\t';
137 				else
138 					l = 0;
139 				if (l) {
140 					*s++ = l;
141 					memmove(s, s + 1, strlen(s));
142 				} else {
143 					memmove(s, s + 1, strlen(s));
144 					if (*s)
145 						s++;
146 				}
147 			} else if (eow) {
148 				if (*s == eow) {
149 					memmove(s, s + 1, strlen(s));
150 					eow = 0;
151 				} else {
152 					s++;
153 				}
154 			} else if (*s == '\'' || *s == '"') {
155 				eow = *s;
156 				memmove(s, s + 1, strlen(s));
157 			} else if (*s == ' ') {
158 				break;
159 			} else {
160 				s++;
161 			}
162 		}
163 		if (f != s) {
164 			if (*s) {
165 				*(char *)s = '\0';
166 				s++;
167 			}
168 			(*argv)[i] = strdup(f);
169 			if ((*argv)[i] == NULL) {
170 				free_args(*argv);
171 				return -ENOMEM;
172 			}
173 			i++;
174 		}
175 	}
176 	(*argv)[i] = NULL;
177 	return 0;
178 }
179 
180 /*
181  * execute a binary file
182  *
183  */
uc_mgr_exec(const char *prog)184 int uc_mgr_exec(const char *prog)
185 {
186 	pid_t p, f, maxfd;
187 	int err = 0, status;
188 	char bin[PATH_MAX];
189 	struct sigaction sa;
190 	struct sigaction intr, quit;
191 	sigset_t omask;
192 	char **argv;
193 
194 	if (parse_args(&argv, 32, prog))
195 		return -EINVAL;
196 
197 	prog = argv[0];
198 	if (prog == NULL) {
199 		err = -EINVAL;
200 		goto __error;
201 	}
202 	if (prog[0] != '/' && prog[0] != '.') {
203 		if (!find_exec(argv[0], bin, sizeof(bin))) {
204 			err = -ENOEXEC;
205 			goto __error;
206 		}
207 		prog = bin;
208 	}
209 
210 	maxfd = sysconf(_SC_OPEN_MAX);
211 
212 	/*
213 	 * block SIGCHLD signal
214 	 * ignore SIGINT and SIGQUIT in parent
215 	 */
216 
217 	memset(&sa, 0, sizeof(sa));
218 	sa.sa_handler = SIG_IGN;
219 	sigemptyset(&sa.sa_mask);
220 	sigaddset(&sa.sa_mask, SIGCHLD);
221 
222 	pthread_mutex_lock(&fork_lock);
223 
224 	sigprocmask(SIG_BLOCK, &sa.sa_mask, &omask);
225 
226 	sigaction(SIGINT, &sa, &intr);
227 	sigaction(SIGQUIT, &sa, &quit);
228 
229 	p = fork();
230 
231 	if (p == -1) {
232 		err = -errno;
233 		pthread_mutex_unlock(&fork_lock);
234 		uc_error("Unable to fork() for \"%s\" -- %s", prog,
235 			 strerror(errno));
236 		goto __error;
237 	}
238 
239 	if (p == 0) {
240 		f = open("/dev/null", O_RDWR);
241 		if (f == -1) {
242 			uc_error("pid %d cannot open /dev/null for redirect %s -- %s",
243 				 getpid(), prog, strerror(errno));
244 			exit(1);
245 		}
246 
247 		close(0);
248 		close(1);
249 		close(2);
250 
251 		dup2(f, 0);
252 		dup2(f, 1);
253 		dup2(f, 2);
254 
255 		close(f);
256 
257 		for (f = 3; f < maxfd; f++)
258 			close(f);
259 
260 		/* install default handlers for the forked process */
261 		signal(SIGINT, SIG_DFL);
262 		signal(SIGQUIT, SIG_DFL);
263 
264 		execve(prog, argv, environ);
265 		exit(1);
266 	}
267 
268 	sigaction(SIGINT, &intr, NULL);
269 	sigaction(SIGQUIT, &quit, NULL);
270 	sigprocmask(SIG_SETMASK, &omask, NULL);
271 
272 	pthread_mutex_unlock(&fork_lock);
273 
274 	/* make the spawned process a session leader so killing the
275 	   process group recursively kills any child process that
276 	   might have been spawned */
277 	setpgid(p, p);
278 
279 	while (1) {
280 		f = waitpid(p, &status, 0);
281 		if (f == -1) {
282 			if (errno == EAGAIN)
283 				continue;
284 			err = -errno;
285 			goto __error;
286 		}
287 		if (WIFSIGNALED(status)) {
288 			err = -EINTR;
289 			break;
290 		}
291 		if (WIFEXITED(status)) {
292 			err = WEXITSTATUS(status);
293 			break;
294 		}
295 	}
296 
297  __error:
298 	free_args(argv);
299 	return err;
300 }
301