]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/exec-util.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / basic / exec-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <dirent.h>
22 #include <errno.h>
23 #include <sys/prctl.h>
24 #include <sys/types.h>
25 #include <unistd.h>
26 #include <stdio.h>
27
28 #include "alloc-util.h"
29 #include "conf-files.h"
30 #include "env-util.h"
31 #include "exec-util.h"
32 #include "fd-util.h"
33 #include "fileio.h"
34 #include "hashmap.h"
35 #include "macro.h"
36 #include "process-util.h"
37 #include "set.h"
38 #include "signal-util.h"
39 #include "stat-util.h"
40 #include "string-util.h"
41 #include "strv.h"
42 #include "terminal-util.h"
43 #include "util.h"
44
45 /* Put this test here for a lack of better place */
46 assert_cc(EAGAIN == EWOULDBLOCK);
47
48 static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) {
49
50 pid_t _pid;
51
52 if (null_or_empty_path(path)) {
53 log_debug("%s is empty (a mask).", path);
54 return 0;
55 }
56
57 _pid = fork();
58 if (_pid < 0)
59 return log_error_errno(errno, "Failed to fork: %m");
60 if (_pid == 0) {
61 char *_argv[2];
62
63 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
64
65 if (stdout_fd >= 0) {
66 /* If the fd happens to be in the right place, go along with that */
67 if (stdout_fd != STDOUT_FILENO &&
68 dup2(stdout_fd, STDOUT_FILENO) < 0)
69 return -errno;
70
71 fd_cloexec(STDOUT_FILENO, false);
72 }
73
74 if (!argv) {
75 _argv[0] = (char*) path;
76 _argv[1] = NULL;
77 argv = _argv;
78 } else
79 argv[0] = (char*) path;
80
81 execv(path, argv);
82 log_error_errno(errno, "Failed to execute %s: %m", path);
83 _exit(EXIT_FAILURE);
84 }
85
86 log_debug("Spawned %s as " PID_FMT ".", path, _pid);
87 *pid = _pid;
88 return 1;
89 }
90
91 static int do_execute(
92 char **directories,
93 usec_t timeout,
94 gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
95 void* const callback_args[_STDOUT_CONSUME_MAX],
96 int output_fd,
97 char *argv[]) {
98
99 _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
100 _cleanup_strv_free_ char **paths = NULL;
101 char **path;
102 int r;
103
104 /* We fork this all off from a child process so that we can somewhat cleanly make
105 * use of SIGALRM to set a time limit.
106 *
107 * If callbacks is nonnull, execution is serial. Otherwise, we default to parallel.
108 */
109
110 (void) reset_all_signal_handlers();
111 (void) reset_signal_mask();
112
113 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
114
115 r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE, (const char* const*) directories);
116 if (r < 0)
117 return r;
118
119 if (!callbacks) {
120 pids = hashmap_new(NULL);
121 if (!pids)
122 return log_oom();
123 }
124
125 /* Abort execution of this process after the timout. We simply rely on SIGALRM as
126 * default action terminating the process, and turn on alarm(). */
127
128 if (timeout != USEC_INFINITY)
129 alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC);
130
131 STRV_FOREACH(path, paths) {
132 _cleanup_free_ char *t = NULL;
133 _cleanup_close_ int fd = -1;
134 pid_t pid;
135
136 t = strdup(*path);
137 if (!t)
138 return log_oom();
139
140 if (callbacks) {
141 fd = open_serialization_fd(basename(*path));
142 if (fd < 0)
143 return log_error_errno(fd, "Failed to open serialization file: %m");
144 }
145
146 r = do_spawn(t, argv, fd, &pid);
147 if (r <= 0)
148 continue;
149
150 if (pids) {
151 r = hashmap_put(pids, PID_TO_PTR(pid), t);
152 if (r < 0)
153 return log_oom();
154 t = NULL;
155 } else {
156 r = wait_for_terminate_and_warn(t, pid, true);
157 if (r < 0)
158 continue;
159
160 if (lseek(fd, 0, SEEK_SET) < 0)
161 return log_error_errno(errno, "Failed to seek on serialization fd: %m");
162
163 r = callbacks[STDOUT_GENERATE](fd, callback_args[STDOUT_GENERATE]);
164 fd = -1;
165 if (r < 0)
166 return log_error_errno(r, "Failed to process output from %s: %m", *path);
167 }
168 }
169
170 if (callbacks) {
171 r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]);
172 if (r < 0)
173 return log_error_errno(r, "Callback two failed: %m");
174 }
175
176 while (!hashmap_isempty(pids)) {
177 _cleanup_free_ char *t = NULL;
178 pid_t pid;
179
180 pid = PTR_TO_PID(hashmap_first_key(pids));
181 assert(pid > 0);
182
183 t = hashmap_remove(pids, PID_TO_PTR(pid));
184 assert(t);
185
186 wait_for_terminate_and_warn(t, pid, true);
187 }
188
189 return 0;
190 }
191
192 int execute_directories(
193 const char* const* directories,
194 usec_t timeout,
195 gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
196 void* const callback_args[_STDOUT_CONSUME_MAX],
197 char *argv[]) {
198
199 pid_t executor_pid;
200 char *name;
201 char **dirs = (char**) directories;
202 _cleanup_close_ int fd = -1;
203 int r;
204
205 assert(!strv_isempty(dirs));
206
207 name = basename(dirs[0]);
208 assert(!isempty(name));
209
210 if (callbacks) {
211 assert(callback_args);
212 assert(callbacks[STDOUT_GENERATE]);
213 assert(callbacks[STDOUT_COLLECT]);
214 assert(callbacks[STDOUT_CONSUME]);
215
216 fd = open_serialization_fd(name);
217 if (fd < 0)
218 return log_error_errno(fd, "Failed to open serialization file: %m");
219 }
220
221 /* Executes all binaries in the directories serially or in parallel and waits for
222 * them to finish. Optionally a timeout is applied. If a file with the same name
223 * exists in more than one directory, the earliest one wins. */
224
225 executor_pid = fork();
226 if (executor_pid < 0)
227 return log_error_errno(errno, "Failed to fork: %m");
228
229 if (executor_pid == 0) {
230 r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv);
231 _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
232 }
233
234 r = wait_for_terminate_and_warn(name, executor_pid, true);
235 if (r < 0)
236 return log_error_errno(r, "Execution failed: %m");
237 if (r > 0) {
238 /* non-zero return code from child */
239 log_error("Forker process failed.");
240 return -EREMOTEIO;
241 }
242
243 if (!callbacks)
244 return 0;
245
246 if (lseek(fd, 0, SEEK_SET) < 0)
247 return log_error_errno(errno, "Failed to rewind serialization fd: %m");
248
249 r = callbacks[STDOUT_CONSUME](fd, callback_args[STDOUT_CONSUME]);
250 fd = -1;
251 if (r < 0)
252 return log_error_errno(r, "Failed to parse returned data: %m");
253 return 0;
254 }
255
256 static int gather_environment_generate(int fd, void *arg) {
257 char ***env = arg, **x, **y;
258 _cleanup_fclose_ FILE *f = NULL;
259 _cleanup_strv_free_ char **new;
260 int r;
261
262 /* Read a series of VAR=value assignments from fd, use them to update the list of
263 * variables in env. Also update the exported environment.
264 *
265 * fd is always consumed, even on error.
266 */
267
268 assert(env);
269
270 f = fdopen(fd, "r");
271 if (!f) {
272 safe_close(fd);
273 return -errno;
274 }
275
276 r = load_env_file_pairs(f, NULL, NULL, &new);
277 if (r < 0)
278 return r;
279
280 STRV_FOREACH_PAIR(x, y, new) {
281 char *p;
282
283 if (!env_name_is_valid(*x)) {
284 log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x);
285 continue;
286 }
287
288 p = strjoin(*x, "=", *y);
289 if (!p)
290 return -ENOMEM;
291
292 r = strv_env_replace(env, p);
293 if (r < 0)
294 return r;
295
296 if (setenv(*x, *y, true) < 0)
297 return -errno;
298 }
299
300 return r;
301 }
302
303 static int gather_environment_collect(int fd, void *arg) {
304 char ***env = arg;
305 _cleanup_fclose_ FILE *f = NULL;
306 int r;
307
308 /* Write out a series of env=cescape(VAR=value) assignments to fd. */
309
310 assert(env);
311
312 f = fdopen(fd, "w");
313 if (!f) {
314 safe_close(fd);
315 return -errno;
316 }
317
318 r = serialize_environment(f, *env);
319 if (r < 0)
320 return r;
321
322 if (ferror(f))
323 return errno > 0 ? -errno : -EIO;
324
325 return 0;
326 }
327
328 static int gather_environment_consume(int fd, void *arg) {
329 char ***env = arg;
330 _cleanup_fclose_ FILE *f = NULL;
331 char line[LINE_MAX];
332 int r = 0, k;
333
334 /* Read a series of env=cescape(VAR=value) assignments from fd into env. */
335
336 assert(env);
337
338 f = fdopen(fd, "r");
339 if (!f) {
340 safe_close(fd);
341 return -errno;
342 }
343
344 FOREACH_LINE(line, f, return -EIO) {
345 truncate_nl(line);
346
347 k = deserialize_environment(env, line);
348 if (k < 0)
349 log_error_errno(k, "Invalid line \"%s\": %m", line);
350 if (k < 0 && r == 0)
351 r = k;
352 }
353
354 return r;
355 }
356
357 const gather_stdout_callback_t gather_environment[] = {
358 gather_environment_generate,
359 gather_environment_collect,
360 gather_environment_consume,
361 };