• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 12940458095

23 Jan 2025 10:34PM UTC coverage: 71.463% (+0.02%) from 71.444%
12940458095

push

github

web-flow
nspawn: support unpriv directory-tree containers (#35685)

So far nspawn supported unpriv containers only if backed by a DDI. This
adds dir-based unpriv containers too.

To make this work this introduces a new UID concept to systemd: the
"foreign UID range". This is a high UID range of size 64K. The idea is
that disk images that are "foreign" to the local system can use that,
and when a container or similar is invoked from it, a transiently
allocated dynamic UID range is mapped from that foreign UID range via id
mapped mounts.

This means the fully dynamic, transient UID ranges never hit the disk,
which should vastly simplify management, and does not require that uid
"subranges" are persistently delegated to any users.

The mountfsd daemon gained a new method call for acquiring an idmapped
mount fd for an mount tree owned by the foreign UID range. Access is
permitted to unpriv clients – as long as the referenced inode is located
within a dir owned by client's own uid range.

166 of 349 new or added lines in 6 files covered. (47.56%)

2432 existing lines in 65 files now uncovered.

291664 of 408131 relevant lines covered (71.46%)

709262.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.9
/src/shared/exec-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <dirent.h>
4
#include <errno.h>
5
#include <sys/prctl.h>
6
#include <sys/types.h>
7
#include <unistd.h>
8
#include <stdio.h>
9

10
#include "alloc-util.h"
11
#include "bitfield.h"
12
#include "conf-files.h"
13
#include "env-file.h"
14
#include "env-util.h"
15
#include "errno-util.h"
16
#include "escape.h"
17
#include "exec-util.h"
18
#include "fd-util.h"
19
#include "fileio.h"
20
#include "hashmap.h"
21
#include "macro.h"
22
#include "missing_syscall.h"
23
#include "path-util.h"
24
#include "process-util.h"
25
#include "serialize.h"
26
#include "set.h"
27
#include "signal-util.h"
28
#include "stat-util.h"
29
#include "string-table.h"
30
#include "string-util.h"
31
#include "strv.h"
32
#include "terminal-util.h"
33
#include "tmpfile-util.h"
34

35
#define EXIT_SKIP_REMAINING 77
36

37
/* Put this test here for a lack of better place */
38
assert_cc(EAGAIN == EWOULDBLOCK);
39

40
static int do_spawn(
1,289✔
41
                const char *path,
42
                char *argv[],
43
                int stdout_fd,
44
                bool set_systemd_exec_pid,
45
                pid_t *ret_pid) {
46

47
        int r;
1,289✔
48

49
        assert(path);
1,289✔
50
        assert(ret_pid);
1,289✔
51

52
        if (null_or_empty_path(path) > 0) {
1,289✔
53
                log_debug("%s is masked, skipping.", path);
×
54
                return 0;
×
55
        }
56

57
        pid_t pid;
1,289✔
58
        r = safe_fork_full(
5,156✔
59
                        "(exec-inner)",
60
                        (const int[]) { STDIN_FILENO, stdout_fd < 0 ? STDOUT_FILENO : stdout_fd, STDERR_FILENO },
1,443✔
61
                        /* except_fds= */ NULL, /* n_except_fds= */ 0,
62
                        FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO|FORK_CLOSE_ALL_FDS,
63
                        &pid);
64
        if (r < 0)
2,578✔
65
                return r;
66
        if (r == 0) {
2,578✔
67
                char *_argv[2];
1,289✔
68

69
                if (set_systemd_exec_pid) {
1,289✔
70
                        r = setenv_systemd_exec_pid(false);
1,257✔
71
                        if (r < 0)
1,257✔
72
                                log_warning_errno(r, "Failed to set $SYSTEMD_EXEC_PID, ignoring: %m");
×
73
                }
74

75
                if (!argv) {
1,289✔
76
                        _argv[0] = (char*) path;
1,140✔
77
                        _argv[1] = NULL;
1,140✔
78
                        argv = _argv;
1,140✔
79
                } else
80
                        argv[0] = (char*) path;
149✔
81

82
                execv(path, argv);
1,289✔
83
                log_error_errno(errno, "Failed to execute %s: %m", path);
1,289✔
84
                _exit(EXIT_FAILURE);
×
85
        }
86

87
        *ret_pid = pid;
1,289✔
88
        return 1;
1,289✔
89
}
90

91
static int do_execute(
1,275✔
92
                char * const *paths,
93
                const char *root,
94
                usec_t timeout,
95
                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
96
                void * const callback_args[_STDOUT_CONSUME_MAX],
97
                int output_fd,
98
                char *argv[],
99
                char *envp[],
100
                ExecDirFlags flags) {
101

102
        _cleanup_hashmap_free_ Hashmap *pids = NULL;
1,275✔
103
        bool parallel_execution;
1,275✔
104
        int r;
1,275✔
105

106
        /* We fork this all off from a child process so that we can somewhat cleanly make use of SIGALRM
107
         * to set a time limit.
108
         *
109
         * We attempt to perform parallel execution if configured by the user, however if `callbacks` is nonnull,
110
         * execution must be serial.
111
         */
112

113
        assert(!strv_isempty(paths));
1,275✔
114

115
        parallel_execution = FLAGS_SET(flags, EXEC_DIR_PARALLEL) && !callbacks;
1,275✔
116

117
        /* Abort execution of this process after the timeout. We simply rely on SIGALRM as
118
         * default action terminating the process, and turn on alarm(). */
119

120
        if (timeout != USEC_INFINITY)
1,275✔
121
                alarm(DIV_ROUND_UP(timeout, USEC_PER_SEC));
1,275✔
122

123
        STRV_FOREACH(e, envp)
7,547✔
124
                if (putenv(*e) != 0)
6,272✔
UNCOV
125
                        return log_error_errno(errno, "Failed to set environment variable: %m");
×
126

127
        STRV_FOREACH(path, paths) {
2,563✔
128
                _cleanup_free_ char *t = NULL;
1,289✔
129
                _cleanup_close_ int fd = -EBADF;
1,289✔
130
                pid_t pid;
1,289✔
131

132
                t = path_join(root, *path);
1,289✔
133
                if (!t)
1,289✔
UNCOV
134
                        return log_oom();
×
135

136
                if (callbacks) {
1,289✔
137
                        _cleanup_free_ char *bn = NULL;
1,135✔
138

139
                        r = path_extract_filename(*path, &bn);
1,135✔
140
                        if (r < 0)
1,135✔
UNCOV
141
                                return log_error_errno(r, "Failed to extract filename from path '%s': %m", *path);
×
142

143
                        fd = open_serialization_fd(bn);
1,135✔
144
                        if (fd < 0)
1,135✔
UNCOV
145
                                return log_error_errno(fd, "Failed to open serialization file: %m");
×
146
                }
147

148
                if (DEBUG_LOGGING) {
1,289✔
149
                        _cleanup_free_ char *args = NULL;
979✔
150
                        if (argv)
979✔
151
                                args = quote_command_line(strv_skip(argv, 1), SHELL_ESCAPE_EMPTY);
149✔
152

153
                        log_debug("About to execute %s%s%s", t, argv ? " " : "", argv ? strnull(args) : "");
1,128✔
154
                }
155

156
                if (FLAGS_SET(flags, EXEC_DIR_WARN_WORLD_WRITABLE)) {
1,289✔
157
                        struct stat st;
138✔
158

159
                        r = stat(t, &st);
138✔
160
                        if (r < 0)
138✔
UNCOV
161
                                log_warning_errno(errno, "Failed to stat '%s', ignoring: %m", t);
×
162
                        else if (S_ISREG(st.st_mode) && (st.st_mode & 0002))
138✔
163
                                log_warning("'%s' is marked world-writable, which is a security risk as it "
138✔
164
                                            "is executed with privileges. Please remove world writability "
165
                                            "permission bits. Proceeding anyway.", t);
166
                }
167

168
                r = do_spawn(t, argv, fd, FLAGS_SET(flags, EXEC_DIR_SET_SYSTEMD_EXEC_PID), &pid);
1,289✔
169
                if (r <= 0)
1,289✔
UNCOV
170
                        continue;
×
171

172
                if (parallel_execution) {
1,289✔
173
                        r = hashmap_ensure_put(&pids, &trivial_hash_ops_value_free, PID_TO_PTR(pid), t);
152✔
174
                        if (r < 0)
152✔
UNCOV
175
                                return log_oom();
×
176
                        t = NULL;
177
                } else {
178
                        bool skip_remaining = false;
1,137✔
179

180
                        r = wait_for_terminate_and_check(t, pid, WAIT_LOG_ABNORMAL);
1,137✔
181
                        if (r < 0)
1,137✔
182
                                return r;
183
                        if (r > 0) {
1,137✔
184
                                if (FLAGS_SET(flags, EXEC_DIR_SKIP_REMAINING) && r == EXIT_SKIP_REMAINING) {
1✔
UNCOV
185
                                        log_info("%s succeeded with exit status %i, not executing remaining executables.", *path, r);
×
186
                                        skip_remaining = true;
187
                                } else if (FLAGS_SET(flags, EXEC_DIR_IGNORE_ERRORS))
1✔
UNCOV
188
                                        log_warning("%s failed with exit status %i, ignoring.", *path, r);
×
189
                                else {
190
                                        log_error("%s failed with exit status %i.", *path, r);
1✔
191
                                        return r;
1✔
192
                                }
193
                        }
194

195
                        if (callbacks) {
1,136✔
196
                                r = finish_serialization_fd(fd);
1,135✔
197
                                if (r < 0)
1,135✔
UNCOV
198
                                        return log_error_errno(r, "Failed to finish serialization fd: %m");
×
199

200
                                r = callbacks[STDOUT_GENERATE](TAKE_FD(fd), callback_args[STDOUT_GENERATE]);
1,135✔
201
                                if (r < 0)
1,135✔
UNCOV
202
                                        return log_error_errno(r, "Failed to process output from %s: %m", *path);
×
203
                        }
204

205
                        if (skip_remaining)
1,136✔
206
                                break;
207
                }
208
        }
209

210
        if (callbacks) {
1,274✔
211
                r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]);
1,124✔
212
                if (r < 0)
1,124✔
UNCOV
213
                        return log_error_errno(r, "Callback two failed: %m");
×
214
        }
215

216
        while (!hashmap_isempty(pids)) {
1,426✔
217
                _cleanup_free_ char *t = NULL;
152✔
218
                pid_t pid;
152✔
219
                void *p;
152✔
220

221
                t = ASSERT_PTR(hashmap_steal_first_key_and_value(pids, &p));
152✔
222
                pid = PTR_TO_PID(p);
152✔
223
                assert(pid > 0);
152✔
224

225
                r = wait_for_terminate_and_check(t, pid, WAIT_LOG);
152✔
226
                if (r < 0)
152✔
227
                        return r;
228
                if (!FLAGS_SET(flags, EXEC_DIR_IGNORE_ERRORS) && r > 0)
152✔
229
                        return r;
230
        }
231

232
        return 0;
233
}
234

235
int execute_strv(
754✔
236
                const char *name,
237
                char * const *paths,
238
                const char *root,
239
                usec_t timeout,
240
                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
241
                void * const callback_args[_STDOUT_CONSUME_MAX],
242
                char *argv[],
243
                char *envp[],
244
                ExecDirFlags flags) {
245

246
        _cleanup_close_ int fd = -EBADF;
754✔
247
        pid_t executor_pid;
754✔
248
        int r;
754✔
249

250
        assert(!FLAGS_SET(flags, EXEC_DIR_PARALLEL | EXEC_DIR_SKIP_REMAINING));
754✔
251

252
        if (strv_isempty(paths))
1,508✔
253
                return 0;
254

255
        if (callbacks) {
754✔
256
                assert(name);
614✔
257
                assert(callbacks[STDOUT_GENERATE]);
614✔
258
                assert(callbacks[STDOUT_COLLECT]);
614✔
259
                assert(callbacks[STDOUT_CONSUME]);
614✔
260
                assert(callback_args);
614✔
261

262
                fd = open_serialization_fd(name);
614✔
263
                if (fd < 0)
614✔
UNCOV
264
                        return log_error_errno(fd, "Failed to open serialization file: %m");
×
265
        }
266

267
        /* Executes all binaries in the directories serially or in parallel and waits for
268
         * them to finish. Optionally a timeout is applied. If a file with the same name
269
         * exists in more than one directory, the earliest one wins. */
270

271
        r = safe_fork("(sd-exec-strv)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &executor_pid);
754✔
272
        if (r < 0)
2,029✔
273
                return r;
274
        if (r == 0) {
2,029✔
275
                r = do_execute(paths, root, timeout, callbacks, callback_args, fd, argv, envp, flags);
1,275✔
276
                _exit(r < 0 ? EXIT_FAILURE : r);
1,275✔
277
        }
278

279
        r = wait_for_terminate_and_check("(sd-exec-strv)", executor_pid, 0);
754✔
280
        if (r < 0)
754✔
281
                return r;
282
        if (!FLAGS_SET(flags, EXEC_DIR_IGNORE_ERRORS) && r > 0)
754✔
283
                return r;
284

285
        if (!callbacks)
753✔
286
                return 0;
287

288
        r = finish_serialization_fd(fd);
614✔
289
        if (r < 0)
614✔
UNCOV
290
                return log_error_errno(r, "Failed to finish serialization fd: %m");
×
291

292
        r = callbacks[STDOUT_CONSUME](TAKE_FD(fd), callback_args[STDOUT_CONSUME]);
614✔
293
        if (r < 0)
614✔
UNCOV
294
                return log_error_errno(r, "Failed to parse returned data: %m");
×
295

296
        return 0;
297
}
298

299
int execute_directories(
754✔
300
                const char * const *directories,
301
                usec_t timeout,
302
                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
303
                void * const callback_args[_STDOUT_CONSUME_MAX],
304
                char *argv[],
305
                char *envp[],
306
                ExecDirFlags flags) {
307

UNCOV
308
        _cleanup_strv_free_ char **paths = NULL;
×
309
        _cleanup_free_ char *name = NULL;
754✔
310
        int r;
754✔
311

312
        assert(!strv_isempty((char* const*) directories));
754✔
313

314
        r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE|CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, directories);
754✔
315
        if (r < 0)
754✔
UNCOV
316
                return log_error_errno(r, "Failed to enumerate executables: %m");
×
317

318
        if (strv_isempty(paths)) {
754✔
UNCOV
319
                log_debug("No executables found.");
×
UNCOV
320
                return 0;
×
321
        }
322

323
        if (callbacks) {
754✔
324
                r = path_extract_filename(directories[0], &name);
614✔
325
                if (r < 0)
614✔
326
                        return log_error_errno(r, "Failed to extract file name from '%s': %m", directories[0]);
×
327
        }
328

329
        return execute_strv(name, paths, /* root = */ NULL, timeout, callbacks, callback_args, argv, envp, flags);
754✔
330
}
331

332
static int gather_environment_generate(int fd, void *arg) {
1,125✔
333
        char ***env = ASSERT_PTR(arg);
1,125✔
334
        _cleanup_fclose_ FILE *f = NULL;
1,125✔
335
        _cleanup_strv_free_ char **new = NULL;
1,125✔
336
        int r;
1,125✔
337

338
        /* Read a series of VAR=value assignments from fd, use them to update the list of variables in env.
339
         * Also update the exported environment.
340
         *
341
         * fd is always consumed, even on error.
342
         */
343

344
        assert(fd >= 0);
1,125✔
345

346
        f = fdopen(fd, "r");
1,125✔
347
        if (!f) {
1,125✔
UNCOV
348
                safe_close(fd);
×
UNCOV
349
                return -errno;
×
350
        }
351

352
        r = load_env_file_pairs(f, NULL, &new);
1,125✔
353
        if (r < 0)
1,125✔
354
                return r;
355

356
        STRV_FOREACH_PAIR(x, y, new) {
2,130✔
357
                if (!env_name_is_valid(*x)) {
1,005✔
358
                        log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x);
4✔
359
                        continue;
4✔
360
                }
361

362
                r = strv_env_assign(env, *x, *y);
1,001✔
363
                if (r < 0)
1,001✔
364
                        return r;
365

366
                if (setenv(*x, *y, /* overwrite = */ true) < 0)
1,001✔
UNCOV
367
                        return -errno;
×
368
        }
369

370
        return 0;
371
}
372

373
static int gather_environment_collect(int fd, void *arg) {
1,121✔
374
        char ***env = ASSERT_PTR(arg);
1,121✔
375
        _cleanup_fclose_ FILE *f = NULL;
1,121✔
376
        int r;
1,121✔
377

378
        /* Write out a series of env=cescape(VAR=value) assignments to fd. */
379

380
        assert(fd >= 0);
1,121✔
381

382
        f = fdopen(fd, "w");
1,121✔
383
        if (!f) {
1,121✔
UNCOV
384
                safe_close(fd);
×
UNCOV
385
                return -errno;
×
386
        }
387

388
        r = serialize_strv(f, "env", *env);
1,121✔
389
        if (r < 0)
1,121✔
390
                return r;
391

392
        r = fflush_and_check(f);
1,121✔
393
        if (r < 0)
1,121✔
UNCOV
394
                return r;
×
395

396
        return 0;
397
}
398

399
static int gather_environment_consume(int fd, void *arg) {
611✔
400
        char ***env = ASSERT_PTR(arg);
611✔
401
        _cleanup_fclose_ FILE *f = NULL;
611✔
402
        int r, ret = 0;
611✔
403

404
        /* Read a series of env=cescape(VAR=value) assignments from fd into env. */
405

406
        assert(fd >= 0);
611✔
407

408
        f = fdopen(fd, "r");
611✔
409
        if (!f) {
611✔
UNCOV
410
                safe_close(fd);
×
UNCOV
411
                return -errno;
×
412
        }
413

414
        for (;;) {
1,098✔
415
                _cleanup_free_ char *line = NULL;
1,098✔
416
                const char *v;
1,098✔
417

418
                r = read_line(f, LONG_LINE_MAX, &line);
1,098✔
419
                if (r < 0)
1,098✔
420
                        return r;
421
                if (r == 0)
1,098✔
422
                        return ret;
423

424
                v = startswith(line, "env=");
487✔
425
                if (!v) {
487✔
UNCOV
426
                        RET_GATHER(ret, log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
×
427
                                                        "Serialization line unexpectedly didn't start with \"env=\", ignoring: %s",
428
                                                        line));
UNCOV
429
                        continue;
×
430
                }
431

432
                r = deserialize_environment(v, env);
487✔
433
                if (r < 0)
487✔
UNCOV
434
                        RET_GATHER(ret, log_debug_errno(r, "Failed to deserialize line \"%s\": %m", line));
×
435
        }
436
}
437

438
const gather_stdout_callback_t gather_environment[_STDOUT_CONSUME_MAX] = {
439
        gather_environment_generate,
440
        gather_environment_collect,
441
        gather_environment_consume,
442
};
443

444
int exec_command_flags_from_strv(char * const *ex_opts, ExecCommandFlags *ret) {
313✔
445
        ExecCommandFlags flags = 0;
313✔
446

447
        assert(ret);
313✔
448

449
        STRV_FOREACH(opt, ex_opts) {
351✔
450
                ExecCommandFlags fl = exec_command_flags_from_string(*opt);
39✔
451
                if (fl < 0)
39✔
452
                        return fl;
453

454
                flags |= fl;
38✔
455
        }
456

457
        *ret = flags;
312✔
458

459
        return 0;
312✔
460
}
461

462
int exec_command_flags_to_strv(ExecCommandFlags flags, char ***ret) {
1,140✔
463
        _cleanup_strv_free_ char **opts = NULL;
1,140✔
464
        int r;
1,140✔
465

466
        assert(flags >= 0);
1,140✔
467
        assert(ret);
1,140✔
468

469
        BIT_FOREACH(i, flags) {
1,312✔
470
                const char *s = exec_command_flags_to_string(1 << i);
173✔
471
                if (!s)
173✔
472
                        return -EINVAL;
473

474
                r = strv_extend(&opts, s);
172✔
475
                if (r < 0)
172✔
476
                        return r;
477
        }
478

479
        *ret = TAKE_PTR(opts);
1,139✔
480

481
        return 0;
1,139✔
482
}
483

484
static const char* const exec_command_strings[] = {
485
        "ignore-failure", /* EXEC_COMMAND_IGNORE_FAILURE */
486
        "privileged",     /* EXEC_COMMAND_FULLY_PRIVILEGED */
487
        "no-setuid",      /* EXEC_COMMAND_NO_SETUID */
488
        "no-env-expand",  /* EXEC_COMMAND_NO_ENV_EXPAND */
489
};
490

491
assert_cc((1 << ELEMENTSOF(exec_command_strings)) - 1 == _EXEC_COMMAND_FLAGS_ALL);
492

493
const char* exec_command_flags_to_string(ExecCommandFlags i) {
173✔
494
        for (size_t idx = 0; idx < ELEMENTSOF(exec_command_strings); idx++)
528✔
495
                if (i == (1 << idx))
527✔
496
                        return exec_command_strings[idx];
172✔
497

498
        return NULL;
499
}
500

501
ExecCommandFlags exec_command_flags_from_string(const char *s) {
39✔
502
        ssize_t idx;
39✔
503

504
        if (streq(s, "ambient")) /* Compatibility with ambient hack, removed in v258, map to no bits set */
39✔
505
                return 0;
506

507
        idx = string_table_lookup(exec_command_strings, ELEMENTSOF(exec_command_strings), s);
39✔
508
        if (idx < 0)
39✔
509
                return _EXEC_COMMAND_FLAGS_INVALID;
510

511
        return 1 << idx;
38✔
512
}
513

514
int fexecve_or_execve(int executable_fd, const char *executable, char *const argv[], char *const envp[]) {
10,932✔
515
        /* Refuse invalid fds, regardless if fexecve() use is enabled or not */
516
        if (executable_fd < 0)
10,932✔
517
                return -EBADF;
518

519
        /* Block any attempts on exploiting Linux' liberal argv[] handling, i.e. CVE-2021-4034 and suchlike */
520
        if (isempty(executable) || strv_isempty(argv))
21,864✔
521
                return -EINVAL;
522

523
#if ENABLE_FEXECVE
524

525
        execveat(executable_fd, "", argv, envp, AT_EMPTY_PATH);
526

527
        if (IN_SET(errno, ENOSYS, ENOENT) || ERRNO_IS_PRIVILEGE(errno))
528
                /* Old kernel or a script or an overzealous seccomp filter? Let's fall back to execve().
529
                 *
530
                 * fexecve(3): "If fd refers to a script (i.e., it is an executable text file that names a
531
                 * script interpreter with a first line that begins with the characters #!) and the
532
                 * close-on-exec flag has been set for fd, then fexecve() fails with the error ENOENT. This
533
                 * error occurs because, by the time the script interpreter is executed, fd has already been
534
                 * closed because of the close-on-exec flag. Thus, the close-on-exec flag can't be set on fd
535
                 * if it refers to a script."
536
                 *
537
                 * Unfortunately, if we unset close-on-exec, the script will be executed just fine, but (at
538
                 * least in case of bash) the script name, $0, will be shown as /dev/fd/nnn, which breaks
539
                 * scripts which make use of $0. Thus, let's fall back to execve() in this case.
540
                 */
541
#endif
542
                execve(executable, argv, envp);
10,932✔
543
        return -errno;
10,932✔
544
}
545

546
int shall_fork_agent(void) {
2,185✔
547
        int r;
2,185✔
548

549
        /* Check if we have a controlling terminal. If not (ENXIO here), we aren't actually invoked
550
         * interactively on a terminal, hence fail. */
551
        r = get_ctty_devnr(0, NULL);
2,185✔
552
        if (r == -ENXIO)
2,185✔
553
                return false;
UNCOV
554
        if (r < 0)
×
555
                return r;
556

UNCOV
557
        if (!is_main_thread())
×
UNCOV
558
                return -EPERM;
×
559

560
        return true;
561
}
562

563
int _fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret_pid, const char *path, ...) {
×
564
        int r;
×
565

UNCOV
566
        assert(path);
×
567

568
        /* Spawns a temporary TTY agent, making sure it goes away when we go away */
569

570
        r = safe_fork_full(name,
×
571
                           NULL,
572
                           (int*) except, /* safe_fork_full only changes except if you pass in FORK_PACK_FDS, which we don't */
573
                           n_except,
574
                           FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE,
575
                           ret_pid);
576
        if (r < 0)
×
577
                return r;
UNCOV
578
        if (r > 0)
×
579
                return 0;
580

581
        /* In the child: */
582

UNCOV
583
        bool stdin_is_tty = isatty_safe(STDIN_FILENO),
×
584
                stdout_is_tty = isatty_safe(STDOUT_FILENO),
×
UNCOV
585
                stderr_is_tty = isatty_safe(STDERR_FILENO);
×
586

UNCOV
587
        if (!stdin_is_tty || !stdout_is_tty || !stderr_is_tty) {
×
UNCOV
588
                int fd;
×
589

590
                /* Detach from stdin/stdout/stderr and reopen /dev/tty for them. This is important to ensure
591
                 * that when systemctl is started via popen() or a similar call that expects to read EOF we
592
                 * actually do generate EOF and not delay this indefinitely by keeping an unused copy of
593
                 * stdin around. */
594
                fd = open_terminal("/dev/tty", stdin_is_tty ? O_WRONLY : (stdout_is_tty && stderr_is_tty) ? O_RDONLY : O_RDWR);
×
UNCOV
595
                if (fd < 0) {
×
UNCOV
596
                        log_error_errno(fd, "Failed to open /dev/tty: %m");
×
UNCOV
597
                        _exit(EXIT_FAILURE);
×
598
                }
599

600
                if (!stdin_is_tty && dup2(fd, STDIN_FILENO) < 0) {
×
601
                        log_error_errno(errno, "Failed to dup2 /dev/tty to STDIN: %m");
×
602
                        _exit(EXIT_FAILURE);
×
603
                }
604

UNCOV
605
                if (!stdout_is_tty && dup2(fd, STDOUT_FILENO) < 0) {
×
606
                        log_error_errno(errno, "Failed to dup2 /dev/tty to STDOUT: %m");
×
607
                        _exit(EXIT_FAILURE);
×
608
                }
609

UNCOV
610
                if (!stderr_is_tty && dup2(fd, STDERR_FILENO) < 0) {
×
611
                        log_error_errno(errno, "Failed to dup2 /dev/tty to STDERR: %m");
×
612
                        _exit(EXIT_FAILURE);
×
613
                }
614

UNCOV
615
                fd = safe_close_above_stdio(fd);
×
616
        }
617

618
        /* Count arguments */
UNCOV
619
        char **l = strv_from_stdarg_alloca(path);
×
UNCOV
620
        execv(path, l);
×
621
        log_error_errno(errno, "Failed to execute %s: %m", path);
×
UNCOV
622
        _exit(EXIT_FAILURE);
×
623
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc