]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/exec-util.c
Merge pull request #17766 from weblate/weblate-systemd-master
[thirdparty/systemd.git] / src / shared / exec-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <dirent.h>
4 #include <errno.h>
5 #include <sys/prctl.h>
6 #include <sys/types.h>
7 #include <unistd.h>
8 #include <stdio.h>
9
10 #include "alloc-util.h"
11 #include "conf-files.h"
12 #include "env-file.h"
13 #include "env-util.h"
14 #include "errno-util.h"
15 #include "exec-util.h"
16 #include "fd-util.h"
17 #include "fileio.h"
18 #include "hashmap.h"
19 #include "macro.h"
20 #include "missing_syscall.h"
21 #include "process-util.h"
22 #include "rlimit-util.h"
23 #include "serialize.h"
24 #include "set.h"
25 #include "signal-util.h"
26 #include "stat-util.h"
27 #include "string-table.h"
28 #include "string-util.h"
29 #include "strv.h"
30 #include "terminal-util.h"
31 #include "tmpfile-util.h"
32 #include "util.h"
33
34 /* Put this test here for a lack of better place */
35 assert_cc(EAGAIN == EWOULDBLOCK);
36
37 static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) {
38 pid_t _pid;
39 int r;
40
41 if (null_or_empty_path(path)) {
42 log_debug("%s is empty (a mask).", path);
43 return 0;
44 }
45
46 r = safe_fork("(direxec)", FORK_DEATHSIG|FORK_LOG, &_pid);
47 if (r < 0)
48 return r;
49 if (r == 0) {
50 char *_argv[2];
51
52 if (stdout_fd >= 0) {
53 r = rearrange_stdio(STDIN_FILENO, stdout_fd, STDERR_FILENO);
54 if (r < 0)
55 _exit(EXIT_FAILURE);
56 }
57
58 (void) rlimit_nofile_safe();
59
60 if (!argv) {
61 _argv[0] = (char*) path;
62 _argv[1] = NULL;
63 argv = _argv;
64 } else
65 argv[0] = (char*) path;
66
67 execv(path, argv);
68 log_error_errno(errno, "Failed to execute %s: %m", path);
69 _exit(EXIT_FAILURE);
70 }
71
72 *pid = _pid;
73 return 1;
74 }
75
76 static int do_execute(
77 char **directories,
78 usec_t timeout,
79 gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
80 void* const callback_args[_STDOUT_CONSUME_MAX],
81 int output_fd,
82 char *argv[],
83 char *envp[],
84 ExecDirFlags flags) {
85
86 _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
87 _cleanup_strv_free_ char **paths = NULL;
88 char **path, **e;
89 int r;
90 bool parallel_execution;
91
92 /* We fork this all off from a child process so that we can somewhat cleanly make
93 * use of SIGALRM to set a time limit.
94 *
95 * We attempt to perform parallel execution if configured by the user, however
96 * if `callbacks` is nonnull, execution must be serial.
97 */
98 parallel_execution = FLAGS_SET(flags, EXEC_DIR_PARALLEL) && !callbacks;
99
100 r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE|CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char* const*) directories);
101 if (r < 0)
102 return log_error_errno(r, "Failed to enumerate executables: %m");
103
104 if (parallel_execution) {
105 pids = hashmap_new(NULL);
106 if (!pids)
107 return log_oom();
108 }
109
110 /* Abort execution of this process after the timeout. We simply rely on SIGALRM as
111 * default action terminating the process, and turn on alarm(). */
112
113 if (timeout != USEC_INFINITY)
114 alarm(DIV_ROUND_UP(timeout, USEC_PER_SEC));
115
116 STRV_FOREACH(e, envp)
117 if (putenv(*e) != 0)
118 return log_error_errno(errno, "Failed to set environment variable: %m");
119
120 STRV_FOREACH(path, paths) {
121 _cleanup_free_ char *t = NULL;
122 _cleanup_close_ int fd = -1;
123 pid_t pid;
124
125 t = strdup(*path);
126 if (!t)
127 return log_oom();
128
129 if (callbacks) {
130 fd = open_serialization_fd(basename(*path));
131 if (fd < 0)
132 return log_error_errno(fd, "Failed to open serialization file: %m");
133 }
134
135 r = do_spawn(t, argv, fd, &pid);
136 if (r <= 0)
137 continue;
138
139 if (parallel_execution) {
140 r = hashmap_put(pids, PID_TO_PTR(pid), t);
141 if (r < 0)
142 return log_oom();
143 t = NULL;
144 } else {
145 r = wait_for_terminate_and_check(t, pid, WAIT_LOG);
146 if (FLAGS_SET(flags, EXEC_DIR_IGNORE_ERRORS)) {
147 if (r < 0)
148 continue;
149 } else if (r > 0)
150 return r;
151
152 if (callbacks) {
153 if (lseek(fd, 0, SEEK_SET) < 0)
154 return log_error_errno(errno, "Failed to seek on serialization fd: %m");
155
156 r = callbacks[STDOUT_GENERATE](fd, callback_args[STDOUT_GENERATE]);
157 fd = -1;
158 if (r < 0)
159 return log_error_errno(r, "Failed to process output from %s: %m", *path);
160 }
161 }
162 }
163
164 if (callbacks) {
165 r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]);
166 if (r < 0)
167 return log_error_errno(r, "Callback two failed: %m");
168 }
169
170 while (!hashmap_isempty(pids)) {
171 _cleanup_free_ char *t = NULL;
172 pid_t pid;
173
174 pid = PTR_TO_PID(hashmap_first_key(pids));
175 assert(pid > 0);
176
177 t = hashmap_remove(pids, PID_TO_PTR(pid));
178 assert(t);
179
180 r = wait_for_terminate_and_check(t, pid, WAIT_LOG);
181 if (!FLAGS_SET(flags, EXEC_DIR_IGNORE_ERRORS) && r > 0)
182 return r;
183 }
184
185 return 0;
186 }
187
188 int execute_directories(
189 const char* const* directories,
190 usec_t timeout,
191 gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
192 void* const callback_args[_STDOUT_CONSUME_MAX],
193 char *argv[],
194 char *envp[],
195 ExecDirFlags flags) {
196
197 char **dirs = (char**) directories;
198 _cleanup_close_ int fd = -1;
199 char *name;
200 int r;
201 pid_t executor_pid;
202
203 assert(!strv_isempty(dirs));
204
205 name = basename(dirs[0]);
206 assert(!isempty(name));
207
208 if (callbacks) {
209 assert(callback_args);
210 assert(callbacks[STDOUT_GENERATE]);
211 assert(callbacks[STDOUT_COLLECT]);
212 assert(callbacks[STDOUT_CONSUME]);
213
214 fd = open_serialization_fd(name);
215 if (fd < 0)
216 return log_error_errno(fd, "Failed to open serialization file: %m");
217 }
218
219 /* Executes all binaries in the directories serially or in parallel and waits for
220 * them to finish. Optionally a timeout is applied. If a file with the same name
221 * exists in more than one directory, the earliest one wins. */
222
223 r = safe_fork("(sd-executor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &executor_pid);
224 if (r < 0)
225 return r;
226 if (r == 0) {
227 r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv, envp, flags);
228 _exit(r < 0 ? EXIT_FAILURE : r);
229 }
230
231 r = wait_for_terminate_and_check("(sd-executor)", executor_pid, 0);
232 if (r < 0)
233 return r;
234 if (!FLAGS_SET(flags, EXEC_DIR_IGNORE_ERRORS) && r > 0)
235 return r;
236
237 if (!callbacks)
238 return 0;
239
240 if (lseek(fd, 0, SEEK_SET) < 0)
241 return log_error_errno(errno, "Failed to rewind serialization fd: %m");
242
243 r = callbacks[STDOUT_CONSUME](fd, callback_args[STDOUT_CONSUME]);
244 fd = -1;
245 if (r < 0)
246 return log_error_errno(r, "Failed to parse returned data: %m");
247 return 0;
248 }
249
250 static int gather_environment_generate(int fd, void *arg) {
251 char ***env = arg, **x, **y;
252 _cleanup_fclose_ FILE *f = NULL;
253 _cleanup_strv_free_ char **new = NULL;
254 int r;
255
256 /* Read a series of VAR=value assignments from fd, use them to update the list of
257 * variables in env. Also update the exported environment.
258 *
259 * fd is always consumed, even on error.
260 */
261
262 assert(env);
263
264 f = fdopen(fd, "r");
265 if (!f) {
266 safe_close(fd);
267 return -errno;
268 }
269
270 r = load_env_file_pairs(f, NULL, &new);
271 if (r < 0)
272 return r;
273
274 STRV_FOREACH_PAIR(x, y, new) {
275 char *p;
276
277 if (!env_name_is_valid(*x)) {
278 log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x);
279 continue;
280 }
281
282 p = strjoin(*x, "=", *y);
283 if (!p)
284 return -ENOMEM;
285
286 r = strv_env_replace(env, p);
287 if (r < 0)
288 return r;
289
290 if (setenv(*x, *y, true) < 0)
291 return -errno;
292 }
293
294 return r;
295 }
296
297 static int gather_environment_collect(int fd, void *arg) {
298 _cleanup_fclose_ FILE *f = NULL;
299 char ***env = arg;
300 int r;
301
302 /* Write out a series of env=cescape(VAR=value) assignments to fd. */
303
304 assert(env);
305
306 f = fdopen(fd, "w");
307 if (!f) {
308 safe_close(fd);
309 return -errno;
310 }
311
312 r = serialize_strv(f, "env", *env);
313 if (r < 0)
314 return r;
315
316 r = fflush_and_check(f);
317 if (r < 0)
318 return r;
319
320 return 0;
321 }
322
323 static int gather_environment_consume(int fd, void *arg) {
324 _cleanup_fclose_ FILE *f = NULL;
325 char ***env = arg;
326 int r = 0;
327
328 /* Read a series of env=cescape(VAR=value) assignments from fd into env. */
329
330 assert(env);
331
332 f = fdopen(fd, "r");
333 if (!f) {
334 safe_close(fd);
335 return -errno;
336 }
337
338 for (;;) {
339 _cleanup_free_ char *line = NULL;
340 const char *v;
341 int k;
342
343 k = read_line(f, LONG_LINE_MAX, &line);
344 if (k < 0)
345 return k;
346 if (k == 0)
347 break;
348
349 v = startswith(line, "env=");
350 if (!v) {
351 log_debug("Serialization line \"%s\" unexpectedly didn't start with \"env=\".", line);
352 if (r == 0)
353 r = -EINVAL;
354
355 continue;
356 }
357
358 k = deserialize_environment(v, env);
359 if (k < 0) {
360 log_debug_errno(k, "Invalid serialization line \"%s\": %m", line);
361
362 if (r == 0)
363 r = k;
364 }
365 }
366
367 return r;
368 }
369
370 int exec_command_flags_from_strv(char **ex_opts, ExecCommandFlags *flags) {
371 ExecCommandFlags ex_flag, ret_flags = 0;
372 char **opt;
373
374 assert(flags);
375
376 STRV_FOREACH(opt, ex_opts) {
377 ex_flag = exec_command_flags_from_string(*opt);
378 if (ex_flag >= 0)
379 ret_flags |= ex_flag;
380 else
381 return -EINVAL;
382 }
383
384 *flags = ret_flags;
385
386 return 0;
387 }
388
389 int exec_command_flags_to_strv(ExecCommandFlags flags, char ***ex_opts) {
390 _cleanup_strv_free_ char **ret_opts = NULL;
391 ExecCommandFlags it = flags;
392 const char *str;
393 int i, r;
394
395 assert(ex_opts);
396
397 for (i = 0; it != 0; it &= ~(1 << i), i++) {
398 if (FLAGS_SET(flags, (1 << i))) {
399 str = exec_command_flags_to_string(1 << i);
400 if (!str)
401 return -EINVAL;
402
403 r = strv_extend(&ret_opts, str);
404 if (r < 0)
405 return r;
406 }
407 }
408
409 *ex_opts = TAKE_PTR(ret_opts);
410
411 return 0;
412 }
413
414 const gather_stdout_callback_t gather_environment[] = {
415 gather_environment_generate,
416 gather_environment_collect,
417 gather_environment_consume,
418 };
419
420 static const char* const exec_command_strings[] = {
421 "ignore-failure", /* EXEC_COMMAND_IGNORE_FAILURE */
422 "privileged", /* EXEC_COMMAND_FULLY_PRIVILEGED */
423 "no-setuid", /* EXEC_COMMAND_NO_SETUID */
424 "ambient", /* EXEC_COMMAND_AMBIENT_MAGIC */
425 "no-env-expand", /* EXEC_COMMAND_NO_ENV_EXPAND */
426 };
427
428 const char* exec_command_flags_to_string(ExecCommandFlags i) {
429 size_t idx;
430
431 for (idx = 0; idx < ELEMENTSOF(exec_command_strings); idx++)
432 if (i == (1 << idx))
433 return exec_command_strings[idx];
434
435 return NULL;
436 }
437
438 ExecCommandFlags exec_command_flags_from_string(const char *s) {
439 ssize_t idx;
440
441 idx = string_table_lookup(exec_command_strings, ELEMENTSOF(exec_command_strings), s);
442
443 if (idx < 0)
444 return _EXEC_COMMAND_FLAGS_INVALID;
445 else
446 return 1 << idx;
447 }
448
449 int fexecve_or_execve(int executable_fd, const char *executable, char *const argv[], char *const envp[]) {
450 #if ENABLE_FEXECVE
451 execveat(executable_fd, "", argv, envp, AT_EMPTY_PATH);
452
453 if (IN_SET(errno, ENOSYS, ENOENT) || ERRNO_IS_PRIVILEGE(errno))
454 /* Old kernel or a script or an overzealous seccomp filter? Let's fall back to execve().
455 *
456 * fexecve(3): "If fd refers to a script (i.e., it is an executable text file that names a
457 * script interpreter with a first line that begins with the characters #!) and the
458 * close-on-exec flag has been set for fd, then fexecve() fails with the error ENOENT. This
459 * error occurs because, by the time the script interpreter is executed, fd has already been
460 * closed because of the close-on-exec flag. Thus, the close-on-exec flag can't be set on fd
461 * if it refers to a script."
462 *
463 * Unfortunately, if we unset close-on-exec, the script will be executed just fine, but (at
464 * least in case of bash) the script name, $0, will be shown as /dev/fd/nnn, which breaks
465 * scripts which make use of $0. Thus, let's fall back to execve() in this case.
466 */
467 #endif
468 execve(executable, argv, envp);
469 return -errno;
470 }