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

systemd / systemd / 20561496515

28 Dec 2025 11:55AM UTC coverage: 72.478% (-0.2%) from 72.692%
20561496515

push

github

web-flow
core: several follow-ups for BindNetworkInterface= (#40202)

13 of 54 new or added lines in 5 files covered. (24.07%)

1051 existing lines in 43 files now uncovered.

309149 of 426542 relevant lines covered (72.48%)

1254687.42 hits per line

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

82.82
/src/core/path.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <sys/inotify.h>
4
#include <unistd.h>
5

6
#include "sd-bus.h"
7

8
#include "async.h"
9
#include "bus-error.h"
10
#include "dbus-path.h"
11
#include "dbus-unit.h"
12
#include "errno-util.h"
13
#include "escape.h"
14
#include "event-util.h"
15
#include "glob-util.h"
16
#include "inotify-util.h"
17
#include "manager.h"
18
#include "mkdir-label.h"
19
#include "path.h"
20
#include "path-util.h"
21
#include "serialize.h"
22
#include "special.h"
23
#include "stat-util.h"
24
#include "string-table.h"
25
#include "string-util.h"
26
#include "strv.h"
27
#include "unit.h"
28

29
static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = {
30
        [PATH_DEAD]    = UNIT_INACTIVE,
31
        [PATH_WAITING] = UNIT_ACTIVE,
32
        [PATH_RUNNING] = UNIT_ACTIVE,
33
        [PATH_FAILED]  = UNIT_FAILED,
34
};
35

36
static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata);
37

38
int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) {
162✔
39
        static const int flags_table[_PATH_TYPE_MAX] = {
162✔
40
                [PATH_EXISTS]              = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
41
                [PATH_EXISTS_GLOB]         = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
42
                [PATH_CHANGED]             = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO,
43
                [PATH_MODIFIED]            = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO|IN_MODIFY,
44
                [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO,
45
        };
46

47
        bool exists = false;
162✔
48
        char *slash, *oldslash = NULL;
162✔
49
        int r;
162✔
50

51
        assert(s);
162✔
52
        assert(s->unit);
162✔
53
        assert(handler);
162✔
54

55
        path_spec_unwatch(s);
162✔
56

57
        s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
162✔
58
        if (s->inotify_fd < 0) {
162✔
59
                r = log_error_errno(errno, "Failed to allocate inotify fd: %m");
×
60
                goto fail;
×
61
        }
62

63
        r = sd_event_add_io(s->unit->manager->event, &s->event_source, s->inotify_fd, EPOLLIN, handler, s);
162✔
64
        if (r < 0) {
162✔
65
                log_error_errno(r, "Failed to add inotify fd to event loop: %m");
×
66
                goto fail;
×
67
        }
68

69
        (void) sd_event_source_set_description(s->event_source, "path");
162✔
70

71
        /* This function assumes the path was passed through path_simplify()! */
72
        assert(!strstr(s->path, "//"));
162✔
73

74
        for (slash = strchr(s->path, '/'); ; slash = strchr(slash+1, '/')) {
162✔
75
                bool incomplete = false;
633✔
76
                int flags, wd = -1;
633✔
77
                char tmp, *cut;
633✔
78

79
                if (slash) {
633✔
80
                        cut = slash + (slash == s->path);
471✔
81
                        tmp = *cut;
471✔
82
                        *cut = '\0';
471✔
83

84
                        flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_CREATE | IN_MOVED_TO;
471✔
85
                } else {
86
                        cut = NULL;
162✔
87
                        flags = flags_table[s->type];
162✔
88
                }
89

90
                /* If this is a symlink watch both the symlink inode and where it points to. If the inode is
91
                 * not a symlink both calls will install the same watch, which is redundant and doesn't
92
                 * hurt. */
93
                for (int follow_symlink = 0; follow_symlink < 2; follow_symlink++) {
1,879✔
94
                        uint32_t f = flags;
1,256✔
95

96
                        SET_FLAG(f, IN_DONT_FOLLOW, !follow_symlink);
1,256✔
97

98
                        wd = inotify_add_watch(s->inotify_fd, s->path, f);
1,256✔
99
                        if (wd < 0) {
1,256✔
100
                                if (IN_SET(errno, EACCES, ENOENT)) {
10✔
101
                                        incomplete = true; /* This is an expected error, let's accept this
102
                                                            * quietly: we have an incomplete watch for
103
                                                            * now. */
104
                                        break;
105
                                }
106

107
                                /* This second call to inotify_add_watch() should fail like the previous one
108
                                 * and is done for logging the error in a comprehensive way. */
109
                                wd = inotify_add_watch_and_warn(s->inotify_fd, s->path, f);
×
110
                                if (wd < 0) {
×
111
                                        if (cut)
×
112
                                                *cut = tmp;
×
113

114
                                        r = wd;
×
115
                                        goto fail;
×
116
                                }
117

118
                                /* Hmm, we succeeded in adding the watch this time... let's continue. */
119
                        }
120
                }
121

122
                if (incomplete) {
633✔
123
                        if (cut)
10✔
124
                                *cut = tmp;
×
125

126
                        break;
127
                }
128

129
                exists = true;
623✔
130

131
                /* Path exists, we don't need to watch parent too closely. */
132
                if (oldslash) {
623✔
133
                        char *cut2 = oldslash + (oldslash == s->path);
461✔
134
                        char tmp2 = *cut2;
461✔
135
                        *cut2 = '\0';
461✔
136

137
                        (void) inotify_add_watch(s->inotify_fd, s->path, IN_MOVE_SELF);
461✔
138
                        /* Error is ignored, the worst can happen is we get spurious events. */
139

140
                        *cut2 = tmp2;
461✔
141
                }
142

143
                if (cut)
623✔
144
                        *cut = tmp;
471✔
145

146
                if (slash)
623✔
147
                        oldslash = slash;
471✔
148
                else {
149
                        /* whole path has been iterated over */
150
                        s->primary_wd = wd;
152✔
151
                        break;
152✔
152
                }
153
        }
154

155
        if (!exists) {
162✔
156
                r = log_error_errno(errno, "Failed to add watch on any of the components of %s: %m", s->path);
×
157
                /* either EACCESS or ENOENT */
158
                goto fail;
×
159
        }
160

161
        return 0;
162

163
fail:
×
164
        path_spec_unwatch(s);
×
165
        return r;
×
166
}
167

168
void path_spec_unwatch(PathSpec *s) {
465✔
169
        assert(s);
465✔
170

171
        s->event_source = sd_event_source_disable_unref(s->event_source);
465✔
172
        s->inotify_fd = asynchronous_close(s->inotify_fd);
465✔
173
}
465✔
174

175
int path_spec_fd_event(PathSpec *s, uint32_t revents) {
11✔
176
        union inotify_event_buffer buffer;
11✔
177
        ssize_t l;
11✔
178

179
        assert(s);
11✔
180

181
        if (revents != EPOLLIN)
11✔
182
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
183
                                       "Got invalid poll event on inotify.");
184

185
        l = read(s->inotify_fd, &buffer, sizeof(buffer));
11✔
186
        if (l < 0) {
11✔
187
                if (ERRNO_IS_TRANSIENT(errno))
×
188
                        return 0;
189

190
                return log_error_errno(errno, "Failed to read inotify event: %m");
×
191
        }
192

193
        if (IN_SET(s->type, PATH_CHANGED, PATH_MODIFIED))
11✔
194
                FOREACH_INOTIFY_EVENT_WARN(e, buffer, l)
6✔
195
                        if (s->primary_wd == e->wd)
4✔
196
                                return 1;
2✔
197

198
        return 0;
199
}
200

201
static bool path_spec_check_good(PathSpec *s, bool initial, bool from_trigger_notify, char **ret_trigger_path) {
337✔
202
        _cleanup_free_ char *trigger = NULL;
337✔
203
        bool b, good = false;
337✔
204

205
        assert(s);
337✔
206
        assert(ret_trigger_path);
337✔
207

208
        switch (s->type) {
337✔
209

210
        case PATH_EXISTS:
13✔
211
                good = access(s->path, F_OK) >= 0;
13✔
212
                break;
13✔
213

214
        case PATH_EXISTS_GLOB:
6✔
215
                good = glob_first(s->path, &trigger) > 0;
6✔
216
                break;
6✔
217

218
        case PATH_DIRECTORY_NOT_EMPTY: {
303✔
219
                int k;
303✔
220

221
                k = dir_is_empty(s->path, /* ignore_hidden_or_backup= */ true);
303✔
222
                good = !(IN_SET(k, -ENOENT, -ENOTDIR) || k > 0);
303✔
223
                break;
224
        }
225

226
        case PATH_CHANGED:
15✔
227
        case PATH_MODIFIED:
228
                b = access(s->path, F_OK) >= 0;
15✔
229
                good = !initial && !from_trigger_notify && b != s->previous_exists;
15✔
230
                s->previous_exists = b;
15✔
231
                break;
15✔
232

233
        default:
19✔
234
                ;
34✔
235
        }
236

237
        if (good) {
34✔
238
                if (!trigger) {
11✔
239
                        trigger = strdup(s->path);
9✔
240
                        if (!trigger)
9✔
241
                                (void) log_oom_debug();
×
242
                }
243
                *ret_trigger_path = TAKE_PTR(trigger);
11✔
244
        }
245

246
        return good;
337✔
247
}
248

249
static void path_spec_mkdir(PathSpec *s, mode_t mode) {
62✔
250
        int r;
62✔
251

252
        assert(s);
62✔
253

254
        if (IN_SET(s->type, PATH_EXISTS, PATH_EXISTS_GLOB))
62✔
255
                return;
256

257
        r = mkdir_p_label(s->path, mode);
62✔
258
        if (r < 0)
62✔
259
                log_warning_errno(r, "mkdir(%s) failed: %m", s->path);
×
260
}
261

262
static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) {
2✔
263
        const char *type;
2✔
264

265
        assert(s);
2✔
266
        assert(f);
2✔
267
        assert(prefix);
2✔
268

269
        assert_se(type = path_type_to_string(s->type));
2✔
270
        fprintf(f, "%s%s: %s\n", prefix, type, s->path);
2✔
271
}
2✔
272

273
void path_spec_done(PathSpec *s) {
233✔
274
        assert(s);
233✔
275
        assert(s->inotify_fd == -EBADF);
233✔
276

277
        free(s->path);
233✔
278
}
233✔
279

280
static void path_init(Unit *u) {
231✔
281
        Path *p = ASSERT_PTR(PATH(u));
231✔
282

283
        assert(u->load_state == UNIT_STUB);
231✔
284

285
        p->directory_mode = 0755;
231✔
286

287
        p->trigger_limit = RATELIMIT_OFF;
231✔
288
}
231✔
289

290
void path_free_specs(Path *p) {
231✔
291
        PathSpec *s;
231✔
292

293
        assert(p);
231✔
294

295
        while ((s = LIST_POP(spec, p->specs))) {
464✔
296
                path_spec_unwatch(s);
233✔
297
                path_spec_done(s);
233✔
298
                free(s);
233✔
299
        }
300
}
231✔
301

302
static void path_done(Unit *u) {
231✔
303
        Path *p = ASSERT_PTR(PATH(u));
231✔
304

305
        p->trigger_notify_event_source = sd_event_source_disable_unref(p->trigger_notify_event_source);
231✔
306
        path_free_specs(p);
231✔
307
}
231✔
308

309
static int path_add_mount_dependencies(Path *p) {
229✔
310
        int r;
229✔
311

312
        assert(p);
229✔
313

314
        LIST_FOREACH(spec, s, p->specs) {
462✔
315
                r = unit_add_mounts_for(UNIT(p), s->path, UNIT_DEPENDENCY_FILE, UNIT_MOUNT_REQUIRES);
233✔
316
                if (r < 0)
233✔
317
                        return r;
318
        }
319

320
        return 0;
321
}
322

323
static int path_verify(Path *p) {
229✔
324
        assert(p);
229✔
325
        assert(UNIT(p)->load_state == UNIT_LOADED);
229✔
326

327
        if (!p->specs)
229✔
328
                return log_unit_error_errno(UNIT(p), SYNTHETIC_ERRNO(ENOEXEC), "Path unit lacks path setting. Refusing.");
×
329

330
        return 0;
331
}
332

333
static int path_add_default_dependencies(Path *p) {
229✔
334
        int r;
229✔
335

336
        assert(p);
229✔
337

338
        if (!UNIT(p)->default_dependencies)
229✔
339
                return 0;
340

341
        r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_PATHS_TARGET, true, UNIT_DEPENDENCY_DEFAULT);
9✔
342
        if (r < 0)
9✔
343
                return r;
344

345
        if (MANAGER_IS_SYSTEM(UNIT(p)->manager)) {
9✔
346
                r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, true, UNIT_DEPENDENCY_DEFAULT);
2✔
347
                if (r < 0)
2✔
348
                        return r;
349
        }
350

351
        return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, true, UNIT_DEPENDENCY_DEFAULT);
9✔
352
}
353

354
static int path_add_trigger_dependencies(Path *p) {
229✔
355
        Unit *x;
229✔
356
        int r;
229✔
357

358
        assert(p);
229✔
359

360
        if (UNIT_TRIGGER(UNIT(p)))
229✔
361
                return 0;
229✔
362

363
        r = unit_load_related_unit(UNIT(p), ".service", &x);
228✔
364
        if (r < 0)
228✔
365
                return r;
366

367
        return unit_add_two_dependencies(UNIT(p), UNIT_BEFORE, UNIT_TRIGGERS, x, true, UNIT_DEPENDENCY_IMPLICIT);
228✔
368
}
369

370
static int path_add_extras(Path *p) {
229✔
371
        int r;
229✔
372

373
        assert(p);
229✔
374

375
        /* To avoid getting pid1 in a busy-loop state (eg: unmet condition on associated service),
376
         * set a default trigger limit if the user didn't specify any. */
377
        if (p->trigger_limit.interval == USEC_INFINITY)
229✔
378
                p->trigger_limit.interval = 2 * USEC_PER_SEC;
229✔
379

380
        if (p->trigger_limit.burst == UINT_MAX)
229✔
381
                p->trigger_limit.burst = 200;
229✔
382

383
        r = path_add_trigger_dependencies(p);
229✔
384
        if (r < 0)
229✔
385
                return r;
386

387
        r = path_add_mount_dependencies(p);
229✔
388
        if (r < 0)
229✔
389
                return r;
390

391
        return path_add_default_dependencies(p);
229✔
392
}
393

394
static int path_load(Unit *u) {
232✔
395
        Path *p = ASSERT_PTR(PATH(u));
232✔
396
        int r;
232✔
397

398
        assert(u->load_state == UNIT_STUB);
232✔
399

400
        r = unit_load_fragment_and_dropin(u, true);
232✔
401
        if (r < 0)
232✔
402
                return r;
403

404
        if (u->load_state != UNIT_LOADED)
229✔
405
                return 0;
406

407
        r = path_add_extras(p);
229✔
408
        if (r < 0)
229✔
409
                return r;
410

411
        return path_verify(p);
229✔
412
}
413

414
static void path_dump(Unit *u, FILE *f, const char *prefix) {
2✔
415
        Path *p = ASSERT_PTR(PATH(u));
2✔
416
        Unit *trigger;
2✔
417

418
        assert(f);
2✔
419
        assert(prefix);
2✔
420

421
        trigger = UNIT_TRIGGER(u);
2✔
422

423
        fprintf(f,
2✔
424
                "%sPath State: %s\n"
425
                "%sResult: %s\n"
426
                "%sUnit: %s\n"
427
                "%sMakeDirectory: %s\n"
428
                "%sDirectoryMode: %04o\n"
429
                "%sTriggerLimitIntervalSec: %s\n"
430
                "%sTriggerLimitBurst: %u\n",
431
                prefix, path_state_to_string(p->state),
432
                prefix, path_result_to_string(p->result),
433
                prefix, trigger ? trigger->id : "n/a",
434
                prefix, yes_no(p->make_directory),
2✔
435
                prefix, p->directory_mode,
436
                prefix, FORMAT_TIMESPAN(p->trigger_limit.interval, USEC_PER_SEC),
2✔
437
                prefix, p->trigger_limit.burst);
438

439
        LIST_FOREACH(spec, s, p->specs)
4✔
440
                path_spec_dump(s, f, prefix);
2✔
441
}
2✔
442

443
static void path_unwatch(Path *p) {
66✔
444
        assert(p);
66✔
445

446
        LIST_FOREACH(spec, s, p->specs)
136✔
447
                path_spec_unwatch(s);
70✔
448
}
66✔
449

450
static int path_watch(Path *p) {
162✔
451
        int r;
162✔
452

453
        assert(p);
162✔
454

455
        LIST_FOREACH(spec, s, p->specs) {
324✔
456
                r = path_spec_watch(s, path_dispatch_io);
162✔
457
                if (r < 0)
162✔
458
                        return r;
459
        }
460

461
        return 0;
462
}
463

464
static void path_set_state(Path *p, PathState state) {
228✔
465
        PathState old_state;
228✔
466

467
        assert(p);
228✔
468

469
        if (p->state != state)
228✔
470
                bus_unit_send_pending_change_signal(UNIT(p), false);
223✔
471

472
        old_state = p->state;
228✔
473
        p->state = state;
228✔
474

475
        if (!IN_SET(state, PATH_WAITING, PATH_RUNNING))
228✔
476
                path_unwatch(p);
47✔
477

478
        if (state != old_state)
228✔
479
                log_unit_debug(UNIT(p), "Changed %s -> %s", path_state_to_string(old_state), path_state_to_string(state));
223✔
480

481
        unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], /* reload_success= */ true);
228✔
482
}
228✔
483

484
static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify);
485

486
static int path_coldplug(Unit *u) {
162✔
487
        Path *p = ASSERT_PTR(PATH(u));
162✔
488

489
        assert(p->state == PATH_DEAD);
162✔
490

491
        if (p->deserialized_state != p->state) {
162✔
492

493
                if (IN_SET(p->deserialized_state, PATH_WAITING, PATH_RUNNING))
90✔
494
                        path_enter_waiting(p, true, false);
90✔
495
                else
496
                        path_set_state(p, p->deserialized_state);
×
497
        }
498

499
        return 0;
162✔
500
}
501

502
static void path_enter_dead(Path *p, PathResult f) {
47✔
503
        assert(p);
47✔
504

505
        if (p->result == PATH_SUCCESS || IN_SET(f, PATH_FAILURE_START_LIMIT_HIT, PATH_FAILURE_UNIT_START_LIMIT_HIT))
47✔
506
                p->result = f;
47✔
507

508
        unit_log_result(UNIT(p), p->result == PATH_SUCCESS, path_result_to_string(p->result));
47✔
509
        path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD);
94✔
510
}
47✔
511

512
static void path_enter_running(Path *p, char *trigger_path) {
13✔
513
        _cleanup_(activation_details_unrefp) ActivationDetails *details = NULL;
13✔
514
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
13✔
515
        Unit *trigger;
13✔
516
        Job *job;
13✔
517
        int r;
13✔
518

519
        assert(p);
13✔
520

521
        /* Don't start job if we are supposed to go down */
522
        if (unit_stop_pending(UNIT(p)))
13✔
523
                return;
524

525
        if (!ratelimit_below(&p->trigger_limit)) {
13✔
526
                log_unit_warning(UNIT(p), "Trigger limit hit, refusing further activation.");
×
527
                path_enter_dead(p, PATH_FAILURE_TRIGGER_LIMIT_HIT);
×
528
                return;
529
        }
530

531
        trigger = UNIT_TRIGGER(UNIT(p));
13✔
532
        if (!trigger) {
13✔
533
                log_unit_error(UNIT(p), "Unit to trigger vanished.");
×
534
                goto fail;
×
535
        }
536

537
        details = activation_details_new(UNIT(p));
13✔
538
        if (!details) {
13✔
539
                log_oom();
×
540
                goto fail;
×
541
        }
542

543
        r = free_and_strdup(&(ACTIVATION_DETAILS_PATH(details))->trigger_path_filename, trigger_path);
13✔
544
        if (r < 0) {
13✔
545
                log_oom();
×
546
                goto fail;
×
547
        }
548

549
        r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, &error, &job);
13✔
550
        if (r < 0) {
13✔
551
                log_unit_warning(UNIT(p), "Failed to queue unit startup job: %s", bus_error_message(&error, r));
×
552
                goto fail;
×
553
        }
554

555
        job_set_activation_details(job, details);
13✔
556

557
        path_set_state(p, PATH_RUNNING);
13✔
558
        path_unwatch(p);
13✔
559

560
        return;
561

562
fail:
×
563
        path_enter_dead(p, PATH_FAILURE_RESOURCES);
×
564
}
565

566
static bool path_check_good(Path *p, bool initial, bool from_trigger_notify, char **ret_trigger_path) {
335✔
567
        assert(p);
335✔
568
        assert(ret_trigger_path);
335✔
569

570
        LIST_FOREACH(spec, s, p->specs)
661✔
571
                if (path_spec_check_good(s, initial, from_trigger_notify, ret_trigger_path))
337✔
572
                        return true;
573

574
        return false;
575
}
576

577
static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify) {
179✔
578
        _cleanup_free_ char *trigger_path = NULL;
179✔
579
        Unit *trigger;
179✔
580
        int r;
179✔
581

582
        if (p->trigger_notify_event_source)
179✔
583
                (void) event_source_disable(p->trigger_notify_event_source);
11✔
584

585
        /* If the triggered unit is already running, so are we */
586
        trigger = UNIT_TRIGGER(UNIT(p));
179✔
587
        if (trigger && !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(trigger))) {
179✔
588
                path_set_state(p, PATH_RUNNING);
6✔
589
                path_unwatch(p);
6✔
590
                return;
591
        }
592

593
        if (path_check_good(p, initial, from_trigger_notify, &trigger_path)) {
173✔
594
                log_unit_debug(UNIT(p), "Got triggered by '%s'.", trigger_path);
11✔
595
                path_enter_running(p, trigger_path);
11✔
596
                return;
597
        }
598

599
        r = path_watch(p);
162✔
600
        if (r < 0) {
162✔
601
                log_unit_warning_errno(UNIT(p), r, "Failed to enter waiting state: %m");
×
602
                path_enter_dead(p, PATH_FAILURE_RESOURCES);
×
603
                return;
604
        }
605

606
        /* Hmm, so now we have created inotify watches, but the file
607
         * might have appeared/been removed by now, so we must
608
         * recheck */
609

610
        if (path_check_good(p, false, from_trigger_notify, &trigger_path)) {
162✔
611
                log_unit_debug(UNIT(p), "Got triggered by '%s'.", trigger_path);
×
612
                path_enter_running(p, trigger_path);
×
613
                return;
614
        }
615

616
        path_set_state(p, PATH_WAITING);
162✔
617
}
618

619
static void path_mkdir(Path *p) {
69✔
620
        assert(p);
69✔
621

622
        if (!p->make_directory)
69✔
623
                return;
624

625
        LIST_FOREACH(spec, s, p->specs)
124✔
626
                path_spec_mkdir(s, p->directory_mode);
62✔
627
}
628

629
static int path_start(Unit *u) {
69✔
630
        Path *p = ASSERT_PTR(PATH(u));
69✔
631
        int r;
69✔
632

633
        assert(IN_SET(p->state, PATH_DEAD, PATH_FAILED));
69✔
634

635
        r = unit_test_trigger_loaded(u);
69✔
636
        if (r < 0)
69✔
637
                return r;
638

639
        r = unit_acquire_invocation_id(u);
69✔
640
        if (r < 0)
69✔
641
                return r;
642

643
        path_mkdir(p);
69✔
644

645
        p->result = PATH_SUCCESS;
69✔
646
        path_enter_waiting(p, true, false);
69✔
647

648
        return 1;
69✔
649
}
650

651
static int path_stop(Unit *u) {
47✔
652
        Path *p = ASSERT_PTR(PATH(u));
47✔
653

654
        assert(IN_SET(p->state, PATH_WAITING, PATH_RUNNING));
47✔
655

656
        path_enter_dead(p, PATH_SUCCESS);
47✔
657
        return 1;
47✔
658
}
659

660
static int path_serialize(Unit *u, FILE *f, FDSet *fds) {
134✔
661
        Path *p = ASSERT_PTR(PATH(u));
134✔
662

663
        assert(f);
134✔
664
        assert(fds);
134✔
665

666
        (void) serialize_item(f, "state", path_state_to_string(p->state));
134✔
667
        (void) serialize_item(f, "result", path_result_to_string(p->result));
134✔
668

669
        LIST_FOREACH(spec, s, p->specs) {
268✔
670
                const char *type;
134✔
671
                _cleanup_free_ char *escaped = NULL;
134✔
672

673
                escaped = cescape(s->path);
134✔
674
                if (!escaped)
134✔
675
                        return log_oom();
×
676

677
                assert_se(type = path_type_to_string(s->type));
134✔
678
                (void) serialize_item_format(f, "path-spec", "%s %i %s",
134✔
679
                                             type,
680
                                             s->previous_exists,
134✔
681
                                             escaped);
682
        }
683

684
        (void) serialize_ratelimit(f, "trigger-ratelimit", &p->trigger_limit);
134✔
685

686
        return 0;
134✔
687
}
688

689
static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
384✔
690
        Path *p = ASSERT_PTR(PATH(u));
384✔
691

692
        assert(key);
384✔
693
        assert(value);
384✔
694
        assert(fds);
384✔
695

696
        if (streq(key, "state")) {
384✔
697
                PathState state;
96✔
698

699
                state = path_state_from_string(value);
96✔
700
                if (state < 0)
96✔
701
                        log_unit_debug(u, "Failed to parse state value: %s", value);
×
702
                else
703
                        p->deserialized_state = state;
96✔
704

705
        } else if (streq(key, "result")) {
288✔
706
                PathResult f;
96✔
707

708
                f = path_result_from_string(value);
96✔
709
                if (f < 0)
96✔
710
                        log_unit_debug(u, "Failed to parse result value: %s", value);
×
711
                else if (f != PATH_SUCCESS)
96✔
712
                        p->result = f;
×
713

714
        } else if (streq(key, "path-spec")) {
192✔
715
                int previous_exists, skip = 0;
96✔
716
                _cleanup_free_ char *type_str = NULL;
96✔
717

718
                if (sscanf(value, "%ms %i %n", &type_str, &previous_exists, &skip) < 2)
96✔
719
                        log_unit_debug(u, "Failed to parse path-spec value: %s", value);
×
720
                else {
721
                        _cleanup_free_ char *unescaped = NULL;
96✔
722
                        ssize_t l;
96✔
723
                        PathType type;
96✔
724

725
                        type = path_type_from_string(type_str);
96✔
726
                        if (type < 0) {
96✔
727
                                log_unit_warning(u, "Unknown path type \"%s\", ignoring.", type_str);
×
728
                                return 0;
×
729
                        }
730

731
                        l = cunescape(value+skip, 0, &unescaped);
96✔
732
                        if (l < 0) {
96✔
733
                                log_unit_warning_errno(u, l, "Failed to unescape serialize path: %m");
×
734
                                return 0;
×
735
                        }
736

737
                        LIST_FOREACH(spec, s, p->specs)
96✔
738
                                if (s->type == type &&
96✔
739
                                    path_equal(s->path, unescaped)) {
96✔
740

741
                                        s->previous_exists = previous_exists;
96✔
742
                                        break;
96✔
743
                                }
744
                }
745

746
        } else if (streq(key, "trigger-ratelimit"))
96✔
747
                deserialize_ratelimit(&p->trigger_limit, key, value);
96✔
748

749
        else
750
                log_unit_debug(u, "Unknown serialization key: %s", key);
×
751

752
        return 0;
753
}
754

755
static UnitActiveState path_active_state(Unit *u) {
5,207✔
756
        Path *p = ASSERT_PTR(PATH(u));
5,207✔
757

758
        return state_translation_table[p->state];
5,207✔
759
}
760

761
static const char *path_sub_state_to_string(Unit *u) {
126✔
762
        Path *p = ASSERT_PTR(PATH(u));
126✔
763

764
        return path_state_to_string(p->state);
126✔
765
}
766

767
static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
11✔
768
        PathSpec *s = ASSERT_PTR(userdata), *found = NULL;
11✔
769
        Path *p = ASSERT_PTR(PATH(s->unit));
11✔
770
        int changed;
11✔
771

772
        assert(fd >= 0);
11✔
773

774
        if (!IN_SET(p->state, PATH_WAITING, PATH_RUNNING))
11✔
775
                return 0;
776

777
        LIST_FOREACH(spec, i, p->specs)
11✔
778
                if (path_spec_owns_inotify_fd(i, fd)) {
11✔
779
                        found = i;
780
                        break;
781
                }
782

783
        if (!found) {
11✔
784
                log_error("Got event on unknown fd.");
×
785
                goto fail;
×
786
        }
787

788
        changed = path_spec_fd_event(found, revents);
11✔
789
        if (changed < 0)
11✔
790
                goto fail;
×
791

792
        if (changed)
11✔
793
                path_enter_running(p, found->path);
2✔
794
        else
795
                path_enter_waiting(p, false, false);
9✔
796

797
        return 0;
798

799
fail:
×
800
        path_enter_dead(p, PATH_FAILURE_RESOURCES);
×
801
        return 0;
×
802
}
803

804
static void path_trigger_notify_impl(Unit *u, Unit *other, bool on_defer);
805

806
static int path_trigger_notify_on_defer(sd_event_source *s, void *userdata) {
11✔
807
        Path *p = ASSERT_PTR(userdata);
11✔
808
        Unit *trigger;
11✔
809

810
        assert(s);
11✔
811

812
        trigger = UNIT_TRIGGER(UNIT(p));
11✔
813
        if (!trigger) {
11✔
814
                log_unit_error(UNIT(p), "Unit to trigger vanished.");
×
815
                path_enter_dead(p, PATH_FAILURE_RESOURCES);
×
816
                return 0;
×
817
        }
818

819
        path_trigger_notify_impl(UNIT(p), trigger, /* on_defer= */ true);
11✔
820
        return 0;
11✔
821
}
822

823
static void path_trigger_notify_impl(Unit *u, Unit *other, bool on_defer) {
78✔
824
        Path *p = ASSERT_PTR(PATH(u));
78✔
825
        int r;
78✔
826

827
        assert(other);
78✔
828

829
        /* Invoked whenever the unit we trigger changes state or gains or loses a job */
830

831
        /* Filter out invocations with bogus state */
832
        assert(UNIT_IS_LOAD_COMPLETE(other->load_state));
78✔
833

834
        /* Don't propagate state changes from the triggered unit if we are already down */
835
        if (!IN_SET(p->state, PATH_WAITING, PATH_RUNNING))
78✔
836
                return;
837

838
        /* Propagate start limit hit state */
839
        if (other->start_limit_hit) {
72✔
840
                path_enter_dead(p, PATH_FAILURE_UNIT_START_LIMIT_HIT);
×
841
                return;
×
842
        }
843

844
        /* Don't propagate anything if there's still a job queued */
845
        if (other->job)
72✔
846
                return;
847

848
        if (p->state == PATH_RUNNING &&
58✔
849
            UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
55✔
850
                if (!on_defer)
28✔
851
                        log_unit_debug(u, "Got notified about unit deactivation.");
17✔
852
        } else if (p->state == PATH_WAITING &&
30✔
853
                   !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
3✔
UNCOV
854
                if (!on_defer)
×
UNCOV
855
                        log_unit_debug(u, "Got notified about unit activation.");
×
856
        } else
857
                return;
30✔
858

859
        if (on_defer) {
17✔
860
                path_enter_waiting(p, /* initial= */ false, /* from_trigger_notify= */ true);
11✔
861
                return;
11✔
862
        }
863

864
        /* Do not call path_enter_waiting() directly from path_trigger_notify(), as this may be called by
865
         * job_install() -> job_finish_and_invalidate() -> unit_trigger_notify(), and path_enter_waiting()
866
         * may install another job and will trigger assertion in job_install().
867
         * https://github.com/systemd/systemd/issues/24577#issuecomment-1522628906
868
         * Hence, first setup defer event source here, and call path_enter_waiting() slightly later. */
869
        if (p->trigger_notify_event_source) {
17✔
870
                r = sd_event_source_set_enabled(p->trigger_notify_event_source, SD_EVENT_ONESHOT);
11✔
871
                if (r < 0) {
11✔
872
                        log_unit_warning_errno(u, r, "Failed to enable event source for triggering notify: %m");
×
873
                        path_enter_dead(p, PATH_FAILURE_RESOURCES);
×
874
                        return;
×
875
                }
876
        } else {
877
                r = sd_event_add_defer(u->manager->event, &p->trigger_notify_event_source, path_trigger_notify_on_defer, p);
6✔
878
                if (r < 0) {
6✔
879
                        log_unit_warning_errno(u, r, "Failed to allocate event source for triggering notify: %m");
×
880
                        path_enter_dead(p, PATH_FAILURE_RESOURCES);
×
881
                        return;
×
882
                }
883

884
                (void) sd_event_source_set_description(p->trigger_notify_event_source, "path-trigger-notify");
6✔
885
        }
886
}
887

888
static void path_trigger_notify(Unit *u, Unit *other) {
67✔
889
        path_trigger_notify_impl(u, other, /* on_defer= */ false);
67✔
890
}
67✔
891

892
static void path_reset_failed(Unit *u) {
2✔
893
        Path *p = ASSERT_PTR(PATH(u));
2✔
894

895
        if (p->state == PATH_FAILED)
2✔
896
                path_set_state(p, PATH_DEAD);
×
897

898
        p->result = PATH_SUCCESS;
2✔
899
}
2✔
900

901
static int path_test_startable(Unit *u) {
69✔
902
        Path *p = ASSERT_PTR(PATH(u));
69✔
903
        int r;
69✔
904

905
        r = unit_test_start_limit(u);
69✔
906
        if (r < 0) {
69✔
907
                path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT);
×
908
                return r;
×
909
        }
910

911
        return true;
912
}
913

914
static void activation_details_path_done(ActivationDetails *details) {
13✔
915
        ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
13✔
916

917
        p->trigger_path_filename = mfree(p->trigger_path_filename);
13✔
918
}
13✔
919

920
static void activation_details_path_serialize(const ActivationDetails *details, FILE *f) {
×
921
        const ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
×
922

923
        assert(f);
×
924

925
        if (p->trigger_path_filename)
×
926
                (void) serialize_item(f, "activation-details-path-filename", p->trigger_path_filename);
×
927
}
×
928

929
static int activation_details_path_deserialize(const char *key, const char *value, ActivationDetails **details) {
×
930
        int r;
×
931

932
        assert(key);
×
933
        assert(value);
×
934

935
        if (!details || !*details)
×
936
                return -EINVAL;
937

938
        ActivationDetailsPath *p = ACTIVATION_DETAILS_PATH(*details);
×
939
        if (!p)
×
940
                return -EINVAL;
941

942
        if (!streq(key, "activation-details-path-filename"))
×
943
                return -EINVAL;
944

945
        r = free_and_strdup(&p->trigger_path_filename, value);
×
946
        if (r < 0)
×
947
                return r;
×
948

949
        return 0;
950
}
951

952
static int activation_details_path_append_env(const ActivationDetails *details, char ***strv) {
13✔
953
        const ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
13✔
954
        char *s;
13✔
955
        int r;
13✔
956

957
        assert(strv);
13✔
958

959
        if (isempty(p->trigger_path_filename))
13✔
960
                return 0;
13✔
961

962
        s = strjoin("TRIGGER_PATH=", p->trigger_path_filename);
13✔
963
        if (!s)
13✔
964
                return -ENOMEM;
965

966
        r = strv_consume(strv, TAKE_PTR(s));
13✔
967
        if (r < 0)
13✔
968
                return r;
×
969

970
        return 1; /* Return the number of variables added to the env block */
971
}
972

973
static int activation_details_path_append_pair(const ActivationDetails *details, char ***strv) {
18✔
974
        const ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
18✔
975
        int r;
18✔
976

977
        assert(strv);
18✔
978

979
        if (isempty(p->trigger_path_filename))
18✔
980
                return 0;
981

982
        r = strv_extend_many(strv, "trigger_path", p->trigger_path_filename);
18✔
983
        if (r < 0)
18✔
984
                return r;
×
985

986
        return 1; /* Return the number of pairs added to the env block */
987
}
988

989
static const char* const path_type_table[_PATH_TYPE_MAX] = {
990
        [PATH_EXISTS]              = "PathExists",
991
        [PATH_EXISTS_GLOB]         = "PathExistsGlob",
992
        [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty",
993
        [PATH_CHANGED]             = "PathChanged",
994
        [PATH_MODIFIED]            = "PathModified",
995
};
996

997
DEFINE_STRING_TABLE_LOOKUP(path_type, PathType);
528✔
998

999
static const char* const path_result_table[_PATH_RESULT_MAX] = {
1000
        [PATH_SUCCESS]                      = "success",
1001
        [PATH_FAILURE_RESOURCES]            = "resources",
1002
        [PATH_FAILURE_START_LIMIT_HIT]      = "start-limit-hit",
1003
        [PATH_FAILURE_UNIT_START_LIMIT_HIT] = "unit-start-limit-hit",
1004
        [PATH_FAILURE_TRIGGER_LIMIT_HIT]    = "trigger-limit-hit",
1005
};
1006

1007
DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult);
571✔
1008

1009
const UnitVTable path_vtable = {
1010
        .object_size = sizeof(Path),
1011

1012
        .sections =
1013
                "Unit\0"
1014
                "Path\0"
1015
                "Install\0",
1016
        .private_section = "Path",
1017

1018
        .can_transient = true,
1019
        .can_fail = true,
1020
        .can_trigger = true,
1021

1022
        .init = path_init,
1023
        .done = path_done,
1024
        .load = path_load,
1025

1026
        .coldplug = path_coldplug,
1027

1028
        .dump = path_dump,
1029

1030
        .start = path_start,
1031
        .stop = path_stop,
1032

1033
        .serialize = path_serialize,
1034
        .deserialize_item = path_deserialize_item,
1035

1036
        .active_state = path_active_state,
1037
        .sub_state_to_string = path_sub_state_to_string,
1038

1039
        .trigger_notify = path_trigger_notify,
1040

1041
        .reset_failed = path_reset_failed,
1042

1043
        .bus_set_property = bus_path_set_property,
1044

1045
        .test_startable = path_test_startable,
1046
};
1047

1048
const ActivationDetailsVTable activation_details_path_vtable = {
1049
        .object_size = sizeof(ActivationDetailsPath),
1050

1051
        .done = activation_details_path_done,
1052
        .serialize = activation_details_path_serialize,
1053
        .deserialize = activation_details_path_deserialize,
1054
        .append_env = activation_details_path_append_env,
1055
        .append_pair = activation_details_path_append_pair,
1056
};
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