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

systemd / systemd / 25084703852

28 Apr 2026 09:34PM UTC coverage: 71.849% (-0.02%) from 71.865%
25084703852

push

github

daandemeyer
ci: Reduce noise from claude-review workflow

322528 of 448894 relevant lines covered (71.85%)

1177215.84 hits per line

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

41.21
/src/shared/fork-notify.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

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

6
#include "alloc-util.h"
7
#include "build-path.h"
8
#include "chase.h"
9
#include "chattr-util.h"
10
#include "escape.h"
11
#include "event-util.h"
12
#include "exit-status.h"
13
#include "fd-util.h"
14
#include "fork-notify.h"
15
#include "log.h"
16
#include "notify-recv.h"
17
#include "parse-util.h"
18
#include "path-util.h"
19
#include "pidref.h"
20
#include "process-util.h"
21
#include "runtime-scope.h"
22
#include "signal-util.h"
23
#include "strv.h"
24

25
static int on_child_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
×
26
        PidRef *child = ASSERT_PTR(userdata);
×
27

28
        assert(si);
×
29
        assert(si->si_pid == child->pid);
×
30

31
        /* Let's first do some debug logging about the exit status of the child */
32

33
        if (si->si_code == CLD_EXITED) {
×
34
                if (si->si_status == EXIT_SUCCESS)
×
35
                        log_debug("Child process " PID_FMT " exited successfully.", si->si_pid);
×
36
                else
37
                        log_debug("Child process " PID_FMT " died with a failure exit status %i, ignoring.", si->si_pid, si->si_status);
×
38
        } else if (si->si_code == CLD_KILLED)
×
39
                log_debug("Child process " PID_FMT " was killed by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status));
×
40
        else if (si->si_code == CLD_DUMPED)
×
41
                log_debug("Child process " PID_FMT " dumped core by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status));
×
42
        else
43
                log_debug("Got unexpected exit code %i from child, ignoring.", si->si_code);
×
44

45
        /* And let's then fail the whole thing, because regardless what the exit status of the child is
46
         * (i.e. even if successful), if it exits before sending READY=1 something is wrong. */
47

48
        return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Child " PID_FMT " died before sending notification message.", child->pid);
×
49
}
50

51
static int on_child_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
2✔
52
        PidRef *child = ASSERT_PTR(userdata);
2✔
53
        int r;
2✔
54

55
        assert(s);
2✔
56
        assert(fd >= 0);
2✔
57

58
        _cleanup_strv_free_ char **msg = NULL;
2✔
59
        _cleanup_(pidref_done) PidRef sender = PIDREF_NULL;
2✔
60
        r = notify_recv_strv(fd, &msg, /* ret_ucred= */ NULL, &sender);
2✔
61
        if (r == -EAGAIN)
2✔
62
                return 0;
63
        if (r < 0)
2✔
64
                return r;
65

66
        if (!pidref_equal(child, &sender)) {
2✔
67
                log_warning("Received notification message from unexpected process " PID_FMT " (expected " PID_FMT "), ignoring.",
×
68
                            sender.pid, child->pid);
69
                return 0;
×
70
        }
71

72
        if (strv_contains(msg, "READY=1"))
2✔
73
                return sd_event_exit(sd_event_source_get_event(s), EXIT_SUCCESS);
2✔
74

75
        const char *e = strv_find_startswith(msg, "ERRNO=");
×
76
        if (e) {
×
77
                int error;
×
78

79
                r = safe_atoi(e, &error);
×
80
                if (r < 0) {
×
81
                        log_debug_errno(r, "Received invalid ERRNO= notification message, ignoring: %s", e);
×
82
                        return 0;
×
83
                }
84
                if (error <= 0) {
×
85
                        log_debug("Received non-positive ERRNO= notification message, ignoring: %d", error);
×
86
                        return 0;
×
87
                }
88

89
                return -error;
×
90
        }
91

92
        return 0;
93
}
94

95
int fork_notify(char * const *argv, fork_notify_handler_t child_handler, void *child_userdata, PidRef *ret_pidref) {
2✔
96
        int r;
2✔
97

98
        assert(argv);
2✔
99
        assert(ret_pidref);
2✔
100

101
        if (!is_main_thread())
2✔
102
                return -EPERM;
2✔
103

104
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
2✔
105
        r = sd_event_new(&event);
2✔
106
        if (r < 0)
2✔
107
                return r;
108

109
        _cleanup_(sd_event_source_disable_unrefp) sd_event_source *notify_event_source = NULL;
2✔
110
        _cleanup_(pidref_done_sigkill_wait) PidRef child = PIDREF_NULL;
×
111
        _cleanup_free_ char *addr_string = NULL;
2✔
112
        r = notify_socket_prepare_full(
2✔
113
                        event,
114
                        SD_EVENT_PRIORITY_NORMAL-10, /* We want the notification message from the child before the child exit */
115
                        on_child_notify,
116
                        &child,
117
                        /* accept_fds= */ false,
118
                        &addr_string,
119
                        &notify_event_source);
120
        if (r < 0)
2✔
121
                return r;
122

123
        r = sd_event_source_set_exit_on_failure(notify_event_source, true);
2✔
124
        if (r < 0)
2✔
125
                return r;
126

127
        if (DEBUG_LOGGING) {
2✔
128
                _cleanup_free_ char *l = quote_command_line(argv, SHELL_ESCAPE_EMPTY);
×
129
                log_debug("Invoking '%s' as child.", strnull(l));
×
130
        }
131

132
        r = pidref_safe_fork_full(
6✔
133
                        "(fork-notify)",
134
                        (const int[3]) { -EBADF, STDOUT_FILENO, STDERR_FILENO },
2✔
135
                        /* except_fds= */ NULL,
136
                        /* n_except_fds= */ 0,
137
                        FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO,
138
                        &child);
139
        if (r < 0)
4✔
140
                return r;
141
        if (r == 0) {
4✔
142
                /* In the child: */
143

144
                if (setenv("NOTIFY_SOCKET", addr_string, /* overwrite= */ true) < 0) {
2✔
145
                        log_debug_errno(errno, "Failed to set $NOTIFY_SOCKET: %m");
×
146
                        _exit(EXIT_MEMORY);
×
147
                }
148

149
                /* After fork and before exec one can execute custom code in this function
150
                 * but since all open FDs were closed only limited actions are safe (e.g., setenv),
151
                 * akin to how in a signal handler only certain things are safe. */
152
                if (child_handler)
2✔
153
                        child_handler(child_userdata);
×
154

155
                r = invoke_callout_binary(argv[0], argv);
2✔
156
                log_debug_errno(r, "Failed to invoke %s: %m", argv[0]);
×
157
                _exit(EXIT_EXEC);
×
158
        }
159

160
        _cleanup_(sd_event_source_disable_unrefp) sd_event_source *child_event_source = NULL;
2✔
161
        r = event_add_child_pidref(event, &child_event_source, &child, WEXITED, on_child_exit, &child);
2✔
162
        if (r < 0)
2✔
163
                return r;
164

165
        r = sd_event_source_set_exit_on_failure(child_event_source, true);
2✔
166
        if (r < 0)
2✔
167
                return r;
168

169
        (void) sd_event_source_set_description(child_event_source, "fork-notify-child");
2✔
170

171
        r = sd_event_loop(event);
2✔
172
        if (r < 0)
2✔
173
                return r;
174
        assert(r == 0);
2✔
175

176
        *ret_pidref = TAKE_PIDREF(child);
2✔
177

178
        return 0;
2✔
179
}
180

181
static void fork_notify_terminate_internal(PidRef *pidref) {
7,042✔
182
        int r;
7,042✔
183

184
        if (!pidref_is_set(pidref))
7,042✔
185
                return;
186

187
        r = pidref_kill(pidref, SIGTERM);
2✔
188
        if (r < 0 && r != -ESRCH)
2✔
189
                log_debug_errno(r, "Failed to send SIGTERM to child " PID_FMT ", ignoring: %m", pidref->pid);
×
190

191
        (void) pidref_wait_for_terminate_and_check(/* name= */ NULL, pidref, /* flags= */ 0);
2✔
192
}
193

194
void fork_notify_terminate(PidRef *pidref) {
7,042✔
195
        fork_notify_terminate_internal(pidref);
7,042✔
196
        pidref_done(pidref);
7,042✔
197
}
7,042✔
198

199
void fork_notify_terminate_many(sd_event_source **array, size_t n) {
×
200
        int r;
×
201

202
        assert(array || n == 0);
×
203

204
        FOREACH_ARRAY(s, array, n) {
×
205
                PidRef child;
×
206

207
                r = event_source_get_child_pidref(*s, &child);
×
208
                if (r >= 0)
×
209
                        fork_notify_terminate_internal(&child);
×
210
                else
211
                        log_debug_errno(r, "Could not get pidref for event source: %m");
×
212

213
                sd_event_source_unref(*s);
×
214
        }
215

216
        free(array);
×
217
}
×
218

219
int journal_fork(RuntimeScope scope, char * const* units, OutputMode output, PidRef *ret_pidref) {
2✔
220
        assert(scope >= 0);
2✔
221
        assert(scope < _RUNTIME_SCOPE_MAX);
2✔
222

223
        if (strv_isempty(units))
2✔
224
                return 0;
2✔
225

226
        _cleanup_strv_free_ char **argv = strv_new(
4✔
227
                        "journalctl",
228
                        "-q",
229
                        "--follow",
230
                        "--no-pager",
231
                        "--lines=0",
232
                        "--synchronize-on-exit=yes");
233
        if (!argv)
2✔
234
                return log_oom_debug();
×
235

236
        STRV_FOREACH(u, units)
4✔
237
                if (strv_extendf(&argv,
2✔
238
                                 scope == RUNTIME_SCOPE_SYSTEM ? "--unit=%s" : "--user-unit=%s",
239
                                 *u) < 0)
240
                        return log_oom_debug();
×
241

242
        if (output >= 0)
2✔
243
                if (strv_extendf(&argv, "--output=%s", output_mode_to_string(output)) < 0)
×
244
                        return log_oom_debug();
×
245

246
        return fork_notify(argv, /* child_handler= */ NULL, /* child_userdata= */ NULL, ret_pidref);
2✔
247
}
248

249
static void set_journal_remote_config(void *userdata) {
×
250
        if (setenv("SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE", "/dev/null", /* overwrite= */ true) < 0) {
×
251
                log_debug_errno(errno, "Failed to set $SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE: %m");
×
252
                _exit(EXIT_MEMORY);
×
253
        }
254
}
×
255

256
int fork_journal_remote(
×
257
                const char *listen_address,
258
                const char *output,
259
                uint64_t max_use,
260
                uint64_t keep_free,
261
                uint64_t max_file_size,
262
                uint64_t max_files,
263
                PidRef *ret_pidref) {
264

265
        int r;
×
266

267
        assert(listen_address);
×
268
        assert(output);
×
269
        assert(ret_pidref);
×
270

271
        ChaseFlags chase_flags = CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY;
×
272
        if (endswith(output, ".journal"))
×
273
                chase_flags |= CHASE_PARENT;
×
274

275
        _cleanup_close_ int fd = -EBADF;
×
276
        r = chase(output, /* root= */ NULL, chase_flags, /* ret_path= */ NULL, &fd);
×
277
        if (r < 0)
×
278
                return log_error_errno(r, "Failed to create journal directory for '%s': %m", output);
×
279

280
        r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
×
281
        if (r < 0)
×
282
                log_debug_errno(r, "Failed to set NOCOW flag on journal directory for '%s', ignoring: %m", output);
×
283

284
        _cleanup_free_ char *sd_socket_activate = NULL;
×
285
        r = find_executable("systemd-socket-activate", &sd_socket_activate);
×
286
        if (r < 0)
×
287
                return log_error_errno(r, "Failed to find systemd-socket-activate binary: %m");
×
288

289
        _cleanup_free_ char *sd_journal_remote = NULL;
×
290
        r = find_executable_full(
×
291
                        "systemd-journal-remote",
292
                        /* root= */ NULL,
293
                        STRV_MAKE(LIBEXECDIR),
×
294
                        /* use_path_envvar= */ true,
295
                        &sd_journal_remote,
296
                        /* ret_fd= */ NULL);
297
        if (r < 0)
×
298
                return log_error_errno(r, "Failed to find systemd-journal-remote binary: %m");
×
299

300
        _cleanup_strv_free_ char **argv = strv_new(
×
301
                        sd_socket_activate,
302
                        "--listen", listen_address,
303
                        sd_journal_remote,
304
                        "--output", output,
305
                        "--split-mode", endswith(output, ".journal") ? "none" : "host");
306
        if (!argv)
×
307
                return log_oom();
×
308

309
        if (max_use != UINT64_MAX &&
×
310
            strv_extendf(&argv, "--max-use=%" PRIu64, max_use) < 0)
×
311
                return log_oom();
×
312

313
        if (keep_free != UINT64_MAX &&
×
314
            strv_extendf(&argv, "--keep-free=%" PRIu64, keep_free) < 0)
×
315
                return log_oom();
×
316

317
        if (max_file_size != UINT64_MAX &&
×
318
            strv_extendf(&argv, "--max-file-size=%" PRIu64, max_file_size) < 0)
×
319
                return log_oom();
×
320

321
        if (max_files != UINT64_MAX &&
×
322
            strv_extendf(&argv, "--max-files=%" PRIu64, max_files) < 0)
×
323
                return log_oom();
×
324

325
        return fork_notify(argv, set_journal_remote_config, /* child_userdata= */ NULL, ret_pidref);
×
326
}
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