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

systemd / systemd / 25026908423

27 Apr 2026 07:14PM UTC coverage: 71.865% (-0.3%) from 72.175%
25026908423

push

github

daandemeyer
udev: don't assert on worker cap after killing a broken idle worker

manager_can_process_event() considers an event processable if either
there is room below children_max to spawn, or an idle worker exists.
When only the latter holds, event_run() picks the idle worker and
tries device_monitor_send(). If that send fails, event_run() SIGKILLs
the worker, marks it WORKER_KILLED and continues the loop. With no
other idle worker available, it falls through to worker_spawn(),
guarded by:

    assert(hashmap_size(manager->workers) < manager->config.children_max);

The just-killed worker is still in manager->workers until its SIGCHLD
is reaped by on_worker_exit(), so at the cap this assertion trips and
udevd aborts:

    Assertion 'hashmap_size(manager->workers) < manager->config.children_max'
    failed at src/udev/udev-manager.c:635, function event_run(). Aborting.

Instead of asserting, bail out when we are already at the worker
limit. The event remains in EVENT_QUEUED; once the killed worker's
SIGCHLD arrives and frees it from the hashmap, on_post() re-runs
event_queue_start() and the event is retried.

1 of 1 new or added line in 1 file covered. (100.0%)

7309 existing lines in 125 files now uncovered.

322519 of 448782 relevant lines covered (71.87%)

1173939.78 hits per line

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

46.89
/src/socket-activate/socket-activate.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <sys/epoll.h>
4
#include <sys/wait.h>
5
#include <unistd.h>
6

7
#include "sd-daemon.h"
8

9
#include "alloc-util.h"
10
#include "build.h"
11
#include "daemon-util.h"
12
#include "env-util.h"
13
#include "errno-util.h"
14
#include "escape.h"
15
#include "fd-util.h"
16
#include "format-table.h"
17
#include "format-util.h"
18
#include "log.h"
19
#include "main-func.h"
20
#include "options.h"
21
#include "pidfd-util.h"
22
#include "pidref.h"
23
#include "pretty-print.h"
24
#include "process-util.h"
25
#include "socket-netlink.h"
26
#include "socket-util.h"
27
#include "string-util.h"
28
#include "strv.h"
29

30
static char **arg_listen = NULL;
31
static bool arg_accept = false;
32
static int arg_socket_type = SOCK_STREAM;
33
static char **arg_setenv = NULL;
34
static char **arg_fdnames = NULL;
35
static bool arg_inetd = false;
36
static bool arg_now = false;
37

38
static int add_epoll(int epoll_fd, int fd) {
3✔
39
        struct epoll_event ev = {
3✔
40
                .events = EPOLLIN,
41
                .data.fd = fd,
42
        };
43

44
        assert(epoll_fd >= 0);
3✔
45
        assert(fd >= 0);
3✔
46

47
        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
3✔
48
                return log_error_errno(errno, "Failed to add event on epoll fd:%d for fd:%d: %m", epoll_fd, fd);
×
49

50
        return 0;
51
}
52

53
static int open_sockets(int *ret_epoll_fd) {
5✔
54
        _cleanup_close_ int epoll_fd = -EBADF;
5✔
55
        int n, r, count = 0;
5✔
56

57
        assert(ret_epoll_fd);
5✔
58

59
        n = sd_listen_fds(true);
5✔
60
        if (n < 0)
5✔
61
                return log_error_errno(n, "Failed to read listening file descriptors from environment: %m");
×
62
        if (n > 0) {
5✔
63
                log_info("Received %i descriptors via the environment.", n);
×
64

65
                for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
×
66
                        r = fd_cloexec(fd, arg_accept);
×
67
                        if (r < 0)
×
68
                                return r;
69

70
                        count++;
×
71
                }
72
        }
73

74
        /* Close logging and all other descriptors */
75
        if (arg_listen) {
5✔
76
                _cleanup_free_ int *except = new(int, n);
10✔
77
                if (!except)
5✔
78
                        return log_oom();
×
79

80
                for (int i = 0; i < n; i++)
5✔
81
                        except[i] = SD_LISTEN_FDS_START + i;
×
82

83
                log_close();
5✔
84
                log_set_open_when_needed(true);
5✔
85
                log_settle_target();
5✔
86

87
                r = close_all_fds(except, n);
5✔
88
                if (r < 0)
5✔
89
                        return log_error_errno(r, "Failed to close all file descriptors: %m");
×
90
        }
91

92
        /* Note: we leak some fd's on error here. It doesn't matter much, since the program will exit
93
         * immediately anyway, but would be a pain to fix. */
94

95
        STRV_FOREACH(address, arg_listen) {
11✔
96
                r = make_socket_fd(LOG_DEBUG, *address, arg_socket_type, (arg_accept * SOCK_CLOEXEC));
6✔
97
                if (r < 0)
6✔
98
                        return log_error_errno(r, "Failed to open '%s': %m", *address);
×
99

100
                assert(r == SD_LISTEN_FDS_START + count);
6✔
101
                count++;
6✔
102
        }
103

104
        if (arg_listen) {
5✔
105
                log_open();
5✔
106
                log_set_open_when_needed(false);
5✔
107
        }
108

109
        if (count > 1 && !arg_accept && arg_inetd)
5✔
110
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1✔
111
                                       "--inetd only supported with a single file descriptor, or with --accept.");
112

113
        if (arg_fdnames && !arg_inetd) {
4✔
114
                size_t n_fdnames = strv_length(arg_fdnames);
×
115

116
                if (!arg_accept && n_fdnames != (size_t) count)
×
117
                        log_warning("The number of fd names is different from the number of fds: %zu vs %i",
×
118
                                    n_fdnames, count);
119

120
                if (arg_accept && n_fdnames > 1)
×
121
                        log_warning("More than one fd name specified with --accept.");
×
122
        }
123

124
        if (!arg_now) {
4✔
125
                epoll_fd = epoll_create1(EPOLL_CLOEXEC);
3✔
126
                if (epoll_fd < 0)
3✔
127
                        return log_error_errno(errno, "Failed to create epoll object: %m");
×
128
        }
129

130
        for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) {
8✔
131
                _cleanup_free_ char *name = NULL;
4✔
132

133
                getsockname_pretty(fd, &name);
4✔
134
                log_info("Listening on %s as %i.", strna(name), fd);
4✔
135

136
                if (epoll_fd >= 0) {
4✔
137
                        r = add_epoll(epoll_fd, fd);
3✔
138
                        if (r < 0)
3✔
139
                                return r;
×
140
                }
141
        }
142

143
        *ret_epoll_fd = TAKE_FD(epoll_fd);
4✔
144
        return count;
4✔
145
}
146

147
static int exec_process(char * const *argv, int start_fd, size_t n_fds) {
5✔
148
        _cleanup_strv_free_ char **envp = NULL;
×
149
        int r;
5✔
150

151
        assert(!strv_isempty(argv));
5✔
152
        assert(start_fd >= 0);
5✔
153
        assert(n_fds > 0);
5✔
154

155
        FOREACH_STRING(var, "TERM", "COLORTERM", "NO_COLOR", "PATH", "USER", "HOME") {
35✔
156
                const char *n;
30✔
157

158
                n = strv_find_prefix(environ, var);
30✔
159
                if (!n)
30✔
160
                        continue;
20✔
161

162
                r = strv_extend(&envp, n);
10✔
163
                if (r < 0)
10✔
164
                        return r;
×
165
        }
166

167
        if (arg_inetd) {
5✔
168
                assert(n_fds == 1);
1✔
169

170
                r = rearrange_stdio(start_fd, start_fd, STDERR_FILENO); /* invalidates start_fd on success + error */
1✔
171
                if (r < 0)
1✔
172
                        return log_error_errno(r, "Failed to move fd to stdin+stdout: %m");
×
173

174
        } else {
175
                if (start_fd != SD_LISTEN_FDS_START) {
4✔
176
                        assert(n_fds == 1);
×
177

178
                        if (dup2(start_fd, SD_LISTEN_FDS_START) < 0)
×
179
                                return log_error_errno(errno, "Failed to dup connection: %m");
×
180

181
                        safe_close(start_fd);
×
182
                }
183

184
                r = strv_extendf(&envp, "LISTEN_FDS=%zu", n_fds);
4✔
185
                if (r < 0)
4✔
186
                        return r;
187

188
                r = strv_extendf(&envp, "LISTEN_PID=" PID_FMT, getpid_cached());
4✔
189
                if (r < 0)
4✔
190
                        return r;
191

192
                uint64_t pidfdid;
4✔
193
                if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
4✔
194
                        r = strv_extendf(&envp, "LISTEN_PIDFDID=%" PRIu64, pidfdid);
4✔
195
                        if (r < 0)
4✔
196
                                return r;
197
                }
198

199
                if (arg_fdnames) {
4✔
200
                        _cleanup_free_ char *names = NULL;
×
201
                        size_t len;
×
202

203
                        len = strv_length(arg_fdnames);
×
204
                        if (len == 1)
×
205
                                for (size_t i = 1; i < n_fds; i++) {
×
206
                                        r = strv_extend(&arg_fdnames, arg_fdnames[0]);
×
207
                                        if (r < 0)
×
208
                                                return log_oom();
×
209
                                }
210

211
                        names = strv_join(arg_fdnames, ":");
×
212
                        if (!names)
×
213
                                return log_oom();
×
214

215
                        char *t = strjoin("LISTEN_FDNAMES=", names);
×
216
                        if (!t)
×
217
                                return log_oom();
×
218

219
                        r = strv_consume(&envp, t);
×
220
                        if (r < 0)
×
221
                                return r;
222
                }
223
        }
224

225
        STRV_FOREACH(s, arg_setenv) {
5✔
226
                r = strv_env_replace_strdup(&envp, *s);
×
227
                if (r < 0)
×
228
                        return r;
229
        }
230

231
        _cleanup_free_ char *joined = strv_join(argv, " ");
5✔
232
        if (!joined)
5✔
233
                return log_oom();
×
234

235
        log_info("Executing: %s", joined);
5✔
236
        execvpe(argv[0], argv, envp);
5✔
237

238
        return log_error_errno(errno, "Failed to execute '%s': %m", joined);
×
239
}
240

241
static int fork_and_exec_process(char * const *argv, int fd) {
×
242
        _cleanup_free_ char *joined = NULL;
×
243
        int r;
×
244

245
        assert(!strv_isempty(argv));
×
246
        assert(fd >= 0);
×
247

248
        joined = strv_join(argv, " ");
×
249
        if (!joined)
×
250
                return log_oom();
×
251

252
        _cleanup_(pidref_done) PidRef child_pidref = PIDREF_NULL;
×
253
        r = pidref_safe_fork(
×
254
                        "(activate)",
255
                        FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG,
256
                        &child_pidref);
257
        if (r < 0)
1✔
258
                return r;
259
        if (r == 0) {
1✔
260
                /* In the child */
261
                (void) exec_process(argv, fd, 1);
1✔
262
                _exit(EXIT_FAILURE);
×
263
        }
264

265
        log_info("Spawned '%s' as PID " PID_FMT ".", joined, child_pidref.pid);
×
266
        return 0;
267
}
268

269
static int do_accept(char * const *argv, int fd) {
×
270
        _cleanup_free_ char *local = NULL, *peer = NULL;
×
271
        _cleanup_close_ int fd_accepted = -EBADF;
×
272

273
        fd_accepted = accept4(fd, NULL, NULL, 0);
×
274
        if (fd_accepted < 0) {
×
275
                if (ERRNO_IS_ACCEPT_AGAIN(errno))
×
276
                        return 0;
277

278
                return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd);
×
279
        }
280

281
        (void) getsockname_pretty(fd_accepted, &local);
×
282
        (void) getpeername_pretty(fd_accepted, true, &peer);
×
283
        log_info("Connection from %s to %s", strna(peer), strna(local));
×
284

285
        return fork_and_exec_process(argv, fd_accepted);
×
286
}
287

288
/* SIGCHLD handler. */
289
static void sigchld_hdl(int sig) {
×
290
        int r;
×
291

292
        PROTECT_ERRNO;
×
293

294
        for (;;) {
×
295
                siginfo_t si = {};
×
296

297
                r = waitid(P_ALL, 0, &si, WEXITED | WNOHANG);
×
298
                if (r < 0) {
×
299
                        if (errno != ECHILD)
×
300
                                log_error_errno(errno, "Failed to reap children: %m");
×
301
                        return;
×
302
                }
303
                if (si.si_pid == 0)
×
304
                        return;
305

306
                log_info("Child %d died with code %d", si.si_pid, si.si_status);
×
307
        }
308
}
309

310
static int install_chld_handler(void) {
×
311
        static const struct sigaction act = {
×
312
                .sa_flags = SA_NOCLDSTOP | SA_RESTART,
313
                .sa_handler = sigchld_hdl,
314
        };
315

316
        if (sigaction(SIGCHLD, &act, NULL) < 0)
×
317
                return log_error_errno(errno, "Failed to install SIGCHLD handler: %m");
×
318

319
        return 0;
320
}
321

322
static int help(void) {
×
323
        _cleanup_free_ char *link = NULL;
×
324
        _cleanup_(table_unrefp) Table *options = NULL;
×
325
        int r;
×
326

327
        r = terminal_urlify_man("systemd-socket-activate", "1", &link);
×
328
        if (r < 0)
×
329
                return log_oom();
×
330

331
        r = option_parser_get_help_table(&options);
×
332
        if (r < 0)
×
333
                return r;
334

335
        printf("%s [OPTIONS...] COMMAND ...\n"
×
336
               "\n%sListen on sockets and launch child on connection.%s\n"
337
               "\n%sOptions:%s\n",
338
               program_invocation_short_name,
339
               ansi_highlight(),
340
               ansi_normal(),
341
               ansi_underline(),
342
               ansi_normal());
343

344
        r = table_print_or_warn(options);
×
345
        if (r < 0)
×
346
                return r;
347

348
        printf("\nNote: file descriptors from sd_listen_fds() will be passed through.\n"
×
349
               "\nSee the %s for details.\n", link);
350
        return 0;
351
}
352

353
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
6✔
354
        assert(argc >= 0);
6✔
355
        assert(argv);
6✔
356
        assert(remaining_args);
6✔
357

358
        OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION };
6✔
359
        int r;
6✔
360

361
        FOREACH_OPTION(c, &opts, /* on_error= */ return c)
29✔
362
                switch (c) {
11✔
363

UNCOV
364
                OPTION_COMMON_HELP:
×
365
                        return help();
×
366

UNCOV
367
                OPTION_COMMON_VERSION:
×
368
                        return version();
×
369

370
                OPTION('l', "listen", "ADDR",
7✔
371
                       "Listen for raw connections at ADDR"):
372
                        r = strv_extend(&arg_listen, opts.arg);
7✔
373
                        if (r < 0)
7✔
UNCOV
374
                                return log_oom();
×
375

376
                        break;
377

UNCOV
378
                OPTION('d', "datagram", NULL,
×
379
                       "Listen on datagram instead of stream socket"):
UNCOV
380
                        if (arg_socket_type == SOCK_SEQPACKET)
×
381
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
382
                                                       "--datagram may not be combined with --seqpacket.");
383

UNCOV
384
                        arg_socket_type = SOCK_DGRAM;
×
385
                        break;
×
386

UNCOV
387
                OPTION_LONG("seqpacket", NULL,
×
388
                            "Listen on SOCK_SEQPACKET instead of stream socket"):
UNCOV
389
                        if (arg_socket_type == SOCK_DGRAM)
×
390
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
391
                                                       "--seqpacket may not be combined with --datagram.");
392

UNCOV
393
                        arg_socket_type = SOCK_SEQPACKET;
×
394
                        break;
×
395

396
                OPTION('a', "accept", NULL,
1✔
397
                       "Spawn separate child for each connection"):
398
                        arg_accept = true;
1✔
399
                        break;
1✔
400

UNCOV
401
                OPTION('E', "setenv", "NAME[=VALUE]",
×
402
                       "Pass an environment variable to children"): {}
×
403
                OPTION_LONG("environment", "NAME[=VALUE]", /* help= */ NULL): /* legacy alias */
×
404
                        r = strv_env_replace_strdup_passthrough(&arg_setenv, opts.arg);
×
405
                        if (r < 0)
×
406
                                return log_error_errno(r, "Cannot assign environment variable %s: %m", opts.arg);
×
407
                        break;
408

UNCOV
409
                OPTION_LONG("fdname", "NAME[:NAME...]",
×
410
                            "Specify names for file descriptors"): {
UNCOV
411
                        _cleanup_strv_free_ char **names = NULL;
×
412

UNCOV
413
                        names = strv_split(opts.arg, ":");
×
414
                        if (!names)
×
415
                                return log_oom();
×
416

UNCOV
417
                        STRV_FOREACH(s, names)
×
418
                                if (!fdname_is_valid(*s)) {
×
419
                                        _cleanup_free_ char *esc = NULL;
×
420

UNCOV
421
                                        esc = cescape(*s);
×
422
                                        log_warning("File descriptor name \"%s\" is not valid.", esc);
×
423
                                }
424

425
                        /* Empty optargs means one empty name */
UNCOV
426
                        r = strv_extend_strv(&arg_fdnames,
×
427
                                             strv_isempty(names) ? STRV_MAKE("") : names,
×
428
                                             false);
UNCOV
429
                        if (r < 0)
×
430
                                return log_error_errno(r, "strv_extend_strv: %m");
×
431
                        break;
×
432
                }
433

434
                OPTION_LONG("inetd", NULL,
1✔
435
                            "Enable inetd file descriptor passing protocol"):
436
                        arg_inetd = true;
1✔
437
                        break;
1✔
438

439
                OPTION_LONG("now", NULL,
2✔
440
                            "Start instantly instead of waiting for connection"):
441
                        arg_now = true;
2✔
442
                        break;
2✔
443
                }
444

445
        *remaining_args = option_parser_get_args(&opts);
6✔
446
        if (strv_isempty(*remaining_args))
6✔
UNCOV
447
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
448
                                       "%s: command to execute is missing.",
449
                                       program_invocation_short_name);
450

451
        if (arg_socket_type == SOCK_DGRAM && arg_accept)
6✔
UNCOV
452
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
453
                                       "Datagram sockets do not accept connections. "
454
                                       "The --datagram and --accept options may not be combined.");
455

456
        if (arg_accept && arg_now)
6✔
457
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1✔
458
                                       "--now cannot be used in conjunction with --accept.");
459

460
        if (arg_fdnames && arg_inetd)
5✔
UNCOV
461
                log_warning("--fdname= has no effect with --inetd present.");
×
462

463
        return 1 /* work to do */;
464
}
465

466
static int run(int argc, char **argv) {
6✔
467
        _cleanup_close_ int epoll_fd = -EBADF;
2✔
468
        _cleanup_strv_free_ char **exec_argv = NULL;
2✔
469
        int r, n;
6✔
470

471
        log_setup();
6✔
472

473
        char **args = NULL;
6✔
474
        r = parse_argv(argc, argv, &args);
6✔
475
        if (r <= 0)
6✔
476
                return r;
477

478
        exec_argv = strv_copy(args);
5✔
479
        if (!exec_argv)
5✔
UNCOV
480
                return log_oom();
×
481

482
        assert(!strv_isempty(exec_argv));
5✔
483

484
        n = open_sockets(&epoll_fd);
5✔
485
        if (n < 0)
5✔
486
                return n;
487
        if (n == 0)
4✔
UNCOV
488
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No sockets to listen on specified or passed in.");
×
489

490
        if (arg_accept) {
4✔
UNCOV
491
                r = install_chld_handler();
×
492
                if (r < 0)
×
493
                        return r;
494
        }
495

496
        /* Notify the caller that all sockets are open now. */
497
        _unused_ _cleanup_(notify_on_cleanup) const char *notify = notify_start(NOTIFY_READY_MESSAGE, NOTIFY_STOPPING_MESSAGE);
4✔
498

499
        for (;;) {
4✔
500
                struct epoll_event event;
4✔
501

502
                if (epoll_fd >= 0) {
4✔
503
                        if (epoll_wait(epoll_fd, &event, 1, -1) < 0) {
3✔
UNCOV
504
                                if (errno == EINTR)
×
505
                                        continue;
×
506

UNCOV
507
                                return log_error_errno(errno, "epoll_wait() failed: %m");
×
508
                        }
509

510
                        log_info("Communication attempt on fd %i.", event.data.fd);
3✔
511
                }
512

513
                if (!arg_accept)
4✔
514
                        return exec_process(exec_argv, SD_LISTEN_FDS_START, (size_t) n);
4✔
515

UNCOV
516
                r = do_accept(exec_argv, event.data.fd);
×
517
                if (r < 0)
×
518
                        return r;
519
        }
520
}
521

522
DEFINE_MAIN_FUNCTION(run);
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc