]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/exec-util.c
json: add helpers for dealing with id128 + strv
[thirdparty/systemd.git] / src / shared / exec-util.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
89711996
ZJS
2
3#include <dirent.h>
4#include <errno.h>
5#include <sys/prctl.h>
6#include <sys/types.h>
7#include <unistd.h>
c6e47247 8#include <stdio.h>
89711996
ZJS
9
10#include "alloc-util.h"
2e4cfe65 11#include "conf-files.h"
686d13b9 12#include "env-file.h"
c6e47247 13#include "env-util.h"
89711996 14#include "exec-util.h"
c6e47247
ZJS
15#include "fd-util.h"
16#include "fileio.h"
89711996
ZJS
17#include "hashmap.h"
18#include "macro.h"
19#include "process-util.h"
595225af 20#include "rlimit-util.h"
d68c645b 21#include "serialize.h"
89711996
ZJS
22#include "set.h"
23#include "signal-util.h"
24#include "stat-util.h"
b3d59367 25#include "string-table.h"
89711996
ZJS
26#include "string-util.h"
27#include "strv.h"
c6e47247 28#include "terminal-util.h"
e4de7287 29#include "tmpfile-util.h"
89711996
ZJS
30#include "util.h"
31
32/* Put this test here for a lack of better place */
33assert_cc(EAGAIN == EWOULDBLOCK);
34
c6e47247
ZJS
35static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) {
36
cf55fc18 37 pid_t _pid;
4c253ed1 38 int r;
cf55fc18
ZJS
39
40 if (null_or_empty_path(path)) {
41 log_debug("%s is empty (a mask).", path);
42 return 0;
43 }
44
b6e1fff1 45 r = safe_fork("(direxec)", FORK_DEATHSIG|FORK_LOG, &_pid);
4c253ed1 46 if (r < 0)
b6e1fff1 47 return r;
4c253ed1 48 if (r == 0) {
cf55fc18
ZJS
49 char *_argv[2];
50
c6e47247 51 if (stdout_fd >= 0) {
2b33ab09
LP
52 r = rearrange_stdio(STDIN_FILENO, stdout_fd, STDERR_FILENO);
53 if (r < 0)
3554ef51 54 _exit(EXIT_FAILURE);
c6e47247
ZJS
55 }
56
595225af
LP
57 (void) rlimit_nofile_safe();
58
cf55fc18
ZJS
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
cf55fc18
ZJS
71 *pid = _pid;
72 return 1;
73}
74
c6e47247
ZJS
75static 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,
78ec1bb4 81 char *argv[],
4b05f0c9
MK
82 char *envp[],
83 ExecDirFlags flags) {
c6e47247 84
89711996 85 _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
2e4cfe65 86 _cleanup_strv_free_ char **paths = NULL;
78ec1bb4 87 char **path, **e;
2e4cfe65 88 int r;
4b05f0c9 89 bool parallel_execution;
89711996 90
c6e47247
ZJS
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 *
4b05f0c9
MK
94 * We attempt to perform parallel execution if configured by the user, however
95 * if `callbacks` is nonnull, execution must be serial.
c6e47247 96 */
4b05f0c9 97 parallel_execution = FLAGS_SET(flags, EXEC_DIR_PARALLEL) && !callbacks;
89711996 98
3e36211b 99 r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE|CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char* const*) directories);
2e4cfe65 100 if (r < 0)
653d4695 101 return log_error_errno(r, "Failed to enumerate executables: %m");
2e4cfe65 102
4b05f0c9 103 if (parallel_execution) {
c6e47247
ZJS
104 pids = hashmap_new(NULL);
105 if (!pids)
106 return log_oom();
107 }
108
5238e957 109 /* Abort execution of this process after the timeout. We simply rely on SIGALRM as
c6e47247
ZJS
110 * default action terminating the process, and turn on alarm(). */
111
112 if (timeout != USEC_INFINITY)
be6b0c21 113 alarm(DIV_ROUND_UP(timeout, USEC_PER_SEC));
89711996 114
78ec1bb4 115 STRV_FOREACH(e, envp)
8f7329ac 116 if (putenv(*e) != 0)
ed689f78 117 return log_error_errno(errno, "Failed to set environment variable: %m");
78ec1bb4 118
2e4cfe65
ZJS
119 STRV_FOREACH(path, paths) {
120 _cleanup_free_ char *t = NULL;
c6e47247 121 _cleanup_close_ int fd = -1;
2e4cfe65 122 pid_t pid;
89711996 123
2e4cfe65
ZJS
124 t = strdup(*path);
125 if (!t)
126 return log_oom();
89711996 127
c6e47247
ZJS
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);
2e4cfe65
ZJS
135 if (r <= 0)
136 continue;
89711996 137
4b05f0c9 138 if (parallel_execution) {
c6e47247
ZJS
139 r = hashmap_put(pids, PID_TO_PTR(pid), t);
140 if (r < 0)
141 return log_oom();
142 t = NULL;
143 } else {
7d4904fe 144 r = wait_for_terminate_and_check(t, pid, WAIT_LOG);
4b05f0c9
MK
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 }
c6e47247 160 }
89711996
ZJS
161 }
162
c6e47247
ZJS
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 }
89711996
ZJS
168
169 while (!hashmap_isempty(pids)) {
2e4cfe65 170 _cleanup_free_ char *t = NULL;
89711996
ZJS
171 pid_t pid;
172
173 pid = PTR_TO_PID(hashmap_first_key(pids));
174 assert(pid > 0);
175
2e4cfe65
ZJS
176 t = hashmap_remove(pids, PID_TO_PTR(pid));
177 assert(t);
89711996 178
4b05f0c9
MK
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;
89711996
ZJS
182 }
183
184 return 0;
185}
186
c6e47247
ZJS
187int 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],
78ec1bb4 192 char *argv[],
4b05f0c9
MK
193 char *envp[],
194 ExecDirFlags flags) {
c6e47247 195
89711996 196 char **dirs = (char**) directories;
c6e47247 197 _cleanup_close_ int fd = -1;
1f5d1e02 198 char *name;
c6e47247 199 int r;
4b05f0c9 200 pid_t executor_pid;
89711996
ZJS
201
202 assert(!strv_isempty(dirs));
203
204 name = basename(dirs[0]);
205 assert(!isempty(name));
206
c6e47247
ZJS
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. */
89711996 221
4b05f0c9 222 r = safe_fork("(sd-executor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &executor_pid);
4c253ed1 223 if (r < 0)
b6e1fff1 224 return r;
4c253ed1 225 if (r == 0) {
4b05f0c9
MK
226 r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv, envp, flags);
227 _exit(r < 0 ? EXIT_FAILURE : r);
89711996
ZJS
228 }
229
4b05f0c9
MK
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
c6e47247
ZJS
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;
89711996 247}
3303d1b2
ZJS
248
249static int gather_environment_generate(int fd, void *arg) {
250 char ***env = arg, **x, **y;
251 _cleanup_fclose_ FILE *f = NULL;
3f199740 252 _cleanup_strv_free_ char **new = NULL;
3303d1b2
ZJS
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
aa8fbc74 269 r = load_env_file_pairs(f, NULL, &new);
3303d1b2
ZJS
270 if (r < 0)
271 return r;
272
273 STRV_FOREACH_PAIR(x, y, new) {
274 char *p;
275
184d1904
ZJS
276 if (!env_name_is_valid(*x)) {
277 log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x);
278 continue;
279 }
280
3303d1b2
ZJS
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
296static int gather_environment_collect(int fd, void *arg) {
3303d1b2 297 _cleanup_fclose_ FILE *f = NULL;
c93d527f 298 char ***env = arg;
3303d1b2
ZJS
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
d68c645b 311 r = serialize_strv(f, "env", *env);
3303d1b2
ZJS
312 if (r < 0)
313 return r;
314
c93d527f
LP
315 r = fflush_and_check(f);
316 if (r < 0)
317 return r;
3303d1b2
ZJS
318
319 return 0;
320}
321
322static int gather_environment_consume(int fd, void *arg) {
3303d1b2 323 _cleanup_fclose_ FILE *f = NULL;
d68c645b
LP
324 char ***env = arg;
325 int r = 0;
3303d1b2
ZJS
326
327 /* Read a series of env=cescape(VAR=value) assignments from fd into env. */
328
329 assert(env);
330
e92aaed3 331 f = fdopen(fd, "r");
3303d1b2
ZJS
332 if (!f) {
333 safe_close(fd);
334 return -errno;
335 }
336
d68c645b
LP
337 for (;;) {
338 _cleanup_free_ char *line = NULL;
339 const char *v;
340 int k;
3303d1b2 341
d68c645b 342 k = read_line(f, LONG_LINE_MAX, &line);
3303d1b2 343 if (k < 0)
d68c645b
LP
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 }
3303d1b2
ZJS
364 }
365
366 return r;
367}
368
b3d59367
AZ
369int 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
388int 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
3303d1b2
ZJS
413const gather_stdout_callback_t gather_environment[] = {
414 gather_environment_generate,
415 gather_environment_collect,
416 gather_environment_consume,
417};
b3d59367
AZ
418
419static 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
427const 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
437ExecCommandFlags 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}