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

systemd / systemd / 20417900562

21 Dec 2025 07:31PM UTC coverage: 72.5% (-0.2%) from 72.701%
20417900562

push

github

DaanDeMeyer
mkosi: Use initrd as exitrd

Let's speed up image builds by avoiding building
an exitrd and instead reusing the initrd image for
the same purpose.

309142 of 426400 relevant lines covered (72.5%)

1141502.27 hits per line

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

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

3
#include <stdio.h>
4
#include <stdlib.h>
5
#include <unistd.h>
6

7
#include "alloc-util.h"
8
#include "bitfield.h"
9
#include "conf-files.h"
10
#include "env-file.h"
11
#include "env-util.h"
12
#include "errno-util.h"
13
#include "escape.h"
14
#include "exec-util.h"
15
#include "fd-util.h"
16
#include "fileio.h"
17
#include "hashmap.h"
18
#include "log.h"
19
#include "path-util.h"
20
#include "pidref.h"
21
#include "process-util.h"
22
#include "serialize.h"
23
#include "stat-util.h"
24
#include "string-table.h"
25
#include "string-util.h"
26
#include "strv.h"
27
#include "terminal-util.h"
28
#include "time-util.h"
29

30
#define EXIT_SKIP_REMAINING 77
31

32
DEFINE_PRIVATE_HASH_OPS_FULL(pidref_hash_ops_free_free,
×
33
                             PidRef, pidref_hash_func, pidref_compare_func,
34
                             pidref_free, char*, free);
35

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

39
static int do_spawn(
1,551✔
40
                const char *path,
41
                char *argv[],
42
                int stdout_fd,
43
                bool set_systemd_exec_pid,
44
                PidRef *ret) {
45

46
        int r;
1,551✔
47

48
        assert(path);
1,551✔
49
        assert(ret);
1,551✔
50

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

56
        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
1,551✔
57
        r = pidref_safe_fork_full(
6,204✔
58
                        "(exec-inner)",
59
                        (const int[]) { STDIN_FILENO, stdout_fd < 0 ? STDOUT_FILENO : stdout_fd, STDERR_FILENO },
1,759✔
60
                        /* except_fds= */ NULL, /* n_except_fds= */ 0,
61
                        FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO|FORK_CLOSE_ALL_FDS,
62
                        &pidref);
63
        if (r < 0)
3,102✔
64
                return r;
65
        if (r == 0) {
3,102✔
66
                char *_argv[2];
1,551✔
67

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

74
                if (!argv) {
1,551✔
75
                        _argv[0] = (char*) path;
1,348✔
76
                        _argv[1] = NULL;
1,348✔
77
                        argv = _argv;
1,348✔
78
                } else
79
                        argv[0] = (char*) path;
203✔
80

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

86
        *ret = TAKE_PIDREF(pidref);
1,551✔
87
        return 1;
1,551✔
88
}
89

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

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

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

112
        assert(!strv_isempty(paths));
1,537✔
113

114
        parallel_execution = FLAGS_SET(flags, EXEC_DIR_PARALLEL) && !callbacks;
1,537✔
115

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

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

122
        STRV_FOREACH(e, envp)
9,363✔
123
                if (putenv(*e) != 0)
7,826✔
124
                        return log_error_errno(errno, "Failed to set environment variable: %m");
×
125

126
        STRV_FOREACH(path, paths) {
3,087✔
127
                _cleanup_free_ char *t = NULL;
1,551✔
128
                _cleanup_close_ int fd = -EBADF;
1,551✔
129

130
                t = path_join(root, *path);
1,551✔
131
                if (!t)
1,551✔
132
                        return log_oom();
×
133

134
                if (callbacks) {
1,551✔
135
                        _cleanup_free_ char *bn = NULL;
1,343✔
136

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

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

146
                if (DEBUG_LOGGING) {
1,551✔
147
                        _cleanup_free_ char *s = NULL;
1,100✔
148

149
                        char **args = strv_skip(argv, 1);
1,100✔
150
                        if (args)
1,100✔
151
                                s = quote_command_line(args, SHELL_ESCAPE_EMPTY);
×
152

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

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

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

168
                _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
1,551✔
169
                r = do_spawn(t, argv, fd, FLAGS_SET(flags, EXEC_DIR_SET_SYSTEMD_EXEC_PID), &pidref);
1,551✔
170
                if (r <= 0)
1,551✔
171
                        continue;
×
172

173
                if (parallel_execution) {
1,551✔
174
                        _cleanup_(pidref_freep) PidRef *dup = NULL;
×
175
                        r = pidref_dup(&pidref, &dup);
206✔
176
                        if (r < 0)
206✔
177
                                return log_error_errno(r, "Failed to duplicate pid reference: %m");
×
178

179
                        r = hashmap_ensure_put(&pids, &pidref_hash_ops_free_free, dup, t);
206✔
180
                        if (r < 0)
206✔
181
                                return log_oom();
×
182

183
                        TAKE_PTR(dup);
206✔
184
                        TAKE_PTR(t);
206✔
185
                } else {
186
                        bool skip_remaining = false;
1,345✔
187

188
                        r = pidref_wait_for_terminate_and_check(t, &pidref, WAIT_LOG_ABNORMAL);
1,345✔
189
                        if (r < 0)
1,345✔
190
                                return r;
191
                        if (r > 0) {
1,345✔
192
                                if (FLAGS_SET(flags, EXEC_DIR_SKIP_REMAINING) && r == EXIT_SKIP_REMAINING) {
1✔
193
                                        log_info("%s succeeded with exit status %i, not executing remaining executables.", *path, r);
×
194
                                        skip_remaining = true;
195
                                } else if (FLAGS_SET(flags, EXEC_DIR_IGNORE_ERRORS))
1✔
196
                                        log_warning("%s failed with exit status %i, ignoring.", *path, r);
×
197
                                else {
198
                                        log_error("%s failed with exit status %i.", *path, r);
1✔
199
                                        return r;
1✔
200
                                }
201
                        }
202

203
                        if (callbacks) {
1,344✔
204
                                r = finish_serialization_fd(fd);
1,343✔
205
                                if (r < 0)
1,343✔
206
                                        return log_error_errno(r, "Failed to finish serialization fd: %m");
×
207

208
                                r = callbacks[STDOUT_GENERATE](TAKE_FD(fd), callback_args[STDOUT_GENERATE]);
1,343✔
209
                                if (r < 0)
1,343✔
210
                                        return log_error_errno(r, "Failed to process output from %s: %m", *path);
×
211
                        }
212

213
                        if (skip_remaining)
1,344✔
214
                                break;
215
                }
216
        }
217

218
        if (callbacks) {
1,536✔
219
                r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]);
1,332✔
220
                if (r < 0)
1,332✔
221
                        return log_error_errno(r, "Callback two failed: %m");
×
222
        }
223

224
        while (!hashmap_isempty(pids)) {
1,742✔
225
                _cleanup_(pidref_freep) PidRef *pidref = NULL;
×
226
                _cleanup_free_ char *t = NULL;
206✔
227

228
                t = ASSERT_PTR(hashmap_steal_first_key_and_value(pids, (void**) &pidref));
206✔
229

230
                r = pidref_wait_for_terminate_and_check(t, pidref, WAIT_LOG);
206✔
231
                if (r < 0)
206✔
232
                        return r;
233
                if (!FLAGS_SET(flags, EXEC_DIR_IGNORE_ERRORS) && r > 0)
206✔
234
                        return r;
235
        }
236

237
        return 0;
238
}
239

240
int execute_strv(
940✔
241
                const char *name,
242
                char * const *paths,
243
                const char *root,
244
                usec_t timeout,
245
                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
246
                void * const callback_args[_STDOUT_CONSUME_MAX],
247
                char *argv[],
248
                char *envp[],
249
                ExecDirFlags flags) {
250

251
        _cleanup_close_ int fd = -EBADF;
940✔
252
        int r;
940✔
253

254
        assert(name);
940✔
255
        assert(!FLAGS_SET(flags, EXEC_DIR_PARALLEL | EXEC_DIR_SKIP_REMAINING));
940✔
256

257
        if (strv_isempty(paths))
1,880✔
258
                return 0;
259

260
        if (callbacks) {
940✔
261
                assert(callbacks[STDOUT_GENERATE]);
746✔
262
                assert(callbacks[STDOUT_COLLECT]);
746✔
263
                assert(callbacks[STDOUT_CONSUME]);
746✔
264
                assert(callback_args);
746✔
265

266
                fd = open_serialization_fd(name);
746✔
267
                if (fd < 0)
746✔
268
                        return log_error_errno(fd, "Failed to open serialization file for %s: %m", name);
×
269
        }
270

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

275
        const char *process_name = strjoina("(", name, ")");
6,580✔
276

277
        _cleanup_(pidref_done) PidRef executor_pidref = PIDREF_NULL;
940✔
278
        r = pidref_safe_fork(process_name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &executor_pidref);
940✔
279
        if (r < 0)
2,477✔
280
                return r;
281
        if (r == 0) {
2,477✔
282
                r = do_execute(paths, root, timeout, callbacks, callback_args, fd, argv, envp, flags);
1,537✔
283
                _exit(r < 0 ? EXIT_FAILURE : r);
1,537✔
284
        }
285

286
        r = pidref_wait_for_terminate_and_check(process_name, &executor_pidref, 0);
940✔
287
        if (r < 0)
940✔
288
                return r;
289
        if (!FLAGS_SET(flags, EXEC_DIR_IGNORE_ERRORS) && r > 0)
940✔
290
                return r;
291

292
        if (!callbacks)
939✔
293
                return 0;
294

295
        r = finish_serialization_fd(fd);
746✔
296
        if (r < 0)
746✔
297
                return log_error_errno(r, "Failed to finish serialization fd for %s: %m", name);
×
298

299
        r = callbacks[STDOUT_CONSUME](TAKE_FD(fd), callback_args[STDOUT_CONSUME]);
746✔
300
        if (r < 0)
746✔
301
                return log_error_errno(r, "Failed to parse returned data for %s: %m", name);
×
302

303
        return 0;
304
}
305

306
int execute_directories(
940✔
307
                const char *name,
308
                const char * const *directories,
309
                usec_t timeout,
310
                gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
311
                void * const callback_args[_STDOUT_CONSUME_MAX],
312
                char *argv[],
313
                char *envp[],
314
                ExecDirFlags flags) {
315

316
        _cleanup_strv_free_ char **paths = NULL;
940✔
317
        int r;
940✔
318

319
        assert(name);
940✔
320
        assert(!strv_isempty((char* const*) directories));
940✔
321

322
        r = conf_files_list_strv(
940✔
323
                        &paths,
324
                        /* suffix= */ NULL,
325
                        /* root= */ NULL,
326
                        CONF_FILES_EXECUTABLE|CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
327
                        directories);
328
        if (r < 0)
940✔
329
                return log_error_errno(r, "%s: failed to enumerate executables: %m", name);
×
330

331
        if (strv_isempty(paths)) {
940✔
332
                log_debug("%s: no executables found.", name);
×
333
                return 0;
×
334
        }
335

336
        return execute_strv(name, paths, /* root= */ NULL, timeout, callbacks, callback_args, argv, envp, flags);
940✔
337
}
338

339
static int gather_environment_generate(int fd, void *arg) {
1,333✔
340
        char ***env = ASSERT_PTR(arg);
1,333✔
341
        _cleanup_fclose_ FILE *f = NULL;
1,333✔
342
        _cleanup_strv_free_ char **new = NULL;
1,333✔
343
        int r;
1,333✔
344

345
        /* Read a series of VAR=value assignments from fd, use them to update the list of variables in env.
346
         * Also update the exported environment.
347
         *
348
         * fd is always consumed, even on error.
349
         */
350

351
        assert(fd >= 0);
1,333✔
352

353
        f = fdopen(fd, "r");
1,333✔
354
        if (!f) {
1,333✔
355
                safe_close(fd);
×
356
                return -errno;
×
357
        }
358

359
        r = load_env_file_pairs(f, NULL, &new);
1,333✔
360
        if (r < 0)
1,333✔
361
                return r;
362

363
        STRV_FOREACH_PAIR(x, y, new) {
2,492✔
364
                if (!env_name_is_valid(*x)) {
1,159✔
365
                        log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x);
4✔
366
                        continue;
4✔
367
                }
368

369
                r = strv_env_assign(env, *x, *y);
1,155✔
370
                if (r < 0)
1,155✔
371
                        return r;
372

373
                if (setenv(*x, *y, /* overwrite= */ true) < 0)
1,155✔
374
                        return -errno;
×
375
        }
376

377
        return 0;
378
}
379

380
static int gather_environment_collect(int fd, void *arg) {
1,329✔
381
        char ***env = ASSERT_PTR(arg);
1,329✔
382
        _cleanup_fclose_ FILE *f = NULL;
1,329✔
383
        int r;
1,329✔
384

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

387
        assert(fd >= 0);
1,329✔
388

389
        f = fdopen(fd, "w");
1,329✔
390
        if (!f) {
1,329✔
391
                safe_close(fd);
×
392
                return -errno;
×
393
        }
394

395
        r = serialize_strv(f, "env", *env);
1,329✔
396
        if (r < 0)
1,329✔
397
                return r;
398

399
        r = fflush_and_check(f);
1,329✔
400
        if (r < 0)
1,329✔
401
                return r;
×
402

403
        return 0;
404
}
405

406
static int gather_environment_consume(int fd, void *arg) {
743✔
407
        char ***env = ASSERT_PTR(arg);
743✔
408
        _cleanup_fclose_ FILE *f = NULL;
743✔
409
        int r, ret = 0;
743✔
410

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

413
        assert(fd >= 0);
743✔
414

415
        f = fdopen(fd, "r");
743✔
416
        if (!f) {
743✔
417
                safe_close(fd);
×
418
                return -errno;
×
419
        }
420

421
        for (;;) {
1,308✔
422
                _cleanup_free_ char *line = NULL;
1,308✔
423
                const char *v;
1,308✔
424

425
                r = read_line(f, LONG_LINE_MAX, &line);
1,308✔
426
                if (r < 0)
1,308✔
427
                        return r;
428
                if (r == 0)
1,308✔
429
                        return ret;
430

431
                v = startswith(line, "env=");
565✔
432
                if (!v) {
565✔
433
                        RET_GATHER(ret, log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
×
434
                                                        "Serialization line unexpectedly didn't start with \"env=\", ignoring: %s",
435
                                                        line));
436
                        continue;
×
437
                }
438

439
                r = deserialize_environment(v, env);
565✔
440
                if (r < 0)
565✔
441
                        RET_GATHER(ret, log_debug_errno(r, "Failed to deserialize line \"%s\": %m", line));
×
442
        }
443
}
444

445
const gather_stdout_callback_t gather_environment[_STDOUT_CONSUME_MAX] = {
446
        gather_environment_generate,
447
        gather_environment_collect,
448
        gather_environment_consume,
449
};
450

451
int exec_command_flags_from_strv(char * const *ex_opts, ExecCommandFlags *ret) {
487✔
452
        ExecCommandFlags flags = 0;
487✔
453

454
        assert(ret);
487✔
455

456
        STRV_FOREACH(opt, ex_opts) {
702✔
457
                ExecCommandFlags fl = exec_command_flags_from_string(*opt);
216✔
458
                if (fl < 0)
216✔
459
                        return fl;
460

461
                flags |= fl;
215✔
462
        }
463

464
        *ret = flags;
486✔
465

466
        return 0;
486✔
467
}
468

469
int exec_command_flags_to_strv(ExecCommandFlags flags, char ***ret) {
3,120✔
470
        _cleanup_strv_free_ char **opts = NULL;
3,120✔
471
        int r;
3,120✔
472

473
        assert(flags >= 0);
3,120✔
474
        assert(ret);
3,120✔
475

476
        BIT_FOREACH(i, flags) {
4,501✔
477
                const char *s = exec_command_flags_to_string(1 << i);
1,382✔
478
                if (!s)
1,382✔
479
                        return -EINVAL;
480

481
                r = strv_extend(&opts, s);
1,381✔
482
                if (r < 0)
1,381✔
483
                        return r;
484
        }
485

486
        *ret = TAKE_PTR(opts);
3,119✔
487

488
        return 0;
3,119✔
489
}
490

491
static const char* const exec_command_strings[] = {
492
        "ignore-failure", /* EXEC_COMMAND_IGNORE_FAILURE */
493
        "privileged",     /* EXEC_COMMAND_FULLY_PRIVILEGED */
494
        "no-setuid",      /* EXEC_COMMAND_NO_SETUID */
495
        "no-env-expand",  /* EXEC_COMMAND_NO_ENV_EXPAND */
496
        "via-shell",      /* EXEC_COMMAND_VIA_SHELL */
497
};
498

499
assert_cc((1 << ELEMENTSOF(exec_command_strings)) - 1 == _EXEC_COMMAND_FLAGS_ALL);
500

501
const char* exec_command_flags_to_string(ExecCommandFlags i) {
1,382✔
502
        for (size_t idx = 0; idx < ELEMENTSOF(exec_command_strings); idx++)
5,508✔
503
                if (i == (1 << idx))
5,507✔
504
                        return exec_command_strings[idx];
1,381✔
505

506
        return NULL;
507
}
508

509
ExecCommandFlags exec_command_flags_from_string(const char *s) {
216✔
510
        ssize_t idx;
216✔
511

512
        if (streq(s, "ambient")) /* Compatibility with ambient hack, removed in v258, map to no bits set */
216✔
513
                return 0;
514

515
        idx = string_table_lookup_from_string(exec_command_strings, ELEMENTSOF(exec_command_strings), s);
216✔
516
        if (idx < 0)
216✔
517
                return _EXEC_COMMAND_FLAGS_INVALID;
518

519
        return 1 << idx;
215✔
520
}
521

522
int fexecve_or_execve(int executable_fd, const char *executable, char *const argv[], char *const envp[]) {
23,346✔
523
        /* Refuse invalid fds, regardless if fexecve() use is enabled or not */
524
        if (executable_fd < 0)
23,346✔
525
                return -EBADF;
526

527
        /* Block any attempts on exploiting Linux' liberal argv[] handling, i.e. CVE-2021-4034 and suchlike */
528
        if (isempty(executable) || strv_isempty(argv))
46,692✔
529
                return -EINVAL;
530

531
#if ENABLE_FEXECVE
532

533
        execveat(executable_fd, "", argv, envp, AT_EMPTY_PATH);
534

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

555
int shall_fork_agent(void) {
4,385✔
556
        int r;
4,385✔
557

558
        /* Check if we have a controlling terminal. If not (ENXIO here), we aren't actually invoked
559
         * interactively on a terminal, hence fail. */
560
        r = get_ctty_devnr(0, NULL);
4,385✔
561
        if (r == -ENXIO)
4,385✔
562
                return false;
563
        if (r < 0)
×
564
                return r;
565

566
        if (!is_main_thread())
×
567
                return -EPERM;
×
568

569
        return true;
570
}
571

572
int _fork_agent(const char *name, char * const *argv, const int except[], size_t n_except, PidRef *ret) {
×
573
        int r;
×
574

575
        assert(!strv_isempty(argv));
×
576

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

579
        r = pidref_safe_fork_full(
×
580
                        name,
581
                        /* stdio_fds= */ NULL,
582
                        (int*) except, n_except, /* safe_fork_full only changes except if you pass in FORK_PACK_FDS, which we don't */
583
                        FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE,
584
                        ret);
585
        if (r < 0)
×
586
                return r;
587
        if (r > 0)
×
588
                return 0;
589

590
        /* In the child: */
591

592
        bool stdin_is_tty = isatty_safe(STDIN_FILENO),
×
593
                stdout_is_tty = isatty_safe(STDOUT_FILENO),
×
594
                stderr_is_tty = isatty_safe(STDERR_FILENO);
×
595

596
        if (!stdin_is_tty || !stdout_is_tty || !stderr_is_tty) {
×
597
                int fd;
×
598

599
                /* Detach from stdin/stdout/stderr and reopen /dev/tty for them. This is important to ensure
600
                 * that when systemctl is started via popen() or a similar call that expects to read EOF we
601
                 * actually do generate EOF and not delay this indefinitely by keeping an unused copy of
602
                 * stdin around. */
603
                fd = open_terminal("/dev/tty", stdin_is_tty ? O_WRONLY : (stdout_is_tty && stderr_is_tty) ? O_RDONLY : O_RDWR);
×
604
                if (fd < 0) {
×
605
                        log_error_errno(fd, "Failed to open %s: %m", "/dev/tty");
×
606
                        _exit(EXIT_FAILURE);
×
607
                }
608

609
                if (!stdin_is_tty && dup2(fd, STDIN_FILENO) < 0) {
×
610
                        log_error_errno(errno, "Failed to dup2 /dev/tty to STDIN: %m");
×
611
                        _exit(EXIT_FAILURE);
×
612
                }
613

614
                if (!stdout_is_tty && dup2(fd, STDOUT_FILENO) < 0) {
×
615
                        log_error_errno(errno, "Failed to dup2 /dev/tty to STDOUT: %m");
×
616
                        _exit(EXIT_FAILURE);
×
617
                }
618

619
                if (!stderr_is_tty && dup2(fd, STDERR_FILENO) < 0) {
×
620
                        log_error_errno(errno, "Failed to dup2 /dev/tty to STDERR: %m");
×
621
                        _exit(EXIT_FAILURE);
×
622
                }
623

624
                fd = safe_close_above_stdio(fd);
×
625
        }
626

627
        /* Count arguments */
628
        execv(argv[0], argv);
×
629

630
        /* Let's treat missing agent binary as a graceful issue (in order to support splitting out the Polkit
631
         * or password agents into separate, optional distro packages), and not complain loudly. */
632
        log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
×
633
                       "Failed to execute %s: %m", argv[0]);
634
        _exit(EXIT_FAILURE);
×
635
}
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