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