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

systemd / systemd / 15057632786

15 May 2025 09:01PM UTC coverage: 72.267% (+0.02%) from 72.244%
15057632786

push

github

bluca
man: document how to hook stuff into system wakeup

Fixes: #6364

298523 of 413084 relevant lines covered (72.27%)

738132.88 hits per line

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

75.0
/src/udev/udev-spawn.c
1
/* SPDX-License-Identifier: GPL-2.0-or-later */
2

3
#include "sd-event.h"
4

5
#include "build-path.h"
6
#include "device-private.h"
7
#include "device-util.h"
8
#include "event-util.h"
9
#include "exec-util.h"
10
#include "extract-word.h"
11
#include "fd-util.h"
12
#include "path-util.h"
13
#include "process-util.h"
14
#include "signal-util.h"
15
#include "string-util.h"
16
#include "strv.h"
17
#include "udev-builtin.h"
18
#include "udev-event.h"
19
#include "udev-spawn.h"
20
#include "udev-trace.h"
21
#include "udev-worker.h"
22

23
typedef struct Spawn {
24
        sd_device *device;
25
        const char *cmd;
26
        PidRef pidref;
27
        usec_t timeout_warn_usec;
28
        usec_t timeout_usec;
29
        int timeout_signal;
30
        usec_t event_birth_usec;
31
        usec_t cmd_birth_usec;
32
        bool accept_failure;
33
        int fd_stdout;
34
        int fd_stderr;
35
        char *result;
36
        size_t result_size;
37
        size_t result_len;
38
        bool truncated;
39
} Spawn;
40

41
static int on_spawn_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
34,963✔
42
        Spawn *spawn = ASSERT_PTR(userdata);
34,963✔
43
        char buf[4096], *p;
34,963✔
44
        size_t size;
34,963✔
45
        ssize_t l;
34,963✔
46
        int r;
34,963✔
47

48
        assert(fd == spawn->fd_stdout || fd == spawn->fd_stderr);
34,963✔
49
        assert(!spawn->result || spawn->result_len < spawn->result_size);
34,963✔
50

51
        if (fd == spawn->fd_stdout && spawn->result) {
34,963✔
52
                p = spawn->result + spawn->result_len;
19,494✔
53
                size = spawn->result_size - spawn->result_len;
19,494✔
54
        } else {
55
                p = buf;
56
                size = sizeof(buf);
57
        }
58

59
        l = read(fd, p, size - (p == buf));
34,963✔
60
        if (l < 0) {
34,963✔
61
                if (errno == EAGAIN)
×
62
                        goto reenable;
×
63

64
                log_device_error_errno(spawn->device, errno,
×
65
                                       "Failed to read stdout of '%s': %m", spawn->cmd);
66

67
                return 0;
34,963✔
68
        }
69

70
        if ((size_t) l == size) {
34,963✔
71
                log_device_warning(spawn->device, "Truncating stdout of '%s' up to %zu byte.",
2✔
72
                                   spawn->cmd, spawn->result_size);
73
                l--;
2✔
74
                spawn->truncated = true;
2✔
75
        }
76

77
        p[l] = '\0';
34,963✔
78
        if (fd == spawn->fd_stdout && spawn->result)
34,963✔
79
                spawn->result_len += l;
19,494✔
80

81
        /* Log output only if we watch stderr. */
82
        if (l > 0 && spawn->fd_stderr >= 0) {
34,963✔
83
                _cleanup_strv_free_ char **v = NULL;
11,125✔
84

85
                r = strv_split_newlines_full(&v, p, EXTRACT_RETAIN_ESCAPE);
11,125✔
86
                if (r < 0)
11,125✔
87
                        log_device_debug(spawn->device,
×
88
                                         "Failed to split output from '%s'(%s), ignoring: %m",
89
                                         spawn->cmd, fd == spawn->fd_stdout ? "out" : "err");
90

91
                STRV_FOREACH(q, v)
63,822✔
92
                        log_device_debug(spawn->device, "'%s'(%s) '%s'", spawn->cmd,
53,218✔
93
                                         fd == spawn->fd_stdout ? "out" : "err", *q);
94
        }
95

96
        if (l == 0 || spawn->truncated)
34,963✔
97
                return 0;
98

99
reenable:
11,134✔
100
        /* Re-enable the event source if we did not encounter EOF */
101

102
        r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT);
11,134✔
103
        if (r < 0)
11,134✔
104
                log_device_error_errno(spawn->device, r,
×
105
                                       "Failed to reactivate IO source of '%s'", spawn->cmd);
106
        return 0;
107
}
108

109
static int on_spawn_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
×
110
        Spawn *spawn = ASSERT_PTR(userdata);
×
111

112
        DEVICE_TRACE_POINT(spawn_timeout, spawn->device, spawn->cmd);
×
113

114
        log_device_error(spawn->device, "Spawned process '%s' ["PID_FMT"] timed out after %s, killing.",
×
115
                         spawn->cmd, spawn->pidref.pid,
116
                         FORMAT_TIMESPAN(spawn->timeout_usec, USEC_PER_SEC));
117

118
        (void) pidref_kill_and_sigcont(&spawn->pidref, spawn->timeout_signal);
×
119
        return 1;
×
120
}
121

122
static int on_spawn_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) {
×
123
        Spawn *spawn = ASSERT_PTR(userdata);
×
124

125
        log_device_warning(spawn->device, "Spawned process '%s' ["PID_FMT"] is taking longer than %s to complete.",
×
126
                           spawn->cmd, spawn->pidref.pid,
127
                           FORMAT_TIMESPAN(spawn->timeout_warn_usec, USEC_PER_SEC));
128

129
        return 1;
×
130
}
131

132
static int on_spawn_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) {
11,920✔
133
        Spawn *spawn = ASSERT_PTR(userdata);
11,920✔
134
        int ret = -EIO;
11,920✔
135

136
        switch (si->si_code) {
11,920✔
137
        case CLD_EXITED:
11,918✔
138
                if (si->si_status == 0)
11,918✔
139
                        log_device_debug(spawn->device, "Process '%s' succeeded.", spawn->cmd);
11,929✔
140
                else
141
                        log_device_full(spawn->device, spawn->accept_failure ? LOG_DEBUG : LOG_WARNING,
×
142
                                        "Process '%s' failed with exit code %i.", spawn->cmd, si->si_status);
143
                ret = si->si_status;
11,918✔
144
                break;
11,918✔
145
        case CLD_KILLED:
2✔
146
        case CLD_DUMPED:
147
                log_device_error(spawn->device, "Process '%s' terminated by signal %s.", spawn->cmd, signal_to_string(si->si_status));
2✔
148
                break;
2✔
149
        default:
×
150
                log_device_error(spawn->device, "Process '%s' failed due to unknown reason.", spawn->cmd);
×
151
        }
152

153
        DEVICE_TRACE_POINT(spawn_exit, spawn->device, spawn->cmd);
11,920✔
154

155
        sd_event_exit(sd_event_source_get_event(s), ret);
11,920✔
156
        return 1;
11,920✔
157
}
158

159
static int spawn_wait(Spawn *spawn) {
11,920✔
160
        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
11,920✔
161
        _cleanup_(sd_event_source_disable_unrefp) sd_event_source *sigchld_source = NULL;
11,920✔
162
        _cleanup_(sd_event_source_disable_unrefp) sd_event_source *stdout_source = NULL;
11,920✔
163
        _cleanup_(sd_event_source_disable_unrefp) sd_event_source *stderr_source = NULL;
11,920✔
164
        int r;
11,920✔
165

166
        assert(spawn);
11,920✔
167

168
        r = sd_event_new(&e);
11,920✔
169
        if (r < 0)
11,920✔
170
                return log_device_debug_errno(spawn->device, r, "Failed to allocate sd-event object: %m");
×
171

172
        if (spawn->timeout_usec != USEC_INFINITY) {
11,920✔
173
                if (spawn->timeout_warn_usec < spawn->timeout_usec) {
11,920✔
174
                        r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC,
35,760✔
175
                                              usec_add(spawn->cmd_birth_usec, spawn->timeout_warn_usec), USEC_PER_SEC,
23,840✔
176
                                              on_spawn_timeout_warning, spawn);
177
                        if (r < 0)
11,920✔
178
                                return log_device_debug_errno(spawn->device, r, "Failed to create timeout warning event source: %m");
×
179
                }
180

181
                r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC,
35,760✔
182
                                      usec_add(spawn->cmd_birth_usec, spawn->timeout_usec), USEC_PER_SEC,
23,840✔
183
                                      on_spawn_timeout, spawn);
184
                if (r < 0)
11,920✔
185
                        return log_device_debug_errno(spawn->device, r, "Failed to create timeout event source: %m");
×
186
        }
187

188
        if (spawn->fd_stdout >= 0) {
11,920✔
189
                r = sd_event_add_io(e, &stdout_source, spawn->fd_stdout, EPOLLIN, on_spawn_io, spawn);
11,920✔
190
                if (r < 0)
11,920✔
191
                        return log_device_debug_errno(spawn->device, r, "Failed to create stdio event source: %m");
×
192
                r = sd_event_source_set_enabled(stdout_source, SD_EVENT_ONESHOT);
11,920✔
193
                if (r < 0)
11,920✔
194
                        return log_device_debug_errno(spawn->device, r, "Failed to enable stdio event source: %m");
×
195
        }
196

197
        if (spawn->fd_stderr >= 0) {
11,920✔
198
                r = sd_event_add_io(e, &stderr_source, spawn->fd_stderr, EPOLLIN, on_spawn_io, spawn);
11,909✔
199
                if (r < 0)
11,909✔
200
                        return log_device_debug_errno(spawn->device, r, "Failed to create stderr event source: %m");
×
201
                r = sd_event_source_set_enabled(stderr_source, SD_EVENT_ONESHOT);
11,909✔
202
                if (r < 0)
11,909✔
203
                        return log_device_debug_errno(spawn->device, r, "Failed to enable stderr event source: %m");
×
204
        }
205

206
        r = event_add_child_pidref(e, &sigchld_source, &spawn->pidref, WEXITED, on_spawn_sigchld, spawn);
11,920✔
207
        if (r < 0)
11,920✔
208
                return log_device_debug_errno(spawn->device, r, "Failed to create sigchild event source: %m");
×
209
        /* SIGCHLD should be processed after IO is complete */
210
        r = sd_event_source_set_priority(sigchld_source, SD_EVENT_PRIORITY_NORMAL + 1);
11,920✔
211
        if (r < 0)
11,920✔
212
                return log_device_debug_errno(spawn->device, r, "Failed to set priority to sigchild event source: %m");
×
213

214
        return sd_event_loop(e);
11,920✔
215
}
216

217
int udev_event_spawn(
11,920✔
218
                UdevEvent *event,
219
                bool accept_failure,
220
                const char *cmd,
221
                char *result,
222
                size_t result_size,
223
                bool *ret_truncated) {
224

225
        int r;
11,920✔
226

227
        assert(event);
11,920✔
228
        assert(IN_SET(event->event_mode, EVENT_UDEV_WORKER, EVENT_UDEVADM_TEST, EVENT_TEST_RULE_RUNNER, EVENT_TEST_SPAWN));
11,920✔
229
        assert(event->dev);
11,920✔
230
        assert(cmd);
11,920✔
231
        assert(result || result_size == 0);
11,920✔
232

233
        if (event->event_mode == EVENT_UDEVADM_TEST &&
11,920✔
234
            !STARTSWITH_SET(cmd, "ata_id", "cdrom_id", "dmi_memory_id", "fido_id", "mtd_probe", "scsi_id")) {
×
235
                log_device_debug(event->dev, "Running in test mode, skipping execution of '%s'.", cmd);
×
236
                result[0] = '\0';
×
237
                if (ret_truncated)
×
238
                        *ret_truncated = false;
×
239
                return 0;
×
240
        }
241

242
        int timeout_signal = event->worker ? event->worker->config.timeout_signal : SIGKILL;
11,920✔
243
        usec_t timeout_usec = event->worker ? event->worker->config.timeout_usec : DEFAULT_WORKER_TIMEOUT_USEC;
11,920✔
244
        usec_t now_usec = now(CLOCK_MONOTONIC);
11,920✔
245
        usec_t age_usec = usec_sub_unsigned(now_usec, event->birth_usec);
11,920✔
246
        usec_t cmd_timeout_usec = usec_sub_unsigned(timeout_usec, age_usec);
11,920✔
247
        if (cmd_timeout_usec <= 0)
11,920✔
248
                return log_device_warning_errno(event->dev, SYNTHETIC_ERRNO(ETIME),
×
249
                                                "The event already takes longer (%s) than the timeout (%s), skipping execution of '%s'.",
250
                                                FORMAT_TIMESPAN(age_usec, 1), FORMAT_TIMESPAN(timeout_usec, 1), cmd);
251

252
        /* pipes from child to parent */
253
        _cleanup_close_pair_ int outpipe[2] = EBADF_PAIR;
11,920✔
254
        if (result || log_get_max_level() >= LOG_INFO)
11,920✔
255
                if (pipe2(outpipe, O_NONBLOCK|O_CLOEXEC) != 0)
11,920✔
256
                        return log_device_error_errno(event->dev, errno,
×
257
                                                      "Failed to create pipe for command '%s': %m", cmd);
258

259
        _cleanup_close_pair_ int errpipe[2] = EBADF_PAIR;
11,920✔
260
        if (log_get_max_level() >= LOG_INFO)
11,920✔
261
                if (pipe2(errpipe, O_NONBLOCK|O_CLOEXEC) != 0)
11,909✔
262
                        return log_device_error_errno(event->dev, errno,
×
263
                                                      "Failed to create pipe for command '%s': %m", cmd);
264

265
        _cleanup_strv_free_ char **argv = NULL;
×
266
        r = strv_split_full(&argv, cmd, NULL, EXTRACT_UNQUOTE | EXTRACT_RELAX | EXTRACT_RETAIN_ESCAPE);
11,920✔
267
        if (r < 0)
11,920✔
268
                return log_device_error_errno(event->dev, r, "Failed to split command: %m");
×
269

270
        if (isempty(argv[0]))
11,920✔
271
                return log_device_error_errno(event->dev, SYNTHETIC_ERRNO(EINVAL),
×
272
                                              "Invalid command '%s'", cmd);
273

274
        /* allow programs in /usr/lib/udev/ to be called without the path */
275
        if (!path_is_absolute(argv[0])) {
11,920✔
276
                char *program;
4,252✔
277

278
                program = path_join(UDEVLIBEXECDIR, argv[0]);
4,252✔
279
                if (!program)
4,252✔
280
                        return log_oom();
×
281

282
                free_and_replace(argv[0], program);
4,252✔
283
        }
284

285
        char *found;
11,920✔
286
        _cleanup_close_ int fd_executable = r = pin_callout_binary(argv[0], &found);
23,840✔
287
        if (r < 0)
11,920✔
288
                return log_device_error_errno(event->dev, r, "Failed to find and pin callout binary \"%s\": %m", argv[0]);
×
289

290
        log_device_debug(event->dev, "Found callout binary: \"%s\".", found);
11,931✔
291
        free_and_replace(argv[0], found);
11,920✔
292

293
        char **envp;
11,920✔
294
        r = device_get_properties_strv(event->dev, &envp);
11,920✔
295
        if (r < 0)
11,920✔
296
                return log_device_error_errno(event->dev, r, "Failed to get device properties");
×
297

298
        log_device_debug(event->dev, "Starting '%s'", cmd);
11,931✔
299

300
        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
11,920✔
301
        r = pidref_safe_fork_full(
35,828✔
302
                        "(spawn)",
303
                        (int[]) { -EBADF, outpipe[WRITE_END], errpipe[WRITE_END] },
11,920✔
304
                        &fd_executable, 1,
305
                        FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE,
306
                        &pidref);
307
        if (r < 0)
23,908✔
308
                return log_device_error_errno(event->dev, r,
×
309
                                              "Failed to fork() to execute command '%s': %m", cmd);
310
        if (r == 0) {
23,908✔
311
                DEVICE_TRACE_POINT(spawn_exec, event->dev, cmd);
11,988✔
312
                (void) fexecve_or_execve(fd_executable, argv[0], argv, envp);
11,988✔
313
                _exit(EXIT_FAILURE);
×
314
        }
315

316
        /* parent closed child's ends of pipes */
317
        outpipe[WRITE_END] = safe_close(outpipe[WRITE_END]);
11,920✔
318
        errpipe[WRITE_END] = safe_close(errpipe[WRITE_END]);
11,920✔
319

320
        Spawn spawn = {
23,840✔
321
                .device = event->dev,
11,920✔
322
                .cmd = cmd,
323
                .pidref = pidref, /* Do not take ownership */
324
                .accept_failure = accept_failure,
325
                .timeout_warn_usec = udev_warn_timeout(cmd_timeout_usec),
11,920✔
326
                .timeout_usec = cmd_timeout_usec,
327
                .timeout_signal = timeout_signal,
328
                .event_birth_usec = event->birth_usec,
11,920✔
329
                .cmd_birth_usec = now_usec,
330
                .fd_stdout = outpipe[READ_END],
11,920✔
331
                .fd_stderr = errpipe[READ_END],
11,920✔
332
                .result = result,
333
                .result_size = result_size,
334
        };
335
        r = spawn_wait(&spawn);
11,920✔
336
        if (r < 0)
11,920✔
337
                return log_device_error_errno(event->dev, r,
2✔
338
                                              "Failed to wait for spawned command '%s': %m", cmd);
339

340
        if (result)
11,918✔
341
                result[spawn.result_len] = '\0';
9,755✔
342

343
        if (ret_truncated)
11,918✔
344
                *ret_truncated = spawn.truncated;
9,728✔
345

346
        return r; /* 0 for success, and positive if the program failed */
347
}
348

349
void udev_event_execute_run(UdevEvent *event) {
105,009✔
350
        const char *command;
105,009✔
351
        void *val;
105,009✔
352
        int r;
105,009✔
353

354
        assert(event);
105,009✔
355

356
        ORDERED_HASHMAP_FOREACH_KEY(val, command, event->run_list) {
111,564✔
357
                UdevBuiltinCommand builtin_cmd = PTR_TO_UDEV_BUILTIN_CMD(val);
6,555✔
358

359
                if (builtin_cmd >= 0) {
4,390✔
360
                        log_device_debug(event->dev, "Running built-in command \"%s\"", command);
4,412✔
361
                        r = udev_builtin_run(event, builtin_cmd, command);
4,390✔
362
                        if (r < 0)
4,390✔
363
                                log_device_debug_errno(event->dev, r, "Failed to run built-in command \"%s\", ignoring: %m", command);
9✔
364
                } else {
365
                        if (event->worker && event->worker->config.exec_delay_usec > 0) {
2,165✔
366
                                usec_t now_usec = now(CLOCK_MONOTONIC);
×
367
                                usec_t age_usec = usec_sub_unsigned(now_usec, event->birth_usec);
×
368

369
                                if (event->worker->config.exec_delay_usec >= usec_sub_unsigned(event->worker->config.timeout_usec, age_usec)) {
×
370
                                        log_device_warning(event->dev,
×
371
                                                           "Cannot delay execution of \"%s\" for %s, skipping.",
372
                                                           command, FORMAT_TIMESPAN(event->worker->config.exec_delay_usec, USEC_PER_SEC));
373
                                        continue;
×
374
                                }
375

376
                                log_device_debug(event->dev, "Delaying execution of \"%s\" for %s.",
×
377
                                                 command, FORMAT_TIMESPAN(event->worker->config.exec_delay_usec, USEC_PER_SEC));
378
                                (void) usleep_safe(event->worker->config.exec_delay_usec);
×
379
                        }
380

381
                        log_device_debug(event->dev, "Running command \"%s\"", command);
2,165✔
382

383
                        r = udev_event_spawn(event, /* accept_failure = */ false, command, NULL, 0, NULL);
2,165✔
384
                        if (r < 0)
2,165✔
385
                                log_device_warning_errno(event->dev, r, "Failed to execute '%s', ignoring: %m", command);
2✔
386
                        else if (r > 0) /* returned value is positive when program fails */
2,163✔
387
                                log_device_debug(event->dev, "Command \"%s\" returned %d (error), ignoring.", command, r);
×
388
                }
389
        }
390
}
105,009✔
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