• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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