]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <errno.h> | |
4 | #include <fcntl.h> | |
5 | #include <poll.h> | |
6 | #include <sys/file.h> | |
7 | #include <sys/mman.h> | |
8 | #include <sys/personality.h> | |
9 | #include <sys/prctl.h> | |
10 | #include <sys/shm.h> | |
11 | #include <sys/types.h> | |
12 | #include <sys/un.h> | |
13 | #include <unistd.h> | |
14 | #include <utmpx.h> | |
15 | ||
16 | #include <linux/fs.h> /* Must be included after <sys/mount.h> */ | |
17 | ||
18 | #include "sd-messages.h" | |
19 | ||
20 | #include "af-list.h" | |
21 | #include "alloc-util.h" | |
22 | #include "async.h" | |
23 | #include "cap-list.h" | |
24 | #include "capability-util.h" | |
25 | #include "cgroup-setup.h" | |
26 | #include "constants.h" | |
27 | #include "cpu-set-util.h" | |
28 | #include "dev-setup.h" | |
29 | #include "env-file.h" | |
30 | #include "env-util.h" | |
31 | #include "errno-list.h" | |
32 | #include "escape.h" | |
33 | #include "exec-credential.h" | |
34 | #include "execute.h" | |
35 | #include "execute-serialize.h" | |
36 | #include "exit-status.h" | |
37 | #include "fd-util.h" | |
38 | #include "fileio.h" | |
39 | #include "format-util.h" | |
40 | #include "glob-util.h" | |
41 | #include "hexdecoct.h" | |
42 | #include "ioprio-util.h" | |
43 | #include "lock-util.h" | |
44 | #include "log.h" | |
45 | #include "macro.h" | |
46 | #include "manager.h" | |
47 | #include "manager-dump.h" | |
48 | #include "memory-util.h" | |
49 | #include "missing_fs.h" | |
50 | #include "missing_prctl.h" | |
51 | #include "mkdir-label.h" | |
52 | #include "namespace.h" | |
53 | #include "parse-util.h" | |
54 | #include "path-util.h" | |
55 | #include "process-util.h" | |
56 | #include "rlimit-util.h" | |
57 | #include "rm-rf.h" | |
58 | #include "seccomp-util.h" | |
59 | #include "securebits-util.h" | |
60 | #include "selinux-util.h" | |
61 | #include "serialize.h" | |
62 | #include "sort-util.h" | |
63 | #include "special.h" | |
64 | #include "stat-util.h" | |
65 | #include "string-table.h" | |
66 | #include "string-util.h" | |
67 | #include "strv.h" | |
68 | #include "syslog-util.h" | |
69 | #include "terminal-util.h" | |
70 | #include "tmpfile-util.h" | |
71 | #include "umask-util.h" | |
72 | #include "unit-serialize.h" | |
73 | #include "user-util.h" | |
74 | #include "utmp-wtmp.h" | |
75 | ||
76 | static bool is_terminal_input(ExecInput i) { | |
77 | return IN_SET(i, | |
78 | EXEC_INPUT_TTY, | |
79 | EXEC_INPUT_TTY_FORCE, | |
80 | EXEC_INPUT_TTY_FAIL); | |
81 | } | |
82 | ||
83 | static bool is_terminal_output(ExecOutput o) { | |
84 | return IN_SET(o, | |
85 | EXEC_OUTPUT_TTY, | |
86 | EXEC_OUTPUT_KMSG_AND_CONSOLE, | |
87 | EXEC_OUTPUT_JOURNAL_AND_CONSOLE); | |
88 | } | |
89 | ||
90 | const char *exec_context_tty_path(const ExecContext *context) { | |
91 | assert(context); | |
92 | ||
93 | if (context->stdio_as_fds) | |
94 | return NULL; | |
95 | ||
96 | if (context->tty_path) | |
97 | return context->tty_path; | |
98 | ||
99 | return "/dev/console"; | |
100 | } | |
101 | ||
102 | static void exec_context_determine_tty_size( | |
103 | const ExecContext *context, | |
104 | const char *tty_path, | |
105 | unsigned *ret_rows, | |
106 | unsigned *ret_cols) { | |
107 | ||
108 | unsigned rows, cols; | |
109 | ||
110 | assert(context); | |
111 | assert(ret_rows); | |
112 | assert(ret_cols); | |
113 | ||
114 | if (!tty_path) | |
115 | tty_path = exec_context_tty_path(context); | |
116 | ||
117 | rows = context->tty_rows; | |
118 | cols = context->tty_cols; | |
119 | ||
120 | if (tty_path && (rows == UINT_MAX || cols == UINT_MAX)) | |
121 | (void) proc_cmdline_tty_size( | |
122 | tty_path, | |
123 | rows == UINT_MAX ? &rows : NULL, | |
124 | cols == UINT_MAX ? &cols : NULL); | |
125 | ||
126 | *ret_rows = rows; | |
127 | *ret_cols = cols; | |
128 | } | |
129 | ||
130 | int exec_context_apply_tty_size( | |
131 | const ExecContext *context, | |
132 | int tty_fd, | |
133 | const char *tty_path) { | |
134 | ||
135 | unsigned rows, cols; | |
136 | ||
137 | exec_context_determine_tty_size(context, tty_path, &rows, &cols); | |
138 | ||
139 | return terminal_set_size_fd(tty_fd, tty_path, rows, cols); | |
140 | } | |
141 | ||
142 | void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) { | |
143 | _cleanup_close_ int _fd = -EBADF, lock_fd = -EBADF; | |
144 | int fd; | |
145 | ||
146 | assert(context); | |
147 | ||
148 | const char *path = exec_context_tty_path(context); | |
149 | ||
150 | if (p && p->stdin_fd >= 0 && isatty_safe(p->stdin_fd)) | |
151 | fd = p->stdin_fd; | |
152 | else if (path && (context->tty_path || is_terminal_input(context->std_input) || | |
153 | is_terminal_output(context->std_output) || is_terminal_output(context->std_error))) { | |
154 | fd = _fd = open_terminal(path, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); | |
155 | if (fd < 0) | |
156 | return (void) log_debug_errno(fd, "Failed to open terminal '%s', ignoring: %m", path); | |
157 | } else | |
158 | return; /* nothing to do */ | |
159 | ||
160 | /* Take a synchronization lock for the duration of the setup that we do here. | |
161 | * systemd-vconsole-setup.service also takes the lock to avoid being interrupted. We open a new fd | |
162 | * that will be closed automatically, and operate on it for convenience. */ | |
163 | lock_fd = lock_dev_console(); | |
164 | if (ERRNO_IS_NEG_PRIVILEGE(lock_fd)) | |
165 | log_debug_errno(lock_fd, "No privileges to lock /dev/console, proceeding without: %m"); | |
166 | else if (ERRNO_IS_NEG_DEVICE_ABSENT(lock_fd)) | |
167 | log_debug_errno(lock_fd, "Device /dev/console does not exist, proceeding without locking it: %m"); | |
168 | else if (lock_fd < 0) | |
169 | return (void) log_debug_errno(lock_fd, "Failed to lock /dev/console: %m"); | |
170 | ||
171 | if (context->tty_vhangup) | |
172 | (void) terminal_vhangup_fd(fd); | |
173 | ||
174 | if (context->tty_reset) | |
175 | (void) reset_terminal_fd(fd, /* switch_to_text= */ true); | |
176 | ||
177 | (void) exec_context_apply_tty_size(context, fd, path); | |
178 | ||
179 | if (context->tty_vt_disallocate && path) | |
180 | (void) vt_disallocate(path); | |
181 | } | |
182 | ||
183 | bool exec_needs_network_namespace(const ExecContext *context) { | |
184 | assert(context); | |
185 | ||
186 | return context->private_network || context->network_namespace_path; | |
187 | } | |
188 | ||
189 | static bool exec_needs_ephemeral(const ExecContext *context) { | |
190 | return (context->root_image || context->root_directory) && context->root_ephemeral; | |
191 | } | |
192 | ||
193 | bool exec_needs_ipc_namespace(const ExecContext *context) { | |
194 | assert(context); | |
195 | ||
196 | return context->private_ipc || context->ipc_namespace_path; | |
197 | } | |
198 | ||
199 | bool exec_needs_mount_namespace( | |
200 | const ExecContext *context, | |
201 | const ExecParameters *params, | |
202 | const ExecRuntime *runtime) { | |
203 | ||
204 | assert(context); | |
205 | ||
206 | if (context->root_image) | |
207 | return true; | |
208 | ||
209 | if (!strv_isempty(context->read_write_paths) || | |
210 | !strv_isempty(context->read_only_paths) || | |
211 | !strv_isempty(context->inaccessible_paths) || | |
212 | !strv_isempty(context->exec_paths) || | |
213 | !strv_isempty(context->no_exec_paths)) | |
214 | return true; | |
215 | ||
216 | if (context->n_bind_mounts > 0) | |
217 | return true; | |
218 | ||
219 | if (context->n_temporary_filesystems > 0) | |
220 | return true; | |
221 | ||
222 | if (context->n_mount_images > 0) | |
223 | return true; | |
224 | ||
225 | if (context->n_extension_images > 0) | |
226 | return true; | |
227 | ||
228 | if (!strv_isempty(context->extension_directories)) | |
229 | return true; | |
230 | ||
231 | if (!IN_SET(context->mount_propagation_flag, 0, MS_SHARED)) | |
232 | return true; | |
233 | ||
234 | if (context->private_tmp && runtime && runtime->shared && (runtime->shared->tmp_dir || runtime->shared->var_tmp_dir)) | |
235 | return true; | |
236 | ||
237 | if (context->private_devices || | |
238 | context->private_mounts > 0 || | |
239 | (context->private_mounts < 0 && exec_needs_network_namespace(context)) || | |
240 | context->protect_system != PROTECT_SYSTEM_NO || | |
241 | context->protect_home != PROTECT_HOME_NO || | |
242 | context->protect_kernel_tunables || | |
243 | context->protect_kernel_modules || | |
244 | context->protect_kernel_logs || | |
245 | context->protect_control_groups || | |
246 | context->protect_proc != PROTECT_PROC_DEFAULT || | |
247 | context->proc_subset != PROC_SUBSET_ALL || | |
248 | exec_needs_ipc_namespace(context)) | |
249 | return true; | |
250 | ||
251 | if (context->root_directory) { | |
252 | if (exec_context_get_effective_mount_apivfs(context)) | |
253 | return true; | |
254 | ||
255 | for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) { | |
256 | if (params && !params->prefix[t]) | |
257 | continue; | |
258 | ||
259 | if (context->directories[t].n_items > 0) | |
260 | return true; | |
261 | } | |
262 | } | |
263 | ||
264 | if (context->dynamic_user && | |
265 | (context->directories[EXEC_DIRECTORY_STATE].n_items > 0 || | |
266 | context->directories[EXEC_DIRECTORY_CACHE].n_items > 0 || | |
267 | context->directories[EXEC_DIRECTORY_LOGS].n_items > 0)) | |
268 | return true; | |
269 | ||
270 | if (context->log_namespace) | |
271 | return true; | |
272 | ||
273 | return false; | |
274 | } | |
275 | ||
276 | bool exec_directory_is_private(const ExecContext *context, ExecDirectoryType type) { | |
277 | assert(context); | |
278 | ||
279 | if (!context->dynamic_user) | |
280 | return false; | |
281 | ||
282 | if (type == EXEC_DIRECTORY_CONFIGURATION) | |
283 | return false; | |
284 | ||
285 | if (type == EXEC_DIRECTORY_RUNTIME && context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO) | |
286 | return false; | |
287 | ||
288 | return true; | |
289 | } | |
290 | ||
291 | int exec_params_get_cgroup_path( | |
292 | const ExecParameters *params, | |
293 | const CGroupContext *c, | |
294 | char **ret) { | |
295 | ||
296 | const char *subgroup = NULL; | |
297 | char *p; | |
298 | ||
299 | assert(params); | |
300 | assert(ret); | |
301 | ||
302 | if (!params->cgroup_path) | |
303 | return -EINVAL; | |
304 | ||
305 | /* If we are called for a unit where cgroup delegation is on, and the payload created its own populated | |
306 | * subcgroup (which we expect it to do, after all it asked for delegation), then we cannot place the control | |
307 | * processes started after the main unit's process in the unit's main cgroup because it is now an inner one, | |
308 | * and inner cgroups may not contain processes. Hence, if delegation is on, and this is a control process, | |
309 | * let's use ".control" as subcgroup instead. Note that we do so only for ExecStartPost=, ExecReload=, | |
310 | * ExecStop=, ExecStopPost=, i.e. for the commands where the main process is already forked. For ExecStartPre= | |
311 | * this is not necessary, the cgroup is still empty. We distinguish these cases with the EXEC_CONTROL_CGROUP | |
312 | * flag, which is only passed for the former statements, not for the latter. */ | |
313 | ||
314 | if (FLAGS_SET(params->flags, EXEC_CGROUP_DELEGATE) && (FLAGS_SET(params->flags, EXEC_CONTROL_CGROUP) || c->delegate_subgroup)) { | |
315 | if (FLAGS_SET(params->flags, EXEC_IS_CONTROL)) | |
316 | subgroup = ".control"; | |
317 | else | |
318 | subgroup = c->delegate_subgroup; | |
319 | } | |
320 | ||
321 | if (subgroup) | |
322 | p = path_join(params->cgroup_path, subgroup); | |
323 | else | |
324 | p = strdup(params->cgroup_path); | |
325 | if (!p) | |
326 | return -ENOMEM; | |
327 | ||
328 | *ret = p; | |
329 | return !!subgroup; | |
330 | } | |
331 | ||
332 | bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c) { | |
333 | assert(c); | |
334 | ||
335 | return c->cpu_affinity_from_numa; | |
336 | } | |
337 | ||
338 | static void log_command_line(Unit *unit, const char *msg, const char *executable, char **argv) { | |
339 | assert(unit); | |
340 | assert(msg); | |
341 | assert(executable); | |
342 | ||
343 | if (!DEBUG_LOGGING) | |
344 | return; | |
345 | ||
346 | _cleanup_free_ char *cmdline = quote_command_line(argv, SHELL_ESCAPE_EMPTY); | |
347 | ||
348 | log_unit_struct(unit, LOG_DEBUG, | |
349 | "EXECUTABLE=%s", executable, | |
350 | LOG_UNIT_MESSAGE(unit, "%s: %s", msg, strnull(cmdline)), | |
351 | LOG_UNIT_INVOCATION_ID(unit)); | |
352 | } | |
353 | ||
354 | static int exec_context_load_environment(const Unit *unit, const ExecContext *c, char ***l); | |
355 | ||
356 | int exec_spawn(Unit *unit, | |
357 | ExecCommand *command, | |
358 | const ExecContext *context, | |
359 | ExecParameters *params, | |
360 | ExecRuntime *runtime, | |
361 | const CGroupContext *cgroup_context, | |
362 | PidRef *ret) { | |
363 | ||
364 | char serialization_fd_number[DECIMAL_STR_MAX(int) + 1]; | |
365 | _cleanup_free_ char *subcgroup_path = NULL, *max_log_levels = NULL, *executor_path = NULL; | |
366 | _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; | |
367 | _cleanup_fdset_free_ FDSet *fdset = NULL; | |
368 | _cleanup_fclose_ FILE *f = NULL; | |
369 | int r; | |
370 | ||
371 | assert(unit); | |
372 | assert(unit->manager); | |
373 | assert(unit->manager->executor_fd >= 0); | |
374 | assert(command); | |
375 | assert(context); | |
376 | assert(params); | |
377 | assert(!params->fds || FLAGS_SET(params->flags, EXEC_PASS_FDS)); | |
378 | assert(params->fds || (params->n_socket_fds + params->n_storage_fds == 0)); | |
379 | assert(!params->files_env); /* We fill this field, ensure it comes NULL-initialized to us */ | |
380 | assert(ret); | |
381 | ||
382 | LOG_CONTEXT_PUSH_UNIT(unit); | |
383 | ||
384 | r = exec_context_load_environment(unit, context, ¶ms->files_env); | |
385 | if (r < 0) | |
386 | return log_unit_error_errno(unit, r, "Failed to load environment files: %m"); | |
387 | ||
388 | /* We won't know the real executable path until we create the mount namespace in the child, but we | |
389 | want to log from the parent, so we use the possibly inaccurate path here. */ | |
390 | log_command_line(unit, "About to execute", command->path, command->argv); | |
391 | ||
392 | if (params->cgroup_path) { | |
393 | r = exec_params_get_cgroup_path(params, cgroup_context, &subcgroup_path); | |
394 | if (r < 0) | |
395 | return log_unit_error_errno(unit, r, "Failed to acquire subcgroup path: %m"); | |
396 | if (r > 0) { | |
397 | /* If there's a subcgroup, then let's create it here now (the main cgroup was already | |
398 | * realized by the unit logic) */ | |
399 | ||
400 | r = cg_create(SYSTEMD_CGROUP_CONTROLLER, subcgroup_path); | |
401 | if (r < 0) | |
402 | return log_unit_error_errno(unit, r, "Failed to create subcgroup '%s': %m", subcgroup_path); | |
403 | } | |
404 | } | |
405 | ||
406 | /* In order to avoid copy-on-write traps and OOM-kills when pid1's memory.current is above the | |
407 | * child's memory.max, serialize all the state needed to start the unit, and pass it to the | |
408 | * systemd-executor binary. clone() with CLONE_VM + CLONE_VFORK will pause the parent until the exec | |
409 | * and ensure all memory is shared. The child immediately execs the new binary so the delay should | |
410 | * be minimal. If glibc 2.39 is available pidfd_spawn() is used in order to get a race-free pid fd | |
411 | * and to clone directly into the target cgroup (if we booted with cgroupv2). */ | |
412 | ||
413 | r = open_serialization_file("sd-executor-state", &f); | |
414 | if (r < 0) | |
415 | return log_unit_error_errno(unit, r, "Failed to open serialization stream: %m"); | |
416 | ||
417 | fdset = fdset_new(); | |
418 | if (!fdset) | |
419 | return log_oom(); | |
420 | ||
421 | r = exec_serialize_invocation(f, fdset, context, command, params, runtime, cgroup_context); | |
422 | if (r < 0) | |
423 | return log_unit_error_errno(unit, r, "Failed to serialize parameters: %m"); | |
424 | ||
425 | if (fseeko(f, 0, SEEK_SET) < 0) | |
426 | return log_unit_error_errno(unit, errno, "Failed to reseek on serialization stream: %m"); | |
427 | ||
428 | r = fd_cloexec(fileno(f), false); | |
429 | if (r < 0) | |
430 | return log_unit_error_errno(unit, r, "Failed to set O_CLOEXEC on serialization fd: %m"); | |
431 | ||
432 | r = fdset_cloexec(fdset, false); | |
433 | if (r < 0) | |
434 | return log_unit_error_errno(unit, r, "Failed to set O_CLOEXEC on serialized fds: %m"); | |
435 | ||
436 | /* If LogLevelMax= is specified, then let's use the specified log level at the beginning of the | |
437 | * executor process. To achieve that the specified log level is passed as an argument, rather than | |
438 | * the one for the manager process. */ | |
439 | r = log_max_levels_to_string(context->log_level_max >= 0 ? context->log_level_max : log_get_max_level(), &max_log_levels); | |
440 | if (r < 0) | |
441 | return log_unit_error_errno(unit, r, "Failed to convert max log levels to string: %m"); | |
442 | ||
443 | r = fd_get_path(unit->manager->executor_fd, &executor_path); | |
444 | if (r < 0) | |
445 | return log_unit_error_errno(unit, r, "Failed to get executor path from fd: %m"); | |
446 | ||
447 | xsprintf(serialization_fd_number, "%i", fileno(f)); | |
448 | ||
449 | /* The executor binary is pinned, to avoid compatibility problems during upgrades. */ | |
450 | r = posix_spawn_wrapper( | |
451 | FORMAT_PROC_FD_PATH(unit->manager->executor_fd), | |
452 | STRV_MAKE(executor_path, | |
453 | "--deserialize", serialization_fd_number, | |
454 | "--log-level", max_log_levels, | |
455 | "--log-target", log_target_to_string(manager_get_executor_log_target(unit->manager))), | |
456 | environ, | |
457 | cg_unified() > 0 ? subcgroup_path : NULL, | |
458 | &pidref); | |
459 | if (r == -EUCLEAN && subcgroup_path) | |
460 | return log_unit_error_errno(unit, r, | |
461 | "Failed to spawn process into cgroup '%s', because the cgroup " | |
462 | "or one of its parents or siblings is in the threaded mode.", | |
463 | subcgroup_path); | |
464 | if (r < 0) | |
465 | return log_unit_error_errno(unit, r, "Failed to spawn executor: %m"); | |
466 | /* We add the new process to the cgroup both in the child (so that we can be sure that no user code is ever | |
467 | * executed outside of the cgroup) and in the parent (so that we can be sure that when we kill the cgroup the | |
468 | * process will be killed too). */ | |
469 | if (r == 0 && subcgroup_path) | |
470 | (void) cg_attach(SYSTEMD_CGROUP_CONTROLLER, subcgroup_path, pidref.pid); | |
471 | /* r > 0: Already in the right cgroup thanks to CLONE_INTO_CGROUP */ | |
472 | ||
473 | log_unit_debug(unit, "Forked %s as " PID_FMT " (%s CLONE_INTO_CGROUP)", | |
474 | command->path, pidref.pid, r > 0 ? "via" : "without"); | |
475 | ||
476 | exec_status_start(&command->exec_status, pidref.pid); | |
477 | ||
478 | *ret = TAKE_PIDREF(pidref); | |
479 | return 0; | |
480 | } | |
481 | ||
482 | void exec_context_init(ExecContext *c) { | |
483 | assert(c); | |
484 | ||
485 | /* When initializing a bool member to 'true', make sure to serialize in execute-serialize.c using | |
486 | * serialize_bool() instead of serialize_bool_elide(). */ | |
487 | ||
488 | *c = (ExecContext) { | |
489 | .umask = 0022, | |
490 | .ioprio = IOPRIO_DEFAULT_CLASS_AND_PRIO, | |
491 | .cpu_sched_policy = SCHED_OTHER, | |
492 | .syslog_priority = LOG_DAEMON|LOG_INFO, | |
493 | .syslog_level_prefix = true, | |
494 | .ignore_sigpipe = true, | |
495 | .timer_slack_nsec = NSEC_INFINITY, | |
496 | .personality = PERSONALITY_INVALID, | |
497 | .timeout_clean_usec = USEC_INFINITY, | |
498 | .capability_bounding_set = CAP_MASK_UNSET, | |
499 | .restrict_namespaces = NAMESPACE_FLAGS_INITIAL, | |
500 | .log_level_max = -1, | |
501 | #if HAVE_SECCOMP | |
502 | .syscall_errno = SECCOMP_ERROR_NUMBER_KILL, | |
503 | #endif | |
504 | .tty_rows = UINT_MAX, | |
505 | .tty_cols = UINT_MAX, | |
506 | .private_mounts = -1, | |
507 | .memory_ksm = -1, | |
508 | .set_login_environment = -1, | |
509 | }; | |
510 | ||
511 | FOREACH_ARRAY(d, c->directories, _EXEC_DIRECTORY_TYPE_MAX) | |
512 | d->mode = 0755; | |
513 | ||
514 | numa_policy_reset(&c->numa_policy); | |
515 | ||
516 | assert_cc(NAMESPACE_FLAGS_INITIAL != NAMESPACE_FLAGS_ALL); | |
517 | } | |
518 | ||
519 | void exec_context_done(ExecContext *c) { | |
520 | assert(c); | |
521 | ||
522 | c->environment = strv_free(c->environment); | |
523 | c->environment_files = strv_free(c->environment_files); | |
524 | c->pass_environment = strv_free(c->pass_environment); | |
525 | c->unset_environment = strv_free(c->unset_environment); | |
526 | ||
527 | rlimit_free_all(c->rlimit); | |
528 | ||
529 | for (size_t l = 0; l < 3; l++) { | |
530 | c->stdio_fdname[l] = mfree(c->stdio_fdname[l]); | |
531 | c->stdio_file[l] = mfree(c->stdio_file[l]); | |
532 | } | |
533 | ||
534 | c->working_directory = mfree(c->working_directory); | |
535 | c->root_directory = mfree(c->root_directory); | |
536 | c->root_image = mfree(c->root_image); | |
537 | c->root_image_options = mount_options_free_all(c->root_image_options); | |
538 | c->root_hash = mfree(c->root_hash); | |
539 | c->root_hash_size = 0; | |
540 | c->root_hash_path = mfree(c->root_hash_path); | |
541 | c->root_hash_sig = mfree(c->root_hash_sig); | |
542 | c->root_hash_sig_size = 0; | |
543 | c->root_hash_sig_path = mfree(c->root_hash_sig_path); | |
544 | c->root_verity = mfree(c->root_verity); | |
545 | c->extension_images = mount_image_free_many(c->extension_images, &c->n_extension_images); | |
546 | c->extension_directories = strv_free(c->extension_directories); | |
547 | c->tty_path = mfree(c->tty_path); | |
548 | c->syslog_identifier = mfree(c->syslog_identifier); | |
549 | c->user = mfree(c->user); | |
550 | c->group = mfree(c->group); | |
551 | ||
552 | c->supplementary_groups = strv_free(c->supplementary_groups); | |
553 | ||
554 | c->pam_name = mfree(c->pam_name); | |
555 | ||
556 | c->read_only_paths = strv_free(c->read_only_paths); | |
557 | c->read_write_paths = strv_free(c->read_write_paths); | |
558 | c->inaccessible_paths = strv_free(c->inaccessible_paths); | |
559 | c->exec_paths = strv_free(c->exec_paths); | |
560 | c->no_exec_paths = strv_free(c->no_exec_paths); | |
561 | c->exec_search_path = strv_free(c->exec_search_path); | |
562 | ||
563 | bind_mount_free_many(c->bind_mounts, c->n_bind_mounts); | |
564 | c->bind_mounts = NULL; | |
565 | c->n_bind_mounts = 0; | |
566 | temporary_filesystem_free_many(c->temporary_filesystems, c->n_temporary_filesystems); | |
567 | c->temporary_filesystems = NULL; | |
568 | c->n_temporary_filesystems = 0; | |
569 | c->mount_images = mount_image_free_many(c->mount_images, &c->n_mount_images); | |
570 | ||
571 | cpu_set_reset(&c->cpu_set); | |
572 | numa_policy_reset(&c->numa_policy); | |
573 | ||
574 | c->utmp_id = mfree(c->utmp_id); | |
575 | c->selinux_context = mfree(c->selinux_context); | |
576 | c->apparmor_profile = mfree(c->apparmor_profile); | |
577 | c->smack_process_label = mfree(c->smack_process_label); | |
578 | ||
579 | c->restrict_filesystems = set_free_free(c->restrict_filesystems); | |
580 | ||
581 | c->syscall_filter = hashmap_free(c->syscall_filter); | |
582 | c->syscall_archs = set_free(c->syscall_archs); | |
583 | c->address_families = set_free(c->address_families); | |
584 | ||
585 | FOREACH_ARRAY(d, c->directories, _EXEC_DIRECTORY_TYPE_MAX) | |
586 | exec_directory_done(d); | |
587 | ||
588 | c->log_level_max = -1; | |
589 | ||
590 | exec_context_free_log_extra_fields(c); | |
591 | c->log_filter_allowed_patterns = set_free_free(c->log_filter_allowed_patterns); | |
592 | c->log_filter_denied_patterns = set_free_free(c->log_filter_denied_patterns); | |
593 | ||
594 | c->log_ratelimit_interval_usec = 0; | |
595 | c->log_ratelimit_burst = 0; | |
596 | ||
597 | c->stdin_data = mfree(c->stdin_data); | |
598 | c->stdin_data_size = 0; | |
599 | ||
600 | c->network_namespace_path = mfree(c->network_namespace_path); | |
601 | c->ipc_namespace_path = mfree(c->ipc_namespace_path); | |
602 | ||
603 | c->log_namespace = mfree(c->log_namespace); | |
604 | ||
605 | c->load_credentials = hashmap_free(c->load_credentials); | |
606 | c->set_credentials = hashmap_free(c->set_credentials); | |
607 | c->import_credentials = set_free_free(c->import_credentials); | |
608 | ||
609 | c->root_image_policy = image_policy_free(c->root_image_policy); | |
610 | c->mount_image_policy = image_policy_free(c->mount_image_policy); | |
611 | c->extension_image_policy = image_policy_free(c->extension_image_policy); | |
612 | } | |
613 | ||
614 | int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) { | |
615 | assert(c); | |
616 | ||
617 | if (!runtime_prefix) | |
618 | return 0; | |
619 | ||
620 | FOREACH_ARRAY(i, c->directories[EXEC_DIRECTORY_RUNTIME].items, c->directories[EXEC_DIRECTORY_RUNTIME].n_items) { | |
621 | _cleanup_free_ char *p = NULL; | |
622 | ||
623 | if (exec_directory_is_private(c, EXEC_DIRECTORY_RUNTIME)) | |
624 | p = path_join(runtime_prefix, "private", i->path); | |
625 | else | |
626 | p = path_join(runtime_prefix, i->path); | |
627 | if (!p) | |
628 | return -ENOMEM; | |
629 | ||
630 | /* We execute this synchronously, since we need to be sure this is gone when we start the | |
631 | * service next. */ | |
632 | (void) rm_rf(p, REMOVE_ROOT); | |
633 | ||
634 | STRV_FOREACH(symlink, i->symlinks) { | |
635 | _cleanup_free_ char *symlink_abs = NULL; | |
636 | ||
637 | if (exec_directory_is_private(c, EXEC_DIRECTORY_RUNTIME)) | |
638 | symlink_abs = path_join(runtime_prefix, "private", *symlink); | |
639 | else | |
640 | symlink_abs = path_join(runtime_prefix, *symlink); | |
641 | if (!symlink_abs) | |
642 | return -ENOMEM; | |
643 | ||
644 | (void) unlink(symlink_abs); | |
645 | } | |
646 | } | |
647 | ||
648 | return 0; | |
649 | } | |
650 | ||
651 | int exec_context_destroy_mount_ns_dir(Unit *u) { | |
652 | _cleanup_free_ char *p = NULL; | |
653 | ||
654 | if (!u || !MANAGER_IS_SYSTEM(u->manager)) | |
655 | return 0; | |
656 | ||
657 | p = path_join("/run/systemd/propagate/", u->id); | |
658 | if (!p) | |
659 | return -ENOMEM; | |
660 | ||
661 | /* This is only filled transiently (see mount_in_namespace()), should be empty or even non-existent*/ | |
662 | if (rmdir(p) < 0 && errno != ENOENT) | |
663 | log_unit_debug_errno(u, errno, "Unable to remove propagation dir '%s', ignoring: %m", p); | |
664 | ||
665 | return 0; | |
666 | } | |
667 | ||
668 | void exec_command_done(ExecCommand *c) { | |
669 | assert(c); | |
670 | ||
671 | c->path = mfree(c->path); | |
672 | c->argv = strv_free(c->argv); | |
673 | } | |
674 | ||
675 | void exec_command_done_array(ExecCommand *c, size_t n) { | |
676 | FOREACH_ARRAY(i, c, n) | |
677 | exec_command_done(i); | |
678 | } | |
679 | ||
680 | ExecCommand* exec_command_free(ExecCommand *c) { | |
681 | if (!c) | |
682 | return NULL; | |
683 | ||
684 | exec_command_done(c); | |
685 | return mfree(c); | |
686 | } | |
687 | ||
688 | ExecCommand* exec_command_free_list(ExecCommand *c) { | |
689 | ExecCommand *i; | |
690 | ||
691 | while ((i = LIST_POP(command, c))) | |
692 | exec_command_free(i); | |
693 | ||
694 | return NULL; | |
695 | } | |
696 | ||
697 | void exec_command_free_array(ExecCommand **c, size_t n) { | |
698 | FOREACH_ARRAY(i, c, n) | |
699 | *i = exec_command_free_list(*i); | |
700 | } | |
701 | ||
702 | void exec_command_reset_status_array(ExecCommand *c, size_t n) { | |
703 | FOREACH_ARRAY(i, c, n) | |
704 | exec_status_reset(&i->exec_status); | |
705 | } | |
706 | ||
707 | void exec_command_reset_status_list_array(ExecCommand **c, size_t n) { | |
708 | FOREACH_ARRAY(i, c, n) | |
709 | LIST_FOREACH(command, z, *i) | |
710 | exec_status_reset(&z->exec_status); | |
711 | } | |
712 | ||
713 | typedef struct InvalidEnvInfo { | |
714 | const Unit *unit; | |
715 | const char *path; | |
716 | } InvalidEnvInfo; | |
717 | ||
718 | static void invalid_env(const char *p, void *userdata) { | |
719 | InvalidEnvInfo *info = userdata; | |
720 | ||
721 | log_unit_error(info->unit, "Ignoring invalid environment assignment '%s': %s", p, info->path); | |
722 | } | |
723 | ||
724 | const char* exec_context_fdname(const ExecContext *c, int fd_index) { | |
725 | assert(c); | |
726 | ||
727 | switch (fd_index) { | |
728 | ||
729 | case STDIN_FILENO: | |
730 | if (c->std_input != EXEC_INPUT_NAMED_FD) | |
731 | return NULL; | |
732 | ||
733 | return c->stdio_fdname[STDIN_FILENO] ?: "stdin"; | |
734 | ||
735 | case STDOUT_FILENO: | |
736 | if (c->std_output != EXEC_OUTPUT_NAMED_FD) | |
737 | return NULL; | |
738 | ||
739 | return c->stdio_fdname[STDOUT_FILENO] ?: "stdout"; | |
740 | ||
741 | case STDERR_FILENO: | |
742 | if (c->std_error != EXEC_OUTPUT_NAMED_FD) | |
743 | return NULL; | |
744 | ||
745 | return c->stdio_fdname[STDERR_FILENO] ?: "stderr"; | |
746 | ||
747 | default: | |
748 | return NULL; | |
749 | } | |
750 | } | |
751 | ||
752 | static int exec_context_load_environment(const Unit *unit, const ExecContext *c, char ***ret) { | |
753 | _cleanup_strv_free_ char **v = NULL; | |
754 | int r; | |
755 | ||
756 | assert(c); | |
757 | assert(ret); | |
758 | ||
759 | STRV_FOREACH(i, c->environment_files) { | |
760 | _cleanup_globfree_ glob_t pglob = {}; | |
761 | bool ignore = false; | |
762 | char *fn = *i; | |
763 | ||
764 | if (fn[0] == '-') { | |
765 | ignore = true; | |
766 | fn++; | |
767 | } | |
768 | ||
769 | if (!path_is_absolute(fn)) { | |
770 | if (ignore) | |
771 | continue; | |
772 | return -EINVAL; | |
773 | } | |
774 | ||
775 | /* Filename supports globbing, take all matching files */ | |
776 | r = safe_glob(fn, 0, &pglob); | |
777 | if (r < 0) { | |
778 | if (ignore) | |
779 | continue; | |
780 | return r; | |
781 | } | |
782 | ||
783 | /* When we don't match anything, -ENOENT should be returned */ | |
784 | assert(pglob.gl_pathc > 0); | |
785 | ||
786 | FOREACH_ARRAY(path, pglob.gl_pathv, pglob.gl_pathc) { | |
787 | _cleanup_strv_free_ char **p = NULL; | |
788 | ||
789 | r = load_env_file(NULL, *path, &p); | |
790 | if (r < 0) { | |
791 | if (ignore) | |
792 | continue; | |
793 | return r; | |
794 | } | |
795 | ||
796 | /* Log invalid environment variables with filename */ | |
797 | if (p) { | |
798 | InvalidEnvInfo info = { | |
799 | .unit = unit, | |
800 | .path = *path, | |
801 | }; | |
802 | ||
803 | p = strv_env_clean_with_callback(p, invalid_env, &info); | |
804 | } | |
805 | ||
806 | if (!v) | |
807 | v = TAKE_PTR(p); | |
808 | else { | |
809 | char **m = strv_env_merge(v, p); | |
810 | if (!m) | |
811 | return -ENOMEM; | |
812 | ||
813 | strv_free_and_replace(v, m); | |
814 | } | |
815 | } | |
816 | } | |
817 | ||
818 | *ret = TAKE_PTR(v); | |
819 | ||
820 | return 0; | |
821 | } | |
822 | ||
823 | static bool tty_may_match_dev_console(const char *tty) { | |
824 | _cleanup_free_ char *resolved = NULL; | |
825 | ||
826 | if (!tty) | |
827 | return true; | |
828 | ||
829 | tty = skip_dev_prefix(tty); | |
830 | ||
831 | /* trivial identity? */ | |
832 | if (streq(tty, "console")) | |
833 | return true; | |
834 | ||
835 | if (resolve_dev_console(&resolved) < 0) | |
836 | return true; /* if we could not resolve, assume it may */ | |
837 | ||
838 | /* "tty0" means the active VC, so it may be the same sometimes */ | |
839 | return path_equal(resolved, tty) || (streq(resolved, "tty0") && tty_is_vc(tty)); | |
840 | } | |
841 | ||
842 | static bool exec_context_may_touch_tty(const ExecContext *ec) { | |
843 | assert(ec); | |
844 | ||
845 | return ec->tty_reset || | |
846 | ec->tty_vhangup || | |
847 | ec->tty_vt_disallocate || | |
848 | is_terminal_input(ec->std_input) || | |
849 | is_terminal_output(ec->std_output) || | |
850 | is_terminal_output(ec->std_error); | |
851 | } | |
852 | ||
853 | bool exec_context_may_touch_console(const ExecContext *ec) { | |
854 | ||
855 | return exec_context_may_touch_tty(ec) && | |
856 | tty_may_match_dev_console(exec_context_tty_path(ec)); | |
857 | } | |
858 | ||
859 | static void strv_fprintf(FILE *f, char **l) { | |
860 | assert(f); | |
861 | ||
862 | STRV_FOREACH(g, l) | |
863 | fprintf(f, " %s", *g); | |
864 | } | |
865 | ||
866 | static void strv_dump(FILE* f, const char *prefix, const char *name, char **strv) { | |
867 | assert(f); | |
868 | assert(prefix); | |
869 | assert(name); | |
870 | ||
871 | if (!strv_isempty(strv)) { | |
872 | fprintf(f, "%s%s:", prefix, name); | |
873 | strv_fprintf(f, strv); | |
874 | fputs("\n", f); | |
875 | } | |
876 | } | |
877 | ||
878 | void exec_params_dump(const ExecParameters *p, FILE* f, const char *prefix) { | |
879 | assert(p); | |
880 | assert(f); | |
881 | ||
882 | prefix = strempty(prefix); | |
883 | ||
884 | fprintf(f, | |
885 | "%sRuntimeScope: %s\n" | |
886 | "%sExecFlags: %u\n" | |
887 | "%sSELinuxContextNetwork: %s\n" | |
888 | "%sCgroupSupportedMask: %u\n" | |
889 | "%sCgroupPath: %s\n" | |
890 | "%sCrededentialsDirectory: %s\n" | |
891 | "%sEncryptedCredentialsDirectory: %s\n" | |
892 | "%sConfirmSpawn: %s\n" | |
893 | "%sShallConfirmSpawn: %s\n" | |
894 | "%sWatchdogUSec: " USEC_FMT "\n" | |
895 | "%sNotifySocket: %s\n" | |
896 | "%sFallbackSmackProcessLabel: %s\n", | |
897 | prefix, runtime_scope_to_string(p->runtime_scope), | |
898 | prefix, p->flags, | |
899 | prefix, yes_no(p->selinux_context_net), | |
900 | prefix, p->cgroup_supported, | |
901 | prefix, p->cgroup_path, | |
902 | prefix, strempty(p->received_credentials_directory), | |
903 | prefix, strempty(p->received_encrypted_credentials_directory), | |
904 | prefix, strempty(p->confirm_spawn), | |
905 | prefix, yes_no(p->shall_confirm_spawn), | |
906 | prefix, p->watchdog_usec, | |
907 | prefix, strempty(p->notify_socket), | |
908 | prefix, strempty(p->fallback_smack_process_label)); | |
909 | ||
910 | strv_dump(f, prefix, "FdNames", p->fd_names); | |
911 | strv_dump(f, prefix, "Environment", p->environment); | |
912 | strv_dump(f, prefix, "Prefix", p->prefix); | |
913 | ||
914 | LIST_FOREACH(open_files, file, p->open_files) | |
915 | fprintf(f, "%sOpenFile: %s %s", prefix, file->path, open_file_flags_to_string(file->flags)); | |
916 | ||
917 | strv_dump(f, prefix, "FilesEnv", p->files_env); | |
918 | } | |
919 | ||
920 | void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { | |
921 | int r; | |
922 | ||
923 | assert(c); | |
924 | assert(f); | |
925 | ||
926 | prefix = strempty(prefix); | |
927 | ||
928 | fprintf(f, | |
929 | "%sUMask: %04o\n" | |
930 | "%sWorkingDirectory: %s\n" | |
931 | "%sRootDirectory: %s\n" | |
932 | "%sRootEphemeral: %s\n" | |
933 | "%sNonBlocking: %s\n" | |
934 | "%sPrivateTmp: %s\n" | |
935 | "%sPrivateDevices: %s\n" | |
936 | "%sProtectKernelTunables: %s\n" | |
937 | "%sProtectKernelModules: %s\n" | |
938 | "%sProtectKernelLogs: %s\n" | |
939 | "%sProtectClock: %s\n" | |
940 | "%sProtectControlGroups: %s\n" | |
941 | "%sPrivateNetwork: %s\n" | |
942 | "%sPrivateUsers: %s\n" | |
943 | "%sProtectHome: %s\n" | |
944 | "%sProtectSystem: %s\n" | |
945 | "%sMountAPIVFS: %s\n" | |
946 | "%sIgnoreSIGPIPE: %s\n" | |
947 | "%sMemoryDenyWriteExecute: %s\n" | |
948 | "%sRestrictRealtime: %s\n" | |
949 | "%sRestrictSUIDSGID: %s\n" | |
950 | "%sKeyringMode: %s\n" | |
951 | "%sProtectHostname: %s\n" | |
952 | "%sProtectProc: %s\n" | |
953 | "%sProcSubset: %s\n", | |
954 | prefix, c->umask, | |
955 | prefix, empty_to_root(c->working_directory), | |
956 | prefix, empty_to_root(c->root_directory), | |
957 | prefix, yes_no(c->root_ephemeral), | |
958 | prefix, yes_no(c->non_blocking), | |
959 | prefix, yes_no(c->private_tmp), | |
960 | prefix, yes_no(c->private_devices), | |
961 | prefix, yes_no(c->protect_kernel_tunables), | |
962 | prefix, yes_no(c->protect_kernel_modules), | |
963 | prefix, yes_no(c->protect_kernel_logs), | |
964 | prefix, yes_no(c->protect_clock), | |
965 | prefix, yes_no(c->protect_control_groups), | |
966 | prefix, yes_no(c->private_network), | |
967 | prefix, yes_no(c->private_users), | |
968 | prefix, protect_home_to_string(c->protect_home), | |
969 | prefix, protect_system_to_string(c->protect_system), | |
970 | prefix, yes_no(exec_context_get_effective_mount_apivfs(c)), | |
971 | prefix, yes_no(c->ignore_sigpipe), | |
972 | prefix, yes_no(c->memory_deny_write_execute), | |
973 | prefix, yes_no(c->restrict_realtime), | |
974 | prefix, yes_no(c->restrict_suid_sgid), | |
975 | prefix, exec_keyring_mode_to_string(c->keyring_mode), | |
976 | prefix, yes_no(c->protect_hostname), | |
977 | prefix, protect_proc_to_string(c->protect_proc), | |
978 | prefix, proc_subset_to_string(c->proc_subset)); | |
979 | ||
980 | if (c->set_login_environment >= 0) | |
981 | fprintf(f, "%sSetLoginEnvironment: %s\n", prefix, yes_no(c->set_login_environment > 0)); | |
982 | ||
983 | if (c->root_image) | |
984 | fprintf(f, "%sRootImage: %s\n", prefix, c->root_image); | |
985 | ||
986 | if (c->root_image_options) { | |
987 | fprintf(f, "%sRootImageOptions:", prefix); | |
988 | LIST_FOREACH(mount_options, o, c->root_image_options) | |
989 | if (!isempty(o->options)) | |
990 | fprintf(f, " %s:%s", | |
991 | partition_designator_to_string(o->partition_designator), | |
992 | o->options); | |
993 | fprintf(f, "\n"); | |
994 | } | |
995 | ||
996 | if (c->root_hash) { | |
997 | _cleanup_free_ char *encoded = NULL; | |
998 | encoded = hexmem(c->root_hash, c->root_hash_size); | |
999 | if (encoded) | |
1000 | fprintf(f, "%sRootHash: %s\n", prefix, encoded); | |
1001 | } | |
1002 | ||
1003 | if (c->root_hash_path) | |
1004 | fprintf(f, "%sRootHash: %s\n", prefix, c->root_hash_path); | |
1005 | ||
1006 | if (c->root_hash_sig) { | |
1007 | _cleanup_free_ char *encoded = NULL; | |
1008 | ssize_t len; | |
1009 | len = base64mem(c->root_hash_sig, c->root_hash_sig_size, &encoded); | |
1010 | if (len) | |
1011 | fprintf(f, "%sRootHashSignature: base64:%s\n", prefix, encoded); | |
1012 | } | |
1013 | ||
1014 | if (c->root_hash_sig_path) | |
1015 | fprintf(f, "%sRootHashSignature: %s\n", prefix, c->root_hash_sig_path); | |
1016 | ||
1017 | if (c->root_verity) | |
1018 | fprintf(f, "%sRootVerity: %s\n", prefix, c->root_verity); | |
1019 | ||
1020 | STRV_FOREACH(e, c->environment) | |
1021 | fprintf(f, "%sEnvironment: %s\n", prefix, *e); | |
1022 | ||
1023 | STRV_FOREACH(e, c->environment_files) | |
1024 | fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e); | |
1025 | ||
1026 | STRV_FOREACH(e, c->pass_environment) | |
1027 | fprintf(f, "%sPassEnvironment: %s\n", prefix, *e); | |
1028 | ||
1029 | STRV_FOREACH(e, c->unset_environment) | |
1030 | fprintf(f, "%sUnsetEnvironment: %s\n", prefix, *e); | |
1031 | ||
1032 | fprintf(f, "%sRuntimeDirectoryPreserve: %s\n", prefix, exec_preserve_mode_to_string(c->runtime_directory_preserve_mode)); | |
1033 | ||
1034 | for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) { | |
1035 | fprintf(f, "%s%sMode: %04o\n", prefix, exec_directory_type_to_string(dt), c->directories[dt].mode); | |
1036 | ||
1037 | for (size_t i = 0; i < c->directories[dt].n_items; i++) { | |
1038 | fprintf(f, "%s%s: %s\n", prefix, exec_directory_type_to_string(dt), c->directories[dt].items[i].path); | |
1039 | ||
1040 | STRV_FOREACH(d, c->directories[dt].items[i].symlinks) | |
1041 | fprintf(f, "%s%s: %s:%s\n", prefix, exec_directory_type_symlink_to_string(dt), c->directories[dt].items[i].path, *d); | |
1042 | } | |
1043 | } | |
1044 | ||
1045 | fprintf(f, "%sTimeoutCleanSec: %s\n", prefix, FORMAT_TIMESPAN(c->timeout_clean_usec, USEC_PER_SEC)); | |
1046 | ||
1047 | if (c->memory_ksm >= 0) | |
1048 | fprintf(f, "%sMemoryKSM: %s\n", prefix, yes_no(c->memory_ksm > 0)); | |
1049 | ||
1050 | if (c->nice_set) | |
1051 | fprintf(f, "%sNice: %i\n", prefix, c->nice); | |
1052 | ||
1053 | if (c->oom_score_adjust_set) | |
1054 | fprintf(f, "%sOOMScoreAdjust: %i\n", prefix, c->oom_score_adjust); | |
1055 | ||
1056 | if (c->coredump_filter_set) | |
1057 | fprintf(f, "%sCoredumpFilter: 0x%"PRIx64"\n", prefix, c->coredump_filter); | |
1058 | ||
1059 | for (unsigned i = 0; i < RLIM_NLIMITS; i++) | |
1060 | if (c->rlimit[i]) { | |
1061 | fprintf(f, "%sLimit%s: " RLIM_FMT "\n", | |
1062 | prefix, rlimit_to_string(i), c->rlimit[i]->rlim_max); | |
1063 | fprintf(f, "%sLimit%sSoft: " RLIM_FMT "\n", | |
1064 | prefix, rlimit_to_string(i), c->rlimit[i]->rlim_cur); | |
1065 | } | |
1066 | ||
1067 | if (c->ioprio_set) { | |
1068 | _cleanup_free_ char *class_str = NULL; | |
1069 | ||
1070 | r = ioprio_class_to_string_alloc(ioprio_prio_class(c->ioprio), &class_str); | |
1071 | if (r >= 0) | |
1072 | fprintf(f, "%sIOSchedulingClass: %s\n", prefix, class_str); | |
1073 | ||
1074 | fprintf(f, "%sIOPriority: %d\n", prefix, ioprio_prio_data(c->ioprio)); | |
1075 | } | |
1076 | ||
1077 | if (c->cpu_sched_set) { | |
1078 | _cleanup_free_ char *policy_str = NULL; | |
1079 | ||
1080 | r = sched_policy_to_string_alloc(c->cpu_sched_policy, &policy_str); | |
1081 | if (r >= 0) | |
1082 | fprintf(f, "%sCPUSchedulingPolicy: %s\n", prefix, policy_str); | |
1083 | ||
1084 | fprintf(f, | |
1085 | "%sCPUSchedulingPriority: %i\n" | |
1086 | "%sCPUSchedulingResetOnFork: %s\n", | |
1087 | prefix, c->cpu_sched_priority, | |
1088 | prefix, yes_no(c->cpu_sched_reset_on_fork)); | |
1089 | } | |
1090 | ||
1091 | if (c->cpu_set.set) { | |
1092 | _cleanup_free_ char *affinity = NULL; | |
1093 | ||
1094 | affinity = cpu_set_to_range_string(&c->cpu_set); | |
1095 | fprintf(f, "%sCPUAffinity: %s\n", prefix, affinity); | |
1096 | } | |
1097 | ||
1098 | if (mpol_is_valid(numa_policy_get_type(&c->numa_policy))) { | |
1099 | _cleanup_free_ char *nodes = NULL; | |
1100 | ||
1101 | nodes = cpu_set_to_range_string(&c->numa_policy.nodes); | |
1102 | fprintf(f, "%sNUMAPolicy: %s\n", prefix, mpol_to_string(numa_policy_get_type(&c->numa_policy))); | |
1103 | fprintf(f, "%sNUMAMask: %s\n", prefix, strnull(nodes)); | |
1104 | } | |
1105 | ||
1106 | if (c->timer_slack_nsec != NSEC_INFINITY) | |
1107 | fprintf(f, "%sTimerSlackNSec: "NSEC_FMT "\n", prefix, c->timer_slack_nsec); | |
1108 | ||
1109 | fprintf(f, | |
1110 | "%sStandardInput: %s\n" | |
1111 | "%sStandardOutput: %s\n" | |
1112 | "%sStandardError: %s\n", | |
1113 | prefix, exec_input_to_string(c->std_input), | |
1114 | prefix, exec_output_to_string(c->std_output), | |
1115 | prefix, exec_output_to_string(c->std_error)); | |
1116 | ||
1117 | if (c->std_input == EXEC_INPUT_NAMED_FD) | |
1118 | fprintf(f, "%sStandardInputFileDescriptorName: %s\n", prefix, c->stdio_fdname[STDIN_FILENO]); | |
1119 | if (c->std_output == EXEC_OUTPUT_NAMED_FD) | |
1120 | fprintf(f, "%sStandardOutputFileDescriptorName: %s\n", prefix, c->stdio_fdname[STDOUT_FILENO]); | |
1121 | if (c->std_error == EXEC_OUTPUT_NAMED_FD) | |
1122 | fprintf(f, "%sStandardErrorFileDescriptorName: %s\n", prefix, c->stdio_fdname[STDERR_FILENO]); | |
1123 | ||
1124 | if (c->std_input == EXEC_INPUT_FILE) | |
1125 | fprintf(f, "%sStandardInputFile: %s\n", prefix, c->stdio_file[STDIN_FILENO]); | |
1126 | if (c->std_output == EXEC_OUTPUT_FILE) | |
1127 | fprintf(f, "%sStandardOutputFile: %s\n", prefix, c->stdio_file[STDOUT_FILENO]); | |
1128 | if (c->std_output == EXEC_OUTPUT_FILE_APPEND) | |
1129 | fprintf(f, "%sStandardOutputFileToAppend: %s\n", prefix, c->stdio_file[STDOUT_FILENO]); | |
1130 | if (c->std_output == EXEC_OUTPUT_FILE_TRUNCATE) | |
1131 | fprintf(f, "%sStandardOutputFileToTruncate: %s\n", prefix, c->stdio_file[STDOUT_FILENO]); | |
1132 | if (c->std_error == EXEC_OUTPUT_FILE) | |
1133 | fprintf(f, "%sStandardErrorFile: %s\n", prefix, c->stdio_file[STDERR_FILENO]); | |
1134 | if (c->std_error == EXEC_OUTPUT_FILE_APPEND) | |
1135 | fprintf(f, "%sStandardErrorFileToAppend: %s\n", prefix, c->stdio_file[STDERR_FILENO]); | |
1136 | if (c->std_error == EXEC_OUTPUT_FILE_TRUNCATE) | |
1137 | fprintf(f, "%sStandardErrorFileToTruncate: %s\n", prefix, c->stdio_file[STDERR_FILENO]); | |
1138 | ||
1139 | if (c->tty_path) | |
1140 | fprintf(f, | |
1141 | "%sTTYPath: %s\n" | |
1142 | "%sTTYReset: %s\n" | |
1143 | "%sTTYVHangup: %s\n" | |
1144 | "%sTTYVTDisallocate: %s\n" | |
1145 | "%sTTYRows: %u\n" | |
1146 | "%sTTYColumns: %u\n", | |
1147 | prefix, c->tty_path, | |
1148 | prefix, yes_no(c->tty_reset), | |
1149 | prefix, yes_no(c->tty_vhangup), | |
1150 | prefix, yes_no(c->tty_vt_disallocate), | |
1151 | prefix, c->tty_rows, | |
1152 | prefix, c->tty_cols); | |
1153 | ||
1154 | if (IN_SET(c->std_output, | |
1155 | EXEC_OUTPUT_KMSG, | |
1156 | EXEC_OUTPUT_JOURNAL, | |
1157 | EXEC_OUTPUT_KMSG_AND_CONSOLE, | |
1158 | EXEC_OUTPUT_JOURNAL_AND_CONSOLE) || | |
1159 | IN_SET(c->std_error, | |
1160 | EXEC_OUTPUT_KMSG, | |
1161 | EXEC_OUTPUT_JOURNAL, | |
1162 | EXEC_OUTPUT_KMSG_AND_CONSOLE, | |
1163 | EXEC_OUTPUT_JOURNAL_AND_CONSOLE)) { | |
1164 | ||
1165 | _cleanup_free_ char *fac_str = NULL, *lvl_str = NULL; | |
1166 | ||
1167 | r = log_facility_unshifted_to_string_alloc(c->syslog_priority >> 3, &fac_str); | |
1168 | if (r >= 0) | |
1169 | fprintf(f, "%sSyslogFacility: %s\n", prefix, fac_str); | |
1170 | ||
1171 | r = log_level_to_string_alloc(LOG_PRI(c->syslog_priority), &lvl_str); | |
1172 | if (r >= 0) | |
1173 | fprintf(f, "%sSyslogLevel: %s\n", prefix, lvl_str); | |
1174 | } | |
1175 | ||
1176 | if (c->log_level_max >= 0) { | |
1177 | _cleanup_free_ char *t = NULL; | |
1178 | ||
1179 | (void) log_level_to_string_alloc(c->log_level_max, &t); | |
1180 | ||
1181 | fprintf(f, "%sLogLevelMax: %s\n", prefix, strna(t)); | |
1182 | } | |
1183 | ||
1184 | if (c->log_ratelimit_interval_usec > 0) | |
1185 | fprintf(f, | |
1186 | "%sLogRateLimitIntervalSec: %s\n", | |
1187 | prefix, FORMAT_TIMESPAN(c->log_ratelimit_interval_usec, USEC_PER_SEC)); | |
1188 | ||
1189 | if (c->log_ratelimit_burst > 0) | |
1190 | fprintf(f, "%sLogRateLimitBurst: %u\n", prefix, c->log_ratelimit_burst); | |
1191 | ||
1192 | if (!set_isempty(c->log_filter_allowed_patterns) || !set_isempty(c->log_filter_denied_patterns)) { | |
1193 | fprintf(f, "%sLogFilterPatterns:", prefix); | |
1194 | ||
1195 | char *pattern; | |
1196 | SET_FOREACH(pattern, c->log_filter_allowed_patterns) | |
1197 | fprintf(f, " %s", pattern); | |
1198 | SET_FOREACH(pattern, c->log_filter_denied_patterns) | |
1199 | fprintf(f, " ~%s", pattern); | |
1200 | fputc('\n', f); | |
1201 | } | |
1202 | ||
1203 | FOREACH_ARRAY(field, c->log_extra_fields, c->n_log_extra_fields) { | |
1204 | fprintf(f, "%sLogExtraFields: ", prefix); | |
1205 | fwrite(field->iov_base, 1, field->iov_len, f); | |
1206 | fputc('\n', f); | |
1207 | } | |
1208 | ||
1209 | if (c->log_namespace) | |
1210 | fprintf(f, "%sLogNamespace: %s\n", prefix, c->log_namespace); | |
1211 | ||
1212 | if (c->secure_bits) { | |
1213 | _cleanup_free_ char *str = NULL; | |
1214 | ||
1215 | r = secure_bits_to_string_alloc(c->secure_bits, &str); | |
1216 | if (r >= 0) | |
1217 | fprintf(f, "%sSecure Bits: %s\n", prefix, str); | |
1218 | } | |
1219 | ||
1220 | if (c->capability_bounding_set != CAP_MASK_UNSET) { | |
1221 | _cleanup_free_ char *str = NULL; | |
1222 | ||
1223 | r = capability_set_to_string(c->capability_bounding_set, &str); | |
1224 | if (r >= 0) | |
1225 | fprintf(f, "%sCapabilityBoundingSet: %s\n", prefix, str); | |
1226 | } | |
1227 | ||
1228 | if (c->capability_ambient_set != 0) { | |
1229 | _cleanup_free_ char *str = NULL; | |
1230 | ||
1231 | r = capability_set_to_string(c->capability_ambient_set, &str); | |
1232 | if (r >= 0) | |
1233 | fprintf(f, "%sAmbientCapabilities: %s\n", prefix, str); | |
1234 | } | |
1235 | ||
1236 | if (c->user) | |
1237 | fprintf(f, "%sUser: %s\n", prefix, c->user); | |
1238 | if (c->group) | |
1239 | fprintf(f, "%sGroup: %s\n", prefix, c->group); | |
1240 | ||
1241 | fprintf(f, "%sDynamicUser: %s\n", prefix, yes_no(c->dynamic_user)); | |
1242 | ||
1243 | strv_dump(f, prefix, "SupplementaryGroups", c->supplementary_groups); | |
1244 | ||
1245 | if (c->pam_name) | |
1246 | fprintf(f, "%sPAMName: %s\n", prefix, c->pam_name); | |
1247 | ||
1248 | strv_dump(f, prefix, "ReadWritePaths", c->read_write_paths); | |
1249 | strv_dump(f, prefix, "ReadOnlyPaths", c->read_only_paths); | |
1250 | strv_dump(f, prefix, "InaccessiblePaths", c->inaccessible_paths); | |
1251 | strv_dump(f, prefix, "ExecPaths", c->exec_paths); | |
1252 | strv_dump(f, prefix, "NoExecPaths", c->no_exec_paths); | |
1253 | strv_dump(f, prefix, "ExecSearchPath", c->exec_search_path); | |
1254 | ||
1255 | FOREACH_ARRAY(mount, c->bind_mounts, c->n_bind_mounts) | |
1256 | fprintf(f, "%s%s: %s%s:%s:%s\n", prefix, | |
1257 | mount->read_only ? "BindReadOnlyPaths" : "BindPaths", | |
1258 | mount->ignore_enoent ? "-": "", | |
1259 | mount->source, | |
1260 | mount->destination, | |
1261 | mount->recursive ? "rbind" : "norbind"); | |
1262 | ||
1263 | FOREACH_ARRAY(tmpfs, c->temporary_filesystems, c->n_temporary_filesystems) | |
1264 | fprintf(f, "%sTemporaryFileSystem: %s%s%s\n", prefix, | |
1265 | tmpfs->path, | |
1266 | isempty(tmpfs->options) ? "" : ":", | |
1267 | strempty(tmpfs->options)); | |
1268 | ||
1269 | if (c->utmp_id) | |
1270 | fprintf(f, | |
1271 | "%sUtmpIdentifier: %s\n", | |
1272 | prefix, c->utmp_id); | |
1273 | ||
1274 | if (c->selinux_context) | |
1275 | fprintf(f, | |
1276 | "%sSELinuxContext: %s%s\n", | |
1277 | prefix, c->selinux_context_ignore ? "-" : "", c->selinux_context); | |
1278 | ||
1279 | if (c->apparmor_profile) | |
1280 | fprintf(f, | |
1281 | "%sAppArmorProfile: %s%s\n", | |
1282 | prefix, c->apparmor_profile_ignore ? "-" : "", c->apparmor_profile); | |
1283 | ||
1284 | if (c->smack_process_label) | |
1285 | fprintf(f, | |
1286 | "%sSmackProcessLabel: %s%s\n", | |
1287 | prefix, c->smack_process_label_ignore ? "-" : "", c->smack_process_label); | |
1288 | ||
1289 | if (c->personality != PERSONALITY_INVALID) | |
1290 | fprintf(f, | |
1291 | "%sPersonality: %s\n", | |
1292 | prefix, strna(personality_to_string(c->personality))); | |
1293 | ||
1294 | fprintf(f, | |
1295 | "%sLockPersonality: %s\n", | |
1296 | prefix, yes_no(c->lock_personality)); | |
1297 | ||
1298 | if (c->syscall_filter) { | |
1299 | fprintf(f, | |
1300 | "%sSystemCallFilter: ", | |
1301 | prefix); | |
1302 | ||
1303 | if (!c->syscall_allow_list) | |
1304 | fputc('~', f); | |
1305 | ||
1306 | #if HAVE_SECCOMP | |
1307 | void *id, *val; | |
1308 | bool first = true; | |
1309 | HASHMAP_FOREACH_KEY(val, id, c->syscall_filter) { | |
1310 | _cleanup_free_ char *name = NULL; | |
1311 | const char *errno_name = NULL; | |
1312 | int num = PTR_TO_INT(val); | |
1313 | ||
1314 | if (first) | |
1315 | first = false; | |
1316 | else | |
1317 | fputc(' ', f); | |
1318 | ||
1319 | name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); | |
1320 | fputs(strna(name), f); | |
1321 | ||
1322 | if (num >= 0) { | |
1323 | errno_name = seccomp_errno_or_action_to_string(num); | |
1324 | if (errno_name) | |
1325 | fprintf(f, ":%s", errno_name); | |
1326 | else | |
1327 | fprintf(f, ":%d", num); | |
1328 | } | |
1329 | } | |
1330 | #endif | |
1331 | ||
1332 | fputc('\n', f); | |
1333 | } | |
1334 | ||
1335 | if (c->syscall_archs) { | |
1336 | fprintf(f, | |
1337 | "%sSystemCallArchitectures:", | |
1338 | prefix); | |
1339 | ||
1340 | #if HAVE_SECCOMP | |
1341 | void *id; | |
1342 | SET_FOREACH(id, c->syscall_archs) | |
1343 | fprintf(f, " %s", strna(seccomp_arch_to_string(PTR_TO_UINT32(id) - 1))); | |
1344 | #endif | |
1345 | fputc('\n', f); | |
1346 | } | |
1347 | ||
1348 | if (exec_context_restrict_namespaces_set(c)) { | |
1349 | _cleanup_free_ char *s = NULL; | |
1350 | ||
1351 | r = namespace_flags_to_string(c->restrict_namespaces, &s); | |
1352 | if (r >= 0) | |
1353 | fprintf(f, "%sRestrictNamespaces: %s\n", | |
1354 | prefix, strna(s)); | |
1355 | } | |
1356 | ||
1357 | #if HAVE_LIBBPF | |
1358 | if (exec_context_restrict_filesystems_set(c)) { | |
1359 | char *fs; | |
1360 | SET_FOREACH(fs, c->restrict_filesystems) | |
1361 | fprintf(f, "%sRestrictFileSystems: %s\n", prefix, fs); | |
1362 | } | |
1363 | #endif | |
1364 | ||
1365 | if (c->network_namespace_path) | |
1366 | fprintf(f, | |
1367 | "%sNetworkNamespacePath: %s\n", | |
1368 | prefix, c->network_namespace_path); | |
1369 | ||
1370 | if (c->syscall_errno > 0) { | |
1371 | fprintf(f, "%sSystemCallErrorNumber: ", prefix); | |
1372 | ||
1373 | #if HAVE_SECCOMP | |
1374 | const char *errno_name = seccomp_errno_or_action_to_string(c->syscall_errno); | |
1375 | if (errno_name) | |
1376 | fputs(errno_name, f); | |
1377 | else | |
1378 | fprintf(f, "%d", c->syscall_errno); | |
1379 | #endif | |
1380 | fputc('\n', f); | |
1381 | } | |
1382 | ||
1383 | FOREACH_ARRAY(mount, c->mount_images, c->n_mount_images) { | |
1384 | fprintf(f, "%sMountImages: %s%s:%s", prefix, | |
1385 | mount->ignore_enoent ? "-": "", | |
1386 | mount->source, | |
1387 | mount->destination); | |
1388 | LIST_FOREACH(mount_options, o, mount->mount_options) | |
1389 | fprintf(f, ":%s:%s", | |
1390 | partition_designator_to_string(o->partition_designator), | |
1391 | strempty(o->options)); | |
1392 | fprintf(f, "\n"); | |
1393 | } | |
1394 | ||
1395 | FOREACH_ARRAY(mount, c->extension_images, c->n_extension_images) { | |
1396 | fprintf(f, "%sExtensionImages: %s%s", prefix, | |
1397 | mount->ignore_enoent ? "-": "", | |
1398 | mount->source); | |
1399 | LIST_FOREACH(mount_options, o, mount->mount_options) | |
1400 | fprintf(f, ":%s:%s", | |
1401 | partition_designator_to_string(o->partition_designator), | |
1402 | strempty(o->options)); | |
1403 | fprintf(f, "\n"); | |
1404 | } | |
1405 | ||
1406 | strv_dump(f, prefix, "ExtensionDirectories", c->extension_directories); | |
1407 | } | |
1408 | ||
1409 | bool exec_context_maintains_privileges(const ExecContext *c) { | |
1410 | assert(c); | |
1411 | ||
1412 | /* Returns true if the process forked off would run under | |
1413 | * an unchanged UID or as root. */ | |
1414 | ||
1415 | if (!c->user) | |
1416 | return true; | |
1417 | ||
1418 | if (STR_IN_SET(c->user, "root", "0")) | |
1419 | return true; | |
1420 | ||
1421 | return false; | |
1422 | } | |
1423 | ||
1424 | int exec_context_get_effective_ioprio(const ExecContext *c) { | |
1425 | int p; | |
1426 | ||
1427 | assert(c); | |
1428 | ||
1429 | if (c->ioprio_set) | |
1430 | return c->ioprio; | |
1431 | ||
1432 | p = ioprio_get(IOPRIO_WHO_PROCESS, 0); | |
1433 | if (p < 0) | |
1434 | return IOPRIO_DEFAULT_CLASS_AND_PRIO; | |
1435 | ||
1436 | return ioprio_normalize(p); | |
1437 | } | |
1438 | ||
1439 | bool exec_context_get_effective_mount_apivfs(const ExecContext *c) { | |
1440 | assert(c); | |
1441 | ||
1442 | /* Explicit setting wins */ | |
1443 | if (c->mount_apivfs_set) | |
1444 | return c->mount_apivfs; | |
1445 | ||
1446 | /* Default to "yes" if root directory or image are specified */ | |
1447 | if (exec_context_with_rootfs(c)) | |
1448 | return true; | |
1449 | ||
1450 | return false; | |
1451 | } | |
1452 | ||
1453 | void exec_context_free_log_extra_fields(ExecContext *c) { | |
1454 | assert(c); | |
1455 | ||
1456 | FOREACH_ARRAY(field, c->log_extra_fields, c->n_log_extra_fields) | |
1457 | free(field->iov_base); | |
1458 | ||
1459 | c->log_extra_fields = mfree(c->log_extra_fields); | |
1460 | c->n_log_extra_fields = 0; | |
1461 | } | |
1462 | ||
1463 | void exec_context_revert_tty(ExecContext *c) { | |
1464 | _cleanup_close_ int fd = -EBADF; | |
1465 | const char *path; | |
1466 | struct stat st; | |
1467 | int r; | |
1468 | ||
1469 | assert(c); | |
1470 | ||
1471 | /* First, reset the TTY (possibly kicking everybody else from the TTY) */ | |
1472 | exec_context_tty_reset(c, /* parameters= */ NULL); | |
1473 | ||
1474 | /* And then undo what chown_terminal() did earlier. Note that we only do this if we have a path | |
1475 | * configured. If the TTY was passed to us as file descriptor we assume the TTY is opened and managed | |
1476 | * by whoever passed it to us and thus knows better when and how to chmod()/chown() it back. */ | |
1477 | if (!exec_context_may_touch_tty(c)) | |
1478 | return; | |
1479 | ||
1480 | path = exec_context_tty_path(c); | |
1481 | if (!path) | |
1482 | return; | |
1483 | ||
1484 | fd = open(path, O_PATH|O_CLOEXEC); /* Pin the inode */ | |
1485 | if (fd < 0) | |
1486 | return (void) log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, | |
1487 | "Failed to open TTY inode of '%s' to adjust ownership/access mode, ignoring: %m", | |
1488 | path); | |
1489 | ||
1490 | if (fstat(fd, &st) < 0) | |
1491 | return (void) log_warning_errno(errno, "Failed to stat TTY '%s', ignoring: %m", path); | |
1492 | ||
1493 | /* Let's add a superficial check that we only do this for stuff that looks like a TTY. We only check | |
1494 | * if things are a character device, since a proper check either means we'd have to open the TTY and | |
1495 | * use isatty(), but we'd rather not do that since opening TTYs comes with all kinds of side-effects | |
1496 | * and is slow. Or we'd have to hardcode dev_t major information, which we'd rather avoid. Why bother | |
1497 | * with this at all? → https://github.com/systemd/systemd/issues/19213 */ | |
1498 | if (!S_ISCHR(st.st_mode)) | |
1499 | return log_warning("Configured TTY '%s' is not actually a character device, ignoring.", path); | |
1500 | ||
1501 | r = fchmod_and_chown(fd, TTY_MODE, 0, TTY_GID); | |
1502 | if (r < 0) | |
1503 | log_warning_errno(r, "Failed to reset TTY ownership/access mode of %s to " UID_FMT ":" GID_FMT ", ignoring: %m", path, (uid_t) 0, (gid_t) TTY_GID); | |
1504 | } | |
1505 | ||
1506 | int exec_context_get_clean_directories( | |
1507 | ExecContext *c, | |
1508 | char **prefix, | |
1509 | ExecCleanMask mask, | |
1510 | char ***ret) { | |
1511 | ||
1512 | _cleanup_strv_free_ char **l = NULL; | |
1513 | int r; | |
1514 | ||
1515 | assert(c); | |
1516 | assert(prefix); | |
1517 | assert(ret); | |
1518 | ||
1519 | for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) { | |
1520 | if (!FLAGS_SET(mask, 1U << t)) | |
1521 | continue; | |
1522 | ||
1523 | if (!prefix[t]) | |
1524 | continue; | |
1525 | ||
1526 | FOREACH_ARRAY(i, c->directories[t].items, c->directories[t].n_items) { | |
1527 | char *j; | |
1528 | ||
1529 | j = path_join(prefix[t], i->path); | |
1530 | if (!j) | |
1531 | return -ENOMEM; | |
1532 | ||
1533 | r = strv_consume(&l, j); | |
1534 | if (r < 0) | |
1535 | return r; | |
1536 | ||
1537 | /* Also remove private directories unconditionally. */ | |
1538 | if (t != EXEC_DIRECTORY_CONFIGURATION) { | |
1539 | j = path_join(prefix[t], "private", i->path); | |
1540 | if (!j) | |
1541 | return -ENOMEM; | |
1542 | ||
1543 | r = strv_consume(&l, j); | |
1544 | if (r < 0) | |
1545 | return r; | |
1546 | } | |
1547 | ||
1548 | STRV_FOREACH(symlink, i->symlinks) { | |
1549 | j = path_join(prefix[t], *symlink); | |
1550 | if (!j) | |
1551 | return -ENOMEM; | |
1552 | ||
1553 | r = strv_consume(&l, j); | |
1554 | if (r < 0) | |
1555 | return r; | |
1556 | } | |
1557 | } | |
1558 | } | |
1559 | ||
1560 | *ret = TAKE_PTR(l); | |
1561 | return 0; | |
1562 | } | |
1563 | ||
1564 | int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret) { | |
1565 | ExecCleanMask mask = 0; | |
1566 | ||
1567 | assert(c); | |
1568 | assert(ret); | |
1569 | ||
1570 | for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) | |
1571 | if (c->directories[t].n_items > 0) | |
1572 | mask |= 1U << t; | |
1573 | ||
1574 | *ret = mask; | |
1575 | return 0; | |
1576 | } | |
1577 | ||
1578 | int exec_context_get_oom_score_adjust(const ExecContext *c) { | |
1579 | int n = 0, r; | |
1580 | ||
1581 | assert(c); | |
1582 | ||
1583 | if (c->oom_score_adjust_set) | |
1584 | return c->oom_score_adjust; | |
1585 | ||
1586 | r = get_oom_score_adjust(&n); | |
1587 | if (r < 0) | |
1588 | log_debug_errno(r, "Failed to read /proc/self/oom_score_adj, ignoring: %m"); | |
1589 | ||
1590 | return n; | |
1591 | } | |
1592 | ||
1593 | uint64_t exec_context_get_coredump_filter(const ExecContext *c) { | |
1594 | _cleanup_free_ char *t = NULL; | |
1595 | uint64_t n = COREDUMP_FILTER_MASK_DEFAULT; | |
1596 | int r; | |
1597 | ||
1598 | assert(c); | |
1599 | ||
1600 | if (c->coredump_filter_set) | |
1601 | return c->coredump_filter; | |
1602 | ||
1603 | r = read_one_line_file("/proc/self/coredump_filter", &t); | |
1604 | if (r < 0) | |
1605 | log_debug_errno(r, "Failed to read /proc/self/coredump_filter, ignoring: %m"); | |
1606 | else { | |
1607 | r = safe_atoux64(t, &n); | |
1608 | if (r < 0) | |
1609 | log_debug_errno(r, "Failed to parse \"%s\" from /proc/self/coredump_filter, ignoring: %m", t); | |
1610 | } | |
1611 | ||
1612 | return n; | |
1613 | } | |
1614 | ||
1615 | int exec_context_get_nice(const ExecContext *c) { | |
1616 | int n; | |
1617 | ||
1618 | assert(c); | |
1619 | ||
1620 | if (c->nice_set) | |
1621 | return c->nice; | |
1622 | ||
1623 | errno = 0; | |
1624 | n = getpriority(PRIO_PROCESS, 0); | |
1625 | if (errno > 0) { | |
1626 | log_debug_errno(errno, "Failed to get process nice value, ignoring: %m"); | |
1627 | n = 0; | |
1628 | } | |
1629 | ||
1630 | return n; | |
1631 | } | |
1632 | ||
1633 | int exec_context_get_cpu_sched_policy(const ExecContext *c) { | |
1634 | int n; | |
1635 | ||
1636 | assert(c); | |
1637 | ||
1638 | if (c->cpu_sched_set) | |
1639 | return c->cpu_sched_policy; | |
1640 | ||
1641 | n = sched_getscheduler(0); | |
1642 | if (n < 0) | |
1643 | log_debug_errno(errno, "Failed to get scheduler policy, ignoring: %m"); | |
1644 | ||
1645 | return n < 0 ? SCHED_OTHER : n; | |
1646 | } | |
1647 | ||
1648 | int exec_context_get_cpu_sched_priority(const ExecContext *c) { | |
1649 | struct sched_param p = {}; | |
1650 | int r; | |
1651 | ||
1652 | assert(c); | |
1653 | ||
1654 | if (c->cpu_sched_set) | |
1655 | return c->cpu_sched_priority; | |
1656 | ||
1657 | r = sched_getparam(0, &p); | |
1658 | if (r < 0) | |
1659 | log_debug_errno(errno, "Failed to get scheduler priority, ignoring: %m"); | |
1660 | ||
1661 | return r >= 0 ? p.sched_priority : 0; | |
1662 | } | |
1663 | ||
1664 | uint64_t exec_context_get_timer_slack_nsec(const ExecContext *c) { | |
1665 | int r; | |
1666 | ||
1667 | assert(c); | |
1668 | ||
1669 | if (c->timer_slack_nsec != NSEC_INFINITY) | |
1670 | return c->timer_slack_nsec; | |
1671 | ||
1672 | r = prctl(PR_GET_TIMERSLACK); | |
1673 | if (r < 0) | |
1674 | log_debug_errno(r, "Failed to get timer slack, ignoring: %m"); | |
1675 | ||
1676 | return (uint64_t) MAX(r, 0); | |
1677 | } | |
1678 | ||
1679 | bool exec_context_get_set_login_environment(const ExecContext *c) { | |
1680 | assert(c); | |
1681 | ||
1682 | if (c->set_login_environment >= 0) | |
1683 | return c->set_login_environment; | |
1684 | ||
1685 | return c->user || c->dynamic_user || c->pam_name; | |
1686 | } | |
1687 | ||
1688 | char** exec_context_get_syscall_filter(const ExecContext *c) { | |
1689 | _cleanup_strv_free_ char **l = NULL; | |
1690 | ||
1691 | assert(c); | |
1692 | ||
1693 | #if HAVE_SECCOMP | |
1694 | void *id, *val; | |
1695 | HASHMAP_FOREACH_KEY(val, id, c->syscall_filter) { | |
1696 | _cleanup_free_ char *name = NULL; | |
1697 | const char *e = NULL; | |
1698 | char *s; | |
1699 | int num = PTR_TO_INT(val); | |
1700 | ||
1701 | if (c->syscall_allow_list && num >= 0) | |
1702 | /* syscall with num >= 0 in allow-list is denied. */ | |
1703 | continue; | |
1704 | ||
1705 | name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); | |
1706 | if (!name) | |
1707 | continue; | |
1708 | ||
1709 | if (num >= 0) { | |
1710 | e = seccomp_errno_or_action_to_string(num); | |
1711 | if (e) { | |
1712 | s = strjoin(name, ":", e); | |
1713 | if (!s) | |
1714 | return NULL; | |
1715 | } else { | |
1716 | if (asprintf(&s, "%s:%d", name, num) < 0) | |
1717 | return NULL; | |
1718 | } | |
1719 | } else | |
1720 | s = TAKE_PTR(name); | |
1721 | ||
1722 | if (strv_consume(&l, s) < 0) | |
1723 | return NULL; | |
1724 | } | |
1725 | ||
1726 | strv_sort(l); | |
1727 | #endif | |
1728 | ||
1729 | return l ? TAKE_PTR(l) : strv_new(NULL); | |
1730 | } | |
1731 | ||
1732 | char** exec_context_get_syscall_archs(const ExecContext *c) { | |
1733 | _cleanup_strv_free_ char **l = NULL; | |
1734 | ||
1735 | assert(c); | |
1736 | ||
1737 | #if HAVE_SECCOMP | |
1738 | void *id; | |
1739 | SET_FOREACH(id, c->syscall_archs) { | |
1740 | const char *name; | |
1741 | ||
1742 | name = seccomp_arch_to_string(PTR_TO_UINT32(id) - 1); | |
1743 | if (!name) | |
1744 | continue; | |
1745 | ||
1746 | if (strv_extend(&l, name) < 0) | |
1747 | return NULL; | |
1748 | } | |
1749 | ||
1750 | strv_sort(l); | |
1751 | #endif | |
1752 | ||
1753 | return l ? TAKE_PTR(l) : strv_new(NULL); | |
1754 | } | |
1755 | ||
1756 | char** exec_context_get_syscall_log(const ExecContext *c) { | |
1757 | _cleanup_strv_free_ char **l = NULL; | |
1758 | ||
1759 | assert(c); | |
1760 | ||
1761 | #if HAVE_SECCOMP | |
1762 | void *id, *val; | |
1763 | HASHMAP_FOREACH_KEY(val, id, c->syscall_log) { | |
1764 | char *name = NULL; | |
1765 | ||
1766 | name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); | |
1767 | if (!name) | |
1768 | continue; | |
1769 | ||
1770 | if (strv_consume(&l, name) < 0) | |
1771 | return NULL; | |
1772 | } | |
1773 | ||
1774 | strv_sort(l); | |
1775 | #endif | |
1776 | ||
1777 | return l ? TAKE_PTR(l) : strv_new(NULL); | |
1778 | } | |
1779 | ||
1780 | char** exec_context_get_address_families(const ExecContext *c) { | |
1781 | _cleanup_strv_free_ char **l = NULL; | |
1782 | void *af; | |
1783 | ||
1784 | assert(c); | |
1785 | ||
1786 | SET_FOREACH(af, c->address_families) { | |
1787 | const char *name; | |
1788 | ||
1789 | name = af_to_name(PTR_TO_INT(af)); | |
1790 | if (!name) | |
1791 | continue; | |
1792 | ||
1793 | if (strv_extend(&l, name) < 0) | |
1794 | return NULL; | |
1795 | } | |
1796 | ||
1797 | strv_sort(l); | |
1798 | ||
1799 | return l ? TAKE_PTR(l) : strv_new(NULL); | |
1800 | } | |
1801 | ||
1802 | char** exec_context_get_restrict_filesystems(const ExecContext *c) { | |
1803 | _cleanup_strv_free_ char **l = NULL; | |
1804 | ||
1805 | assert(c); | |
1806 | ||
1807 | #if HAVE_LIBBPF | |
1808 | l = set_get_strv(c->restrict_filesystems); | |
1809 | if (!l) | |
1810 | return NULL; | |
1811 | ||
1812 | strv_sort(l); | |
1813 | #endif | |
1814 | ||
1815 | return l ? TAKE_PTR(l) : strv_new(NULL); | |
1816 | } | |
1817 | ||
1818 | void exec_status_start(ExecStatus *s, pid_t pid) { | |
1819 | assert(s); | |
1820 | ||
1821 | *s = (ExecStatus) { | |
1822 | .pid = pid, | |
1823 | }; | |
1824 | ||
1825 | dual_timestamp_now(&s->start_timestamp); | |
1826 | } | |
1827 | ||
1828 | void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int code, int status) { | |
1829 | assert(s); | |
1830 | ||
1831 | if (s->pid != pid) | |
1832 | *s = (ExecStatus) { | |
1833 | .pid = pid, | |
1834 | }; | |
1835 | ||
1836 | dual_timestamp_now(&s->exit_timestamp); | |
1837 | ||
1838 | s->code = code; | |
1839 | s->status = status; | |
1840 | ||
1841 | if (context && context->utmp_id) | |
1842 | (void) utmp_put_dead_process(context->utmp_id, pid, code, status); | |
1843 | } | |
1844 | ||
1845 | void exec_status_handoff(ExecStatus *s, const struct ucred *ucred, const dual_timestamp *ts) { | |
1846 | assert(s); | |
1847 | assert(ucred); | |
1848 | assert(ts); | |
1849 | ||
1850 | if (ucred->pid != s->pid) | |
1851 | *s = (ExecStatus) { | |
1852 | .pid = ucred->pid, | |
1853 | }; | |
1854 | ||
1855 | s->handoff_timestamp = *ts; | |
1856 | } | |
1857 | ||
1858 | void exec_status_reset(ExecStatus *s) { | |
1859 | assert(s); | |
1860 | ||
1861 | *s = (ExecStatus) {}; | |
1862 | } | |
1863 | ||
1864 | void exec_status_dump(const ExecStatus *s, FILE *f, const char *prefix) { | |
1865 | assert(s); | |
1866 | assert(f); | |
1867 | ||
1868 | if (s->pid <= 0) | |
1869 | return; | |
1870 | ||
1871 | prefix = strempty(prefix); | |
1872 | ||
1873 | fprintf(f, | |
1874 | "%sPID: "PID_FMT"\n", | |
1875 | prefix, s->pid); | |
1876 | ||
1877 | if (dual_timestamp_is_set(&s->start_timestamp)) | |
1878 | fprintf(f, | |
1879 | "%sStart Timestamp: %s\n", | |
1880 | prefix, FORMAT_TIMESTAMP(s->start_timestamp.realtime)); | |
1881 | ||
1882 | if (dual_timestamp_is_set(&s->handoff_timestamp)) | |
1883 | fprintf(f, | |
1884 | "%sHandoff Timestamp: %s\n", | |
1885 | prefix, FORMAT_TIMESTAMP(s->handoff_timestamp.realtime)); | |
1886 | ||
1887 | if (dual_timestamp_is_set(&s->exit_timestamp)) | |
1888 | fprintf(f, | |
1889 | "%sExit Timestamp: %s\n" | |
1890 | "%sExit Code: %s\n" | |
1891 | "%sExit Status: %i\n", | |
1892 | prefix, FORMAT_TIMESTAMP(s->exit_timestamp.realtime), | |
1893 | prefix, sigchld_code_to_string(s->code), | |
1894 | prefix, s->status); | |
1895 | } | |
1896 | ||
1897 | void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix) { | |
1898 | _cleanup_free_ char *cmd = NULL; | |
1899 | const char *prefix2; | |
1900 | ||
1901 | assert(c); | |
1902 | assert(f); | |
1903 | ||
1904 | prefix = strempty(prefix); | |
1905 | prefix2 = strjoina(prefix, "\t"); | |
1906 | ||
1907 | cmd = quote_command_line(c->argv, SHELL_ESCAPE_EMPTY); | |
1908 | ||
1909 | fprintf(f, | |
1910 | "%sCommand Line: %s\n", | |
1911 | prefix, strnull(cmd)); | |
1912 | ||
1913 | exec_status_dump(&c->exec_status, f, prefix2); | |
1914 | } | |
1915 | ||
1916 | void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix) { | |
1917 | assert(f); | |
1918 | ||
1919 | prefix = strempty(prefix); | |
1920 | ||
1921 | LIST_FOREACH(command, i, c) | |
1922 | exec_command_dump(i, f, prefix); | |
1923 | } | |
1924 | ||
1925 | void exec_command_append_list(ExecCommand **l, ExecCommand *e) { | |
1926 | ExecCommand *end; | |
1927 | ||
1928 | assert(l); | |
1929 | assert(e); | |
1930 | ||
1931 | if (*l) { | |
1932 | /* It's kind of important, that we keep the order here */ | |
1933 | end = LIST_FIND_TAIL(command, *l); | |
1934 | LIST_INSERT_AFTER(command, *l, end, e); | |
1935 | } else | |
1936 | *l = e; | |
1937 | } | |
1938 | ||
1939 | int exec_command_set(ExecCommand *c, const char *path, ...) { | |
1940 | va_list ap; | |
1941 | char **l, *p; | |
1942 | ||
1943 | assert(c); | |
1944 | assert(path); | |
1945 | ||
1946 | va_start(ap, path); | |
1947 | l = strv_new_ap(path, ap); | |
1948 | va_end(ap); | |
1949 | ||
1950 | if (!l) | |
1951 | return -ENOMEM; | |
1952 | ||
1953 | p = strdup(path); | |
1954 | if (!p) { | |
1955 | strv_free(l); | |
1956 | return -ENOMEM; | |
1957 | } | |
1958 | ||
1959 | free_and_replace(c->path, p); | |
1960 | ||
1961 | return strv_free_and_replace(c->argv, l); | |
1962 | } | |
1963 | ||
1964 | int exec_command_append(ExecCommand *c, const char *path, ...) { | |
1965 | _cleanup_strv_free_ char **l = NULL; | |
1966 | va_list ap; | |
1967 | int r; | |
1968 | ||
1969 | assert(c); | |
1970 | assert(path); | |
1971 | ||
1972 | va_start(ap, path); | |
1973 | l = strv_new_ap(path, ap); | |
1974 | va_end(ap); | |
1975 | ||
1976 | if (!l) | |
1977 | return -ENOMEM; | |
1978 | ||
1979 | r = strv_extend_strv(&c->argv, l, false); | |
1980 | if (r < 0) | |
1981 | return r; | |
1982 | ||
1983 | return 0; | |
1984 | } | |
1985 | ||
1986 | static char *destroy_tree(char *path) { | |
1987 | if (!path) | |
1988 | return NULL; | |
1989 | ||
1990 | if (!path_equal(path, RUN_SYSTEMD_EMPTY)) { | |
1991 | log_debug("Spawning process to nuke '%s'", path); | |
1992 | ||
1993 | (void) asynchronous_rm_rf(path, REMOVE_ROOT|REMOVE_SUBVOLUME|REMOVE_PHYSICAL); | |
1994 | } | |
1995 | ||
1996 | return mfree(path); | |
1997 | } | |
1998 | ||
1999 | void exec_shared_runtime_done(ExecSharedRuntime *rt) { | |
2000 | assert(rt); | |
2001 | ||
2002 | if (rt->manager) | |
2003 | (void) hashmap_remove(rt->manager->exec_shared_runtime_by_id, rt->id); | |
2004 | ||
2005 | rt->id = mfree(rt->id); | |
2006 | rt->tmp_dir = mfree(rt->tmp_dir); | |
2007 | rt->var_tmp_dir = mfree(rt->var_tmp_dir); | |
2008 | safe_close_pair(rt->netns_storage_socket); | |
2009 | safe_close_pair(rt->ipcns_storage_socket); | |
2010 | } | |
2011 | ||
2012 | static ExecSharedRuntime* exec_shared_runtime_free(ExecSharedRuntime *rt) { | |
2013 | if (!rt) | |
2014 | return NULL; | |
2015 | ||
2016 | exec_shared_runtime_done(rt); | |
2017 | return mfree(rt); | |
2018 | } | |
2019 | ||
2020 | DEFINE_TRIVIAL_UNREF_FUNC(ExecSharedRuntime, exec_shared_runtime, exec_shared_runtime_free); | |
2021 | DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSharedRuntime*, exec_shared_runtime_free); | |
2022 | ||
2023 | ExecSharedRuntime* exec_shared_runtime_destroy(ExecSharedRuntime *rt) { | |
2024 | if (!rt) | |
2025 | return NULL; | |
2026 | ||
2027 | assert(rt->n_ref > 0); | |
2028 | rt->n_ref--; | |
2029 | ||
2030 | if (rt->n_ref > 0) | |
2031 | return NULL; | |
2032 | ||
2033 | rt->tmp_dir = destroy_tree(rt->tmp_dir); | |
2034 | rt->var_tmp_dir = destroy_tree(rt->var_tmp_dir); | |
2035 | ||
2036 | return exec_shared_runtime_free(rt); | |
2037 | } | |
2038 | ||
2039 | static int exec_shared_runtime_allocate(ExecSharedRuntime **ret, const char *id) { | |
2040 | _cleanup_free_ char *id_copy = NULL; | |
2041 | ExecSharedRuntime *n; | |
2042 | ||
2043 | assert(ret); | |
2044 | ||
2045 | id_copy = strdup(id); | |
2046 | if (!id_copy) | |
2047 | return -ENOMEM; | |
2048 | ||
2049 | n = new(ExecSharedRuntime, 1); | |
2050 | if (!n) | |
2051 | return -ENOMEM; | |
2052 | ||
2053 | *n = (ExecSharedRuntime) { | |
2054 | .id = TAKE_PTR(id_copy), | |
2055 | .netns_storage_socket = EBADF_PAIR, | |
2056 | .ipcns_storage_socket = EBADF_PAIR, | |
2057 | }; | |
2058 | ||
2059 | *ret = n; | |
2060 | return 0; | |
2061 | } | |
2062 | ||
2063 | static int exec_shared_runtime_add( | |
2064 | Manager *m, | |
2065 | const char *id, | |
2066 | char **tmp_dir, | |
2067 | char **var_tmp_dir, | |
2068 | int netns_storage_socket[2], | |
2069 | int ipcns_storage_socket[2], | |
2070 | ExecSharedRuntime **ret) { | |
2071 | ||
2072 | _cleanup_(exec_shared_runtime_freep) ExecSharedRuntime *rt = NULL; | |
2073 | int r; | |
2074 | ||
2075 | assert(m); | |
2076 | assert(id); | |
2077 | ||
2078 | /* tmp_dir, var_tmp_dir, {net,ipc}ns_storage_socket fds are donated on success */ | |
2079 | ||
2080 | r = exec_shared_runtime_allocate(&rt, id); | |
2081 | if (r < 0) | |
2082 | return r; | |
2083 | ||
2084 | r = hashmap_ensure_put(&m->exec_shared_runtime_by_id, &string_hash_ops, rt->id, rt); | |
2085 | if (r < 0) | |
2086 | return r; | |
2087 | ||
2088 | assert(!!rt->tmp_dir == !!rt->var_tmp_dir); /* We require both to be set together */ | |
2089 | rt->tmp_dir = TAKE_PTR(*tmp_dir); | |
2090 | rt->var_tmp_dir = TAKE_PTR(*var_tmp_dir); | |
2091 | ||
2092 | if (netns_storage_socket) { | |
2093 | rt->netns_storage_socket[0] = TAKE_FD(netns_storage_socket[0]); | |
2094 | rt->netns_storage_socket[1] = TAKE_FD(netns_storage_socket[1]); | |
2095 | } | |
2096 | ||
2097 | if (ipcns_storage_socket) { | |
2098 | rt->ipcns_storage_socket[0] = TAKE_FD(ipcns_storage_socket[0]); | |
2099 | rt->ipcns_storage_socket[1] = TAKE_FD(ipcns_storage_socket[1]); | |
2100 | } | |
2101 | ||
2102 | rt->manager = m; | |
2103 | ||
2104 | if (ret) | |
2105 | *ret = rt; | |
2106 | /* do not remove created ExecSharedRuntime object when the operation succeeds. */ | |
2107 | TAKE_PTR(rt); | |
2108 | return 0; | |
2109 | } | |
2110 | ||
2111 | static int exec_shared_runtime_make( | |
2112 | Manager *m, | |
2113 | const ExecContext *c, | |
2114 | const char *id, | |
2115 | ExecSharedRuntime **ret) { | |
2116 | ||
2117 | _cleanup_(namespace_cleanup_tmpdirp) char *tmp_dir = NULL, *var_tmp_dir = NULL; | |
2118 | _cleanup_close_pair_ int netns_storage_socket[2] = EBADF_PAIR, ipcns_storage_socket[2] = EBADF_PAIR; | |
2119 | int r; | |
2120 | ||
2121 | assert(m); | |
2122 | assert(c); | |
2123 | assert(id); | |
2124 | ||
2125 | /* It is not necessary to create ExecSharedRuntime object. */ | |
2126 | if (!exec_needs_network_namespace(c) && !exec_needs_ipc_namespace(c) && !c->private_tmp) { | |
2127 | *ret = NULL; | |
2128 | return 0; | |
2129 | } | |
2130 | ||
2131 | if (c->private_tmp && | |
2132 | !(prefixed_path_strv_contains(c->inaccessible_paths, "/tmp") && | |
2133 | (prefixed_path_strv_contains(c->inaccessible_paths, "/var/tmp") || | |
2134 | prefixed_path_strv_contains(c->inaccessible_paths, "/var")))) { | |
2135 | r = setup_tmp_dirs(id, &tmp_dir, &var_tmp_dir); | |
2136 | if (r < 0) | |
2137 | return r; | |
2138 | } | |
2139 | ||
2140 | if (exec_needs_network_namespace(c)) | |
2141 | if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, netns_storage_socket) < 0) | |
2142 | return -errno; | |
2143 | ||
2144 | if (exec_needs_ipc_namespace(c)) | |
2145 | if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, ipcns_storage_socket) < 0) | |
2146 | return -errno; | |
2147 | ||
2148 | r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_storage_socket, ipcns_storage_socket, ret); | |
2149 | if (r < 0) | |
2150 | return r; | |
2151 | ||
2152 | return 1; | |
2153 | } | |
2154 | ||
2155 | int exec_shared_runtime_acquire(Manager *m, const ExecContext *c, const char *id, bool create, ExecSharedRuntime **ret) { | |
2156 | ExecSharedRuntime *rt; | |
2157 | int r; | |
2158 | ||
2159 | assert(m); | |
2160 | assert(id); | |
2161 | assert(ret); | |
2162 | ||
2163 | rt = hashmap_get(m->exec_shared_runtime_by_id, id); | |
2164 | if (rt) | |
2165 | /* We already have an ExecSharedRuntime object, let's increase the ref count and reuse it */ | |
2166 | goto ref; | |
2167 | ||
2168 | if (!create) { | |
2169 | *ret = NULL; | |
2170 | return 0; | |
2171 | } | |
2172 | ||
2173 | /* If not found, then create a new object. */ | |
2174 | r = exec_shared_runtime_make(m, c, id, &rt); | |
2175 | if (r < 0) | |
2176 | return r; | |
2177 | if (r == 0) { | |
2178 | /* When r == 0, it is not necessary to create ExecSharedRuntime object. */ | |
2179 | *ret = NULL; | |
2180 | return 0; | |
2181 | } | |
2182 | ||
2183 | ref: | |
2184 | /* increment reference counter. */ | |
2185 | rt->n_ref++; | |
2186 | *ret = rt; | |
2187 | return 1; | |
2188 | } | |
2189 | ||
2190 | int exec_shared_runtime_serialize(const Manager *m, FILE *f, FDSet *fds) { | |
2191 | ExecSharedRuntime *rt; | |
2192 | ||
2193 | assert(m); | |
2194 | assert(f); | |
2195 | assert(fds); | |
2196 | ||
2197 | HASHMAP_FOREACH(rt, m->exec_shared_runtime_by_id) { | |
2198 | fprintf(f, "exec-runtime=%s", rt->id); | |
2199 | ||
2200 | if (rt->tmp_dir) | |
2201 | fprintf(f, " tmp-dir=%s", rt->tmp_dir); | |
2202 | ||
2203 | if (rt->var_tmp_dir) | |
2204 | fprintf(f, " var-tmp-dir=%s", rt->var_tmp_dir); | |
2205 | ||
2206 | if (rt->netns_storage_socket[0] >= 0) { | |
2207 | int copy; | |
2208 | ||
2209 | copy = fdset_put_dup(fds, rt->netns_storage_socket[0]); | |
2210 | if (copy < 0) | |
2211 | return copy; | |
2212 | ||
2213 | fprintf(f, " netns-socket-0=%i", copy); | |
2214 | } | |
2215 | ||
2216 | if (rt->netns_storage_socket[1] >= 0) { | |
2217 | int copy; | |
2218 | ||
2219 | copy = fdset_put_dup(fds, rt->netns_storage_socket[1]); | |
2220 | if (copy < 0) | |
2221 | return copy; | |
2222 | ||
2223 | fprintf(f, " netns-socket-1=%i", copy); | |
2224 | } | |
2225 | ||
2226 | if (rt->ipcns_storage_socket[0] >= 0) { | |
2227 | int copy; | |
2228 | ||
2229 | copy = fdset_put_dup(fds, rt->ipcns_storage_socket[0]); | |
2230 | if (copy < 0) | |
2231 | return copy; | |
2232 | ||
2233 | fprintf(f, " ipcns-socket-0=%i", copy); | |
2234 | } | |
2235 | ||
2236 | if (rt->ipcns_storage_socket[1] >= 0) { | |
2237 | int copy; | |
2238 | ||
2239 | copy = fdset_put_dup(fds, rt->ipcns_storage_socket[1]); | |
2240 | if (copy < 0) | |
2241 | return copy; | |
2242 | ||
2243 | fprintf(f, " ipcns-socket-1=%i", copy); | |
2244 | } | |
2245 | ||
2246 | fputc('\n', f); | |
2247 | } | |
2248 | ||
2249 | return 0; | |
2250 | } | |
2251 | ||
2252 | int exec_shared_runtime_deserialize_compat(Unit *u, const char *key, const char *value, FDSet *fds) { | |
2253 | _cleanup_(exec_shared_runtime_freep) ExecSharedRuntime *rt_create = NULL; | |
2254 | ExecSharedRuntime *rt = NULL; | |
2255 | int r; | |
2256 | ||
2257 | /* This is for the migration from old (v237 or earlier) deserialization text. | |
2258 | * Due to the bug #7790, this may not work with the units that use JoinsNamespaceOf=. | |
2259 | * Even if the ExecSharedRuntime object originally created by the other unit, we cannot judge | |
2260 | * so or not from the serialized text, then we always creates a new object owned by this. */ | |
2261 | ||
2262 | assert(u); | |
2263 | assert(key); | |
2264 | assert(value); | |
2265 | ||
2266 | /* Manager manages ExecSharedRuntime objects by the unit id. | |
2267 | * So, we omit the serialized text when the unit does not have id (yet?)... */ | |
2268 | if (isempty(u->id)) { | |
2269 | log_unit_debug(u, "Invocation ID not found. Dropping runtime parameter."); | |
2270 | return 0; | |
2271 | } | |
2272 | ||
2273 | if (u->manager) { | |
2274 | if (hashmap_ensure_allocated(&u->manager->exec_shared_runtime_by_id, &string_hash_ops) < 0) | |
2275 | return log_oom(); | |
2276 | ||
2277 | rt = hashmap_get(u->manager->exec_shared_runtime_by_id, u->id); | |
2278 | } | |
2279 | if (!rt) { | |
2280 | if (exec_shared_runtime_allocate(&rt_create, u->id) < 0) | |
2281 | return log_oom(); | |
2282 | ||
2283 | rt = rt_create; | |
2284 | } | |
2285 | ||
2286 | if (streq(key, "tmp-dir")) { | |
2287 | if (free_and_strdup_warn(&rt->tmp_dir, value) < 0) | |
2288 | return -ENOMEM; | |
2289 | ||
2290 | } else if (streq(key, "var-tmp-dir")) { | |
2291 | if (free_and_strdup_warn(&rt->var_tmp_dir, value) < 0) | |
2292 | return -ENOMEM; | |
2293 | ||
2294 | } else if (streq(key, "netns-socket-0")) { | |
2295 | ||
2296 | safe_close(rt->netns_storage_socket[0]); | |
2297 | rt->netns_storage_socket[0] = deserialize_fd(fds, value); | |
2298 | if (rt->netns_storage_socket[0] < 0) | |
2299 | return 0; | |
2300 | ||
2301 | } else if (streq(key, "netns-socket-1")) { | |
2302 | ||
2303 | safe_close(rt->netns_storage_socket[1]); | |
2304 | rt->netns_storage_socket[1] = deserialize_fd(fds, value); | |
2305 | if (rt->netns_storage_socket[1] < 0) | |
2306 | return 0; | |
2307 | } else | |
2308 | return 0; | |
2309 | ||
2310 | /* If the object is newly created, then put it to the hashmap which manages ExecSharedRuntime objects. */ | |
2311 | if (rt_create && u->manager) { | |
2312 | r = hashmap_put(u->manager->exec_shared_runtime_by_id, rt_create->id, rt_create); | |
2313 | if (r < 0) { | |
2314 | log_unit_debug_errno(u, r, "Failed to put runtime parameter to manager's storage: %m"); | |
2315 | return 0; | |
2316 | } | |
2317 | ||
2318 | rt_create->manager = u->manager; | |
2319 | ||
2320 | /* Avoid cleanup */ | |
2321 | TAKE_PTR(rt_create); | |
2322 | } | |
2323 | ||
2324 | return 1; | |
2325 | } | |
2326 | ||
2327 | int exec_shared_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) { | |
2328 | _cleanup_free_ char *tmp_dir = NULL, *var_tmp_dir = NULL; | |
2329 | char *id = NULL; | |
2330 | int r, netns_fdpair[] = {-1, -1}, ipcns_fdpair[] = {-1, -1}; | |
2331 | const char *p, *v = ASSERT_PTR(value); | |
2332 | size_t n; | |
2333 | ||
2334 | assert(m); | |
2335 | assert(fds); | |
2336 | ||
2337 | n = strcspn(v, " "); | |
2338 | id = strndupa_safe(v, n); | |
2339 | if (v[n] != ' ') | |
2340 | goto finalize; | |
2341 | p = v + n + 1; | |
2342 | ||
2343 | v = startswith(p, "tmp-dir="); | |
2344 | if (v) { | |
2345 | n = strcspn(v, " "); | |
2346 | tmp_dir = strndup(v, n); | |
2347 | if (!tmp_dir) | |
2348 | return log_oom(); | |
2349 | if (v[n] != ' ') | |
2350 | goto finalize; | |
2351 | p = v + n + 1; | |
2352 | } | |
2353 | ||
2354 | v = startswith(p, "var-tmp-dir="); | |
2355 | if (v) { | |
2356 | n = strcspn(v, " "); | |
2357 | var_tmp_dir = strndup(v, n); | |
2358 | if (!var_tmp_dir) | |
2359 | return log_oom(); | |
2360 | if (v[n] != ' ') | |
2361 | goto finalize; | |
2362 | p = v + n + 1; | |
2363 | } | |
2364 | ||
2365 | v = startswith(p, "netns-socket-0="); | |
2366 | if (v) { | |
2367 | char *buf; | |
2368 | ||
2369 | n = strcspn(v, " "); | |
2370 | buf = strndupa_safe(v, n); | |
2371 | ||
2372 | netns_fdpair[0] = deserialize_fd(fds, buf); | |
2373 | if (netns_fdpair[0] < 0) | |
2374 | return netns_fdpair[0]; | |
2375 | if (v[n] != ' ') | |
2376 | goto finalize; | |
2377 | p = v + n + 1; | |
2378 | } | |
2379 | ||
2380 | v = startswith(p, "netns-socket-1="); | |
2381 | if (v) { | |
2382 | char *buf; | |
2383 | ||
2384 | n = strcspn(v, " "); | |
2385 | buf = strndupa_safe(v, n); | |
2386 | ||
2387 | netns_fdpair[1] = deserialize_fd(fds, buf); | |
2388 | if (netns_fdpair[1] < 0) | |
2389 | return netns_fdpair[1]; | |
2390 | if (v[n] != ' ') | |
2391 | goto finalize; | |
2392 | p = v + n + 1; | |
2393 | } | |
2394 | ||
2395 | v = startswith(p, "ipcns-socket-0="); | |
2396 | if (v) { | |
2397 | char *buf; | |
2398 | ||
2399 | n = strcspn(v, " "); | |
2400 | buf = strndupa_safe(v, n); | |
2401 | ||
2402 | ipcns_fdpair[0] = deserialize_fd(fds, buf); | |
2403 | if (ipcns_fdpair[0] < 0) | |
2404 | return ipcns_fdpair[0]; | |
2405 | if (v[n] != ' ') | |
2406 | goto finalize; | |
2407 | p = v + n + 1; | |
2408 | } | |
2409 | ||
2410 | v = startswith(p, "ipcns-socket-1="); | |
2411 | if (v) { | |
2412 | char *buf; | |
2413 | ||
2414 | n = strcspn(v, " "); | |
2415 | buf = strndupa_safe(v, n); | |
2416 | ||
2417 | ipcns_fdpair[1] = deserialize_fd(fds, buf); | |
2418 | if (ipcns_fdpair[1] < 0) | |
2419 | return ipcns_fdpair[1]; | |
2420 | } | |
2421 | ||
2422 | finalize: | |
2423 | r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_fdpair, ipcns_fdpair, NULL); | |
2424 | if (r < 0) | |
2425 | return log_debug_errno(r, "Failed to add exec-runtime: %m"); | |
2426 | return 0; | |
2427 | } | |
2428 | ||
2429 | void exec_shared_runtime_vacuum(Manager *m) { | |
2430 | ExecSharedRuntime *rt; | |
2431 | ||
2432 | assert(m); | |
2433 | ||
2434 | /* Free unreferenced ExecSharedRuntime objects. This is used after manager deserialization process. */ | |
2435 | ||
2436 | HASHMAP_FOREACH(rt, m->exec_shared_runtime_by_id) { | |
2437 | if (rt->n_ref > 0) | |
2438 | continue; | |
2439 | ||
2440 | (void) exec_shared_runtime_free(rt); | |
2441 | } | |
2442 | } | |
2443 | ||
2444 | int exec_runtime_make( | |
2445 | const Unit *unit, | |
2446 | const ExecContext *context, | |
2447 | ExecSharedRuntime *shared, | |
2448 | DynamicCreds *creds, | |
2449 | ExecRuntime **ret) { | |
2450 | _cleanup_close_pair_ int ephemeral_storage_socket[2] = EBADF_PAIR; | |
2451 | _cleanup_free_ char *ephemeral = NULL; | |
2452 | _cleanup_(exec_runtime_freep) ExecRuntime *rt = NULL; | |
2453 | int r; | |
2454 | ||
2455 | assert(unit); | |
2456 | assert(context); | |
2457 | assert(ret); | |
2458 | ||
2459 | if (!shared && !creds && !exec_needs_ephemeral(context)) { | |
2460 | *ret = NULL; | |
2461 | return 0; | |
2462 | } | |
2463 | ||
2464 | if (exec_needs_ephemeral(context)) { | |
2465 | r = mkdir_p("/var/lib/systemd/ephemeral-trees", 0755); | |
2466 | if (r < 0) | |
2467 | return r; | |
2468 | ||
2469 | r = tempfn_random_child("/var/lib/systemd/ephemeral-trees", unit->id, &ephemeral); | |
2470 | if (r < 0) | |
2471 | return r; | |
2472 | ||
2473 | if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, ephemeral_storage_socket) < 0) | |
2474 | return -errno; | |
2475 | } | |
2476 | ||
2477 | rt = new(ExecRuntime, 1); | |
2478 | if (!rt) | |
2479 | return -ENOMEM; | |
2480 | ||
2481 | *rt = (ExecRuntime) { | |
2482 | .shared = shared, | |
2483 | .dynamic_creds = creds, | |
2484 | .ephemeral_copy = TAKE_PTR(ephemeral), | |
2485 | .ephemeral_storage_socket[0] = TAKE_FD(ephemeral_storage_socket[0]), | |
2486 | .ephemeral_storage_socket[1] = TAKE_FD(ephemeral_storage_socket[1]), | |
2487 | }; | |
2488 | ||
2489 | *ret = TAKE_PTR(rt); | |
2490 | return 1; | |
2491 | } | |
2492 | ||
2493 | ExecRuntime* exec_runtime_free(ExecRuntime *rt) { | |
2494 | if (!rt) | |
2495 | return NULL; | |
2496 | ||
2497 | exec_shared_runtime_unref(rt->shared); | |
2498 | dynamic_creds_unref(rt->dynamic_creds); | |
2499 | ||
2500 | rt->ephemeral_copy = destroy_tree(rt->ephemeral_copy); | |
2501 | ||
2502 | safe_close_pair(rt->ephemeral_storage_socket); | |
2503 | return mfree(rt); | |
2504 | } | |
2505 | ||
2506 | ExecRuntime* exec_runtime_destroy(ExecRuntime *rt) { | |
2507 | if (!rt) | |
2508 | return NULL; | |
2509 | ||
2510 | rt->shared = exec_shared_runtime_destroy(rt->shared); | |
2511 | rt->dynamic_creds = dynamic_creds_destroy(rt->dynamic_creds); | |
2512 | return exec_runtime_free(rt); | |
2513 | } | |
2514 | ||
2515 | void exec_runtime_clear(ExecRuntime *rt) { | |
2516 | if (!rt) | |
2517 | return; | |
2518 | ||
2519 | safe_close_pair(rt->ephemeral_storage_socket); | |
2520 | rt->ephemeral_copy = mfree(rt->ephemeral_copy); | |
2521 | } | |
2522 | ||
2523 | void exec_params_shallow_clear(ExecParameters *p) { | |
2524 | if (!p) | |
2525 | return; | |
2526 | ||
2527 | /* This is called on the PID1 side, as many of the struct's FDs are only borrowed, and actually | |
2528 | * owned by the manager or other objects, and reused across multiple units. */ | |
2529 | ||
2530 | p->environment = strv_free(p->environment); | |
2531 | p->fd_names = strv_free(p->fd_names); | |
2532 | p->files_env = strv_free(p->files_env); | |
2533 | p->fds = mfree(p->fds); | |
2534 | p->exec_fd = safe_close(p->exec_fd); | |
2535 | p->user_lookup_fd = -EBADF; | |
2536 | p->bpf_restrict_fs_map_fd = -EBADF; | |
2537 | p->unit_id = mfree(p->unit_id); | |
2538 | p->invocation_id = SD_ID128_NULL; | |
2539 | p->invocation_id_string[0] = '\0'; | |
2540 | p->confirm_spawn = mfree(p->confirm_spawn); | |
2541 | } | |
2542 | ||
2543 | void exec_params_deep_clear(ExecParameters *p) { | |
2544 | if (!p) | |
2545 | return; | |
2546 | ||
2547 | /* This is called on the sd-executor side, where everything received is owned by the process and has | |
2548 | * to be fully cleaned up to make sanitizers and analyzers happy, as opposed as the shallow clean | |
2549 | * function above. */ | |
2550 | ||
2551 | close_many_unset(p->fds, p->n_socket_fds + p->n_storage_fds); | |
2552 | ||
2553 | p->cgroup_path = mfree(p->cgroup_path); | |
2554 | ||
2555 | if (p->prefix) { | |
2556 | free_many_charp(p->prefix, _EXEC_DIRECTORY_TYPE_MAX); | |
2557 | p->prefix = mfree(p->prefix); | |
2558 | } | |
2559 | ||
2560 | p->received_credentials_directory = mfree(p->received_credentials_directory); | |
2561 | p->received_encrypted_credentials_directory = mfree(p->received_encrypted_credentials_directory); | |
2562 | ||
2563 | if (p->idle_pipe) { | |
2564 | close_many_and_free(p->idle_pipe, 4); | |
2565 | p->idle_pipe = NULL; | |
2566 | } | |
2567 | ||
2568 | p->stdin_fd = safe_close(p->stdin_fd); | |
2569 | p->stdout_fd = safe_close(p->stdout_fd); | |
2570 | p->stderr_fd = safe_close(p->stderr_fd); | |
2571 | ||
2572 | p->notify_socket = mfree(p->notify_socket); | |
2573 | ||
2574 | open_file_free_many(&p->open_files); | |
2575 | ||
2576 | p->fallback_smack_process_label = mfree(p->fallback_smack_process_label); | |
2577 | ||
2578 | exec_params_shallow_clear(p); | |
2579 | } | |
2580 | ||
2581 | void exec_directory_done(ExecDirectory *d) { | |
2582 | if (!d) | |
2583 | return; | |
2584 | ||
2585 | FOREACH_ARRAY(i, d->items, d->n_items) { | |
2586 | free(i->path); | |
2587 | strv_free(i->symlinks); | |
2588 | } | |
2589 | ||
2590 | d->items = mfree(d->items); | |
2591 | d->n_items = 0; | |
2592 | d->mode = 0755; | |
2593 | } | |
2594 | ||
2595 | static ExecDirectoryItem *exec_directory_find(ExecDirectory *d, const char *path) { | |
2596 | assert(d); | |
2597 | assert(path); | |
2598 | ||
2599 | FOREACH_ARRAY(i, d->items, d->n_items) | |
2600 | if (path_equal(i->path, path)) | |
2601 | return i; | |
2602 | ||
2603 | return NULL; | |
2604 | } | |
2605 | ||
2606 | int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink) { | |
2607 | _cleanup_strv_free_ char **s = NULL; | |
2608 | _cleanup_free_ char *p = NULL; | |
2609 | ExecDirectoryItem *existing; | |
2610 | int r; | |
2611 | ||
2612 | assert(d); | |
2613 | assert(path); | |
2614 | ||
2615 | existing = exec_directory_find(d, path); | |
2616 | if (existing) { | |
2617 | r = strv_extend(&existing->symlinks, symlink); | |
2618 | if (r < 0) | |
2619 | return r; | |
2620 | ||
2621 | return 0; /* existing item is updated */ | |
2622 | } | |
2623 | ||
2624 | p = strdup(path); | |
2625 | if (!p) | |
2626 | return -ENOMEM; | |
2627 | ||
2628 | if (symlink) { | |
2629 | s = strv_new(symlink); | |
2630 | if (!s) | |
2631 | return -ENOMEM; | |
2632 | } | |
2633 | ||
2634 | if (!GREEDY_REALLOC(d->items, d->n_items + 1)) | |
2635 | return -ENOMEM; | |
2636 | ||
2637 | d->items[d->n_items++] = (ExecDirectoryItem) { | |
2638 | .path = TAKE_PTR(p), | |
2639 | .symlinks = TAKE_PTR(s), | |
2640 | }; | |
2641 | ||
2642 | return 1; /* new item is added */ | |
2643 | } | |
2644 | ||
2645 | static int exec_directory_item_compare_func(const ExecDirectoryItem *a, const ExecDirectoryItem *b) { | |
2646 | assert(a); | |
2647 | assert(b); | |
2648 | ||
2649 | return path_compare(a->path, b->path); | |
2650 | } | |
2651 | ||
2652 | void exec_directory_sort(ExecDirectory *d) { | |
2653 | assert(d); | |
2654 | ||
2655 | /* Sort the exec directories to make always parent directories processed at first in | |
2656 | * setup_exec_directory(), e.g., even if StateDirectory=foo/bar foo, we need to create foo at first, | |
2657 | * then foo/bar. Also, set .only_create flag if one of the parent directories is contained in the | |
2658 | * list. See also comments in setup_exec_directory() and issue #24783. */ | |
2659 | ||
2660 | if (d->n_items <= 1) | |
2661 | return; | |
2662 | ||
2663 | typesafe_qsort(d->items, d->n_items, exec_directory_item_compare_func); | |
2664 | ||
2665 | for (size_t i = 1; i < d->n_items; i++) | |
2666 | for (size_t j = 0; j < i; j++) | |
2667 | if (path_startswith(d->items[i].path, d->items[j].path)) { | |
2668 | d->items[i].only_create = true; | |
2669 | break; | |
2670 | } | |
2671 | } | |
2672 | ||
2673 | ExecCleanMask exec_clean_mask_from_string(const char *s) { | |
2674 | ExecDirectoryType t; | |
2675 | ||
2676 | assert(s); | |
2677 | ||
2678 | if (streq(s, "all")) | |
2679 | return EXEC_CLEAN_ALL; | |
2680 | if (streq(s, "fdstore")) | |
2681 | return EXEC_CLEAN_FDSTORE; | |
2682 | ||
2683 | t = exec_resource_type_from_string(s); | |
2684 | if (t < 0) | |
2685 | return (ExecCleanMask) t; | |
2686 | ||
2687 | return 1U << t; | |
2688 | } | |
2689 | ||
2690 | static const char* const exec_input_table[_EXEC_INPUT_MAX] = { | |
2691 | [EXEC_INPUT_NULL] = "null", | |
2692 | [EXEC_INPUT_TTY] = "tty", | |
2693 | [EXEC_INPUT_TTY_FORCE] = "tty-force", | |
2694 | [EXEC_INPUT_TTY_FAIL] = "tty-fail", | |
2695 | [EXEC_INPUT_SOCKET] = "socket", | |
2696 | [EXEC_INPUT_NAMED_FD] = "fd", | |
2697 | [EXEC_INPUT_DATA] = "data", | |
2698 | [EXEC_INPUT_FILE] = "file", | |
2699 | }; | |
2700 | ||
2701 | DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput); | |
2702 | ||
2703 | static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = { | |
2704 | [EXEC_OUTPUT_INHERIT] = "inherit", | |
2705 | [EXEC_OUTPUT_NULL] = "null", | |
2706 | [EXEC_OUTPUT_TTY] = "tty", | |
2707 | [EXEC_OUTPUT_KMSG] = "kmsg", | |
2708 | [EXEC_OUTPUT_KMSG_AND_CONSOLE] = "kmsg+console", | |
2709 | [EXEC_OUTPUT_JOURNAL] = "journal", | |
2710 | [EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console", | |
2711 | [EXEC_OUTPUT_SOCKET] = "socket", | |
2712 | [EXEC_OUTPUT_NAMED_FD] = "fd", | |
2713 | [EXEC_OUTPUT_FILE] = "file", | |
2714 | [EXEC_OUTPUT_FILE_APPEND] = "append", | |
2715 | [EXEC_OUTPUT_FILE_TRUNCATE] = "truncate", | |
2716 | }; | |
2717 | ||
2718 | DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput); | |
2719 | ||
2720 | static const char* const exec_utmp_mode_table[_EXEC_UTMP_MODE_MAX] = { | |
2721 | [EXEC_UTMP_INIT] = "init", | |
2722 | [EXEC_UTMP_LOGIN] = "login", | |
2723 | [EXEC_UTMP_USER] = "user", | |
2724 | }; | |
2725 | ||
2726 | DEFINE_STRING_TABLE_LOOKUP(exec_utmp_mode, ExecUtmpMode); | |
2727 | ||
2728 | static const char* const exec_preserve_mode_table[_EXEC_PRESERVE_MODE_MAX] = { | |
2729 | [EXEC_PRESERVE_NO] = "no", | |
2730 | [EXEC_PRESERVE_YES] = "yes", | |
2731 | [EXEC_PRESERVE_RESTART] = "restart", | |
2732 | }; | |
2733 | ||
2734 | DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(exec_preserve_mode, ExecPreserveMode, EXEC_PRESERVE_YES); | |
2735 | ||
2736 | /* This table maps ExecDirectoryType to the setting it is configured with in the unit */ | |
2737 | static const char* const exec_directory_type_table[_EXEC_DIRECTORY_TYPE_MAX] = { | |
2738 | [EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectory", | |
2739 | [EXEC_DIRECTORY_STATE] = "StateDirectory", | |
2740 | [EXEC_DIRECTORY_CACHE] = "CacheDirectory", | |
2741 | [EXEC_DIRECTORY_LOGS] = "LogsDirectory", | |
2742 | [EXEC_DIRECTORY_CONFIGURATION] = "ConfigurationDirectory", | |
2743 | }; | |
2744 | ||
2745 | DEFINE_STRING_TABLE_LOOKUP(exec_directory_type, ExecDirectoryType); | |
2746 | ||
2747 | /* This table maps ExecDirectoryType to the symlink setting it is configured with in the unit */ | |
2748 | static const char* const exec_directory_type_symlink_table[_EXEC_DIRECTORY_TYPE_MAX] = { | |
2749 | [EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectorySymlink", | |
2750 | [EXEC_DIRECTORY_STATE] = "StateDirectorySymlink", | |
2751 | [EXEC_DIRECTORY_CACHE] = "CacheDirectorySymlink", | |
2752 | [EXEC_DIRECTORY_LOGS] = "LogsDirectorySymlink", | |
2753 | [EXEC_DIRECTORY_CONFIGURATION] = "ConfigurationDirectorySymlink", | |
2754 | }; | |
2755 | ||
2756 | DEFINE_STRING_TABLE_LOOKUP(exec_directory_type_symlink, ExecDirectoryType); | |
2757 | ||
2758 | static const char* const exec_directory_type_mode_table[_EXEC_DIRECTORY_TYPE_MAX] = { | |
2759 | [EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectoryMode", | |
2760 | [EXEC_DIRECTORY_STATE] = "StateDirectoryMode", | |
2761 | [EXEC_DIRECTORY_CACHE] = "CacheDirectoryMode", | |
2762 | [EXEC_DIRECTORY_LOGS] = "LogsDirectoryMode", | |
2763 | [EXEC_DIRECTORY_CONFIGURATION] = "ConfigurationDirectoryMode", | |
2764 | }; | |
2765 | ||
2766 | DEFINE_STRING_TABLE_LOOKUP(exec_directory_type_mode, ExecDirectoryType); | |
2767 | ||
2768 | /* And this table maps ExecDirectoryType too, but to a generic term identifying the type of resource. This | |
2769 | * one is supposed to be generic enough to be used for unit types that don't use ExecContext and per-unit | |
2770 | * directories, specifically .timer units with their timestamp touch file. */ | |
2771 | static const char* const exec_resource_type_table[_EXEC_DIRECTORY_TYPE_MAX] = { | |
2772 | [EXEC_DIRECTORY_RUNTIME] = "runtime", | |
2773 | [EXEC_DIRECTORY_STATE] = "state", | |
2774 | [EXEC_DIRECTORY_CACHE] = "cache", | |
2775 | [EXEC_DIRECTORY_LOGS] = "logs", | |
2776 | [EXEC_DIRECTORY_CONFIGURATION] = "configuration", | |
2777 | }; | |
2778 | ||
2779 | DEFINE_STRING_TABLE_LOOKUP(exec_resource_type, ExecDirectoryType); | |
2780 | ||
2781 | static const char* const exec_keyring_mode_table[_EXEC_KEYRING_MODE_MAX] = { | |
2782 | [EXEC_KEYRING_INHERIT] = "inherit", | |
2783 | [EXEC_KEYRING_PRIVATE] = "private", | |
2784 | [EXEC_KEYRING_SHARED] = "shared", | |
2785 | }; | |
2786 | ||
2787 | DEFINE_STRING_TABLE_LOOKUP(exec_keyring_mode, ExecKeyringMode); |