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

systemd / systemd / 23990547145

04 Apr 2026 09:30PM UTC coverage: 72.373% (+0.3%) from 72.107%
23990547145

push

github

web-flow
shutdown: enforce a minimum uptime to make boot loops less annoying (#41215)

Fixes: #9453

Split out of #41016

3 of 39 new or added lines in 2 files covered. (7.69%)

2565 existing lines in 66 files now uncovered.

319531 of 441505 relevant lines covered (72.37%)

1187721.39 hits per line

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

82.67
/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) {
199✔
39
        static const int flags_table[_PATH_TYPE_MAX] = {
199✔
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;
199✔
48
        char *slash, *oldslash = NULL;
199✔
49
        int r;
199✔
50

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

55
        path_spec_unwatch(s);
199✔
56

57
        s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
199✔
58
        if (s->inotify_fd < 0) {
199✔
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);
199✔
64
        if (r < 0) {
199✔
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");
199✔
70

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

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

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

84
                        flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_CREATE | IN_MOVED_TO;
582✔
85
                } else {
86
                        cut = NULL;
199✔
87
                        flags = flags_table[s->type];
199✔
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++) {
2,323✔
94
                        uint32_t f = flags;
1,552✔
95

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

98
                        wd = inotify_add_watch(s->inotify_fd, s->path, f);
1,552✔
99
                        if (wd < 0) {
1,552✔
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) {
781✔
123
                        if (cut)
10✔
124
                                *cut = tmp;
×
125

126
                        break;
127
                }
128

129
                exists = true;
771✔
130

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

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

140
                        *cut2 = tmp2;
572✔
141
                }
142

143
                if (cut)
771✔
144
                        *cut = tmp;
582✔
145

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

155
        if (!exists) {
199✔
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) {
557✔
169
        assert(s);
557✔
170

171
        s->event_source = sd_event_source_disable_unref(s->event_source);
557✔
172
        s->inotify_fd = asynchronous_close(s->inotify_fd);
557✔
173
}
557✔
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) {
412✔
202
        _cleanup_free_ char *trigger = NULL;
412✔
203
        bool b, good = false;
412✔
204

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

208
        switch (s->type) {
412✔
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: {
378✔
219
                int k;
378✔
220

221
                k = dir_is_empty(s->path, /* ignore_hidden_or_backup= */ true);
378✔
222
                good = !(IN_SET(k, -ENOENT, -ENOTDIR) || k > 0);
378✔
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) {
12✔
239
                        trigger = strdup(s->path);
10✔
240
                        if (!trigger)
10✔
241
                                (void) log_oom_debug();
×
242
                }
243
                *ret_trigger_path = TAKE_PTR(trigger);
12✔
244
        }
245

246
        return good;
412✔
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) {
283✔
274
        assert(s);
283✔
275
        assert(s->inotify_fd == -EBADF);
283✔
276

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

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

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

285
        p->directory_mode = 0755;
282✔
286

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

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

293
        assert(p);
282✔
294

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

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

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

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

312
        assert(p);
279✔
313

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

320
        return 0;
321
}
322

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

327
        if (!p->specs)
279✔
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) {
279✔
334
        int r;
279✔
335

336
        assert(p);
279✔
337

338
        if (!UNIT(p)->default_dependencies)
279✔
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) {
279✔
355
        Unit *x;
279✔
356
        int r;
279✔
357

358
        assert(p);
279✔
359

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

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

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

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

373
        assert(p);
279✔
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)
279✔
378
                p->trigger_limit.interval = 2 * USEC_PER_SEC;
279✔
379

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

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

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

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

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

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

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

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

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

411
        return path_verify(p);
279✔
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) {
71✔
444
        assert(p);
71✔
445

446
        LIST_FOREACH(spec, s, p->specs)
146✔
447
                path_spec_unwatch(s);
75✔
448
}
71✔
449

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

453
        assert(p);
199✔
454

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

461
        return 0;
462
}
463

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

467
        assert(p);
270✔
468

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

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

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

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

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

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

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

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

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

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

499
        return 0;
212✔
500
}
501

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

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

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

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

519
        assert(p);
14✔
520

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

525
        if (!ratelimit_below(&p->trigger_limit)) {
14✔
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));
14✔
532
        if (!trigger) {
14✔
533
                log_unit_error(UNIT(p), "Unit to trigger vanished.");
×
534
                goto fail;
×
535
        }
536

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

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

549
        r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, &error, &job);
14✔
550
        if (r < 0) {
14✔
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);
14✔
556

557
        path_set_state(p, PATH_RUNNING);
14✔
558
        path_unwatch(p);
14✔
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) {
410✔
567
        assert(p);
410✔
568
        assert(ret_trigger_path);
410✔
569

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

574
        return false;
575
}
576

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

582
        if (p->trigger_notify_event_source)
220✔
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));
220✔
587
        if (trigger && !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(trigger))) {
220✔
588
                path_set_state(p, PATH_RUNNING);
9✔
589
                path_unwatch(p);
9✔
590
                return;
591
        }
592

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

599
        r = path_watch(p);
199✔
600
        if (r < 0) {
199✔
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)) {
199✔
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);
199✔
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_acquire_invocation_id(u);
69✔
636
        if (r < 0)
69✔
637
                return r;
638

639
        path_mkdir(p);
69✔
640

641
        p->result = PATH_SUCCESS;
69✔
642
        path_enter_waiting(p, true, false);
69✔
643

644
        return 1;
69✔
645
}
646

647
static int path_stop(Unit *u) {
48✔
648
        Path *p = ASSERT_PTR(PATH(u));
48✔
649

650
        assert(IN_SET(p->state, PATH_WAITING, PATH_RUNNING));
48✔
651

652
        path_enter_dead(p, PATH_SUCCESS);
48✔
653
        return 1;
48✔
654
}
655

656
static int path_serialize(Unit *u, FILE *f, FDSet *fds) {
184✔
657
        Path *p = ASSERT_PTR(PATH(u));
184✔
658

659
        assert(f);
184✔
660
        assert(fds);
184✔
661

662
        (void) serialize_item(f, "state", path_state_to_string(p->state));
184✔
663
        (void) serialize_item(f, "result", path_result_to_string(p->result));
184✔
664

665
        LIST_FOREACH(spec, s, p->specs) {
368✔
666
                const char *type;
184✔
667
                _cleanup_free_ char *escaped = NULL;
184✔
668

669
                escaped = cescape(s->path);
184✔
670
                if (!escaped)
184✔
671
                        return log_oom();
×
672

673
                assert_se(type = path_type_to_string(s->type));
184✔
674
                (void) serialize_item_format(f, "path-spec", "%s %i %s",
184✔
675
                                             type,
676
                                             s->previous_exists,
184✔
677
                                             escaped);
678
        }
679

680
        (void) serialize_ratelimit(f, "trigger-ratelimit", &p->trigger_limit);
184✔
681

682
        return 0;
184✔
683
}
684

685
static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
584✔
686
        Path *p = ASSERT_PTR(PATH(u));
584✔
687

688
        assert(key);
584✔
689
        assert(value);
584✔
690
        assert(fds);
584✔
691

692
        if (streq(key, "state")) {
584✔
693
                PathState state;
146✔
694

695
                state = path_state_from_string(value);
146✔
696
                if (state < 0)
146✔
697
                        log_unit_debug(u, "Failed to parse state value: %s", value);
×
698
                else
699
                        p->deserialized_state = state;
146✔
700

701
        } else if (streq(key, "result")) {
438✔
702
                PathResult f;
146✔
703

704
                f = path_result_from_string(value);
146✔
705
                if (f < 0)
146✔
706
                        log_unit_debug(u, "Failed to parse result value: %s", value);
×
707
                else if (f != PATH_SUCCESS)
146✔
708
                        p->result = f;
×
709

710
        } else if (streq(key, "path-spec")) {
292✔
711
                int previous_exists, skip = 0;
146✔
712
                _cleanup_free_ char *type_str = NULL;
146✔
713

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

721
                        type = path_type_from_string(type_str);
146✔
722
                        if (type < 0) {
146✔
723
                                log_unit_warning(u, "Unknown path type \"%s\", ignoring.", type_str);
×
724
                                return 0;
×
725
                        }
726

727
                        l = cunescape(value+skip, 0, &unescaped);
146✔
728
                        if (l < 0) {
146✔
729
                                log_unit_warning_errno(u, l, "Failed to unescape serialize path: %m");
×
730
                                return 0;
×
731
                        }
732

733
                        LIST_FOREACH(spec, s, p->specs)
146✔
734
                                if (s->type == type &&
146✔
735
                                    path_equal(s->path, unescaped)) {
146✔
736

737
                                        s->previous_exists = previous_exists;
146✔
738
                                        break;
146✔
739
                                }
740
                }
741

742
        } else if (streq(key, "trigger-ratelimit"))
146✔
743
                deserialize_ratelimit(&p->trigger_limit, key, value);
146✔
744

745
        else
746
                log_unit_debug(u, "Unknown serialization key: %s", key);
×
747

748
        return 0;
749
}
750

751
static UnitActiveState path_active_state(Unit *u) {
12,127✔
752
        Path *p = ASSERT_PTR(PATH(u));
12,127✔
753

754
        return state_translation_table[p->state];
12,127✔
755
}
756

757
static const char *path_sub_state_to_string(Unit *u) {
158✔
758
        Path *p = ASSERT_PTR(PATH(u));
158✔
759

760
        return path_state_to_string(p->state);
158✔
761
}
762

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

768
        assert(fd >= 0);
11✔
769

770
        if (!IN_SET(p->state, PATH_WAITING, PATH_RUNNING))
11✔
771
                return 0;
772

773
        LIST_FOREACH(spec, i, p->specs)
11✔
774
                if (path_spec_owns_inotify_fd(i, fd)) {
11✔
775
                        found = i;
776
                        break;
777
                }
778

779
        if (!found) {
11✔
780
                log_error("Got event on unknown fd.");
×
781
                goto fail;
×
782
        }
783

784
        changed = path_spec_fd_event(found, revents);
11✔
785
        if (changed < 0)
11✔
786
                goto fail;
×
787

788
        if (changed)
11✔
789
                path_enter_running(p, found->path);
2✔
790
        else
791
                path_enter_waiting(p, false, false);
9✔
792

793
        return 0;
794

795
fail:
×
796
        path_enter_dead(p, PATH_FAILURE_RESOURCES);
×
797
        return 0;
×
798
}
799

800
static void path_trigger_notify_impl(Unit *u, Unit *other, bool on_defer);
801

802
static int path_trigger_notify_on_defer(sd_event_source *s, void *userdata) {
11✔
803
        Path *p = ASSERT_PTR(userdata);
11✔
804
        Unit *trigger;
11✔
805

806
        assert(s);
11✔
807

808
        trigger = UNIT_TRIGGER(UNIT(p));
11✔
809
        if (!trigger) {
11✔
810
                log_unit_error(UNIT(p), "Unit to trigger vanished.");
×
811
                path_enter_dead(p, PATH_FAILURE_RESOURCES);
×
812
                return 0;
×
813
        }
814

815
        path_trigger_notify_impl(UNIT(p), trigger, /* on_defer= */ true);
11✔
816
        return 0;
11✔
817
}
818

819
static void path_trigger_notify_impl(Unit *u, Unit *other, bool on_defer) {
88✔
820
        Path *p = ASSERT_PTR(PATH(u));
88✔
821
        int r;
88✔
822

823
        assert(other);
88✔
824

825
        /* Invoked whenever the unit we trigger changes state or gains or loses a job */
826

827
        /* Filter out invocations with bogus state */
828
        assert(UNIT_IS_LOAD_COMPLETE(other->load_state));
88✔
829

830
        /* Don't propagate state changes from the triggered unit if we are already down */
831
        if (!IN_SET(p->state, PATH_WAITING, PATH_RUNNING))
88✔
832
                return;
833

834
        /* Propagate start limit hit state */
835
        if (other->start_limit_hit) {
76✔
836
                path_enter_dead(p, PATH_FAILURE_UNIT_START_LIMIT_HIT);
×
837
                return;
×
838
        }
839

840
        /* Don't propagate anything if there's still a job queued */
841
        if (other->job)
76✔
842
                return;
843

844
        if (p->state == PATH_RUNNING &&
59✔
845
            UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
55✔
846
                if (!on_defer)
26✔
847
                        log_unit_debug(u, "Got notified about unit deactivation.");
15✔
848
        } else if (p->state == PATH_WAITING &&
33✔
849
                   !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
4✔
UNCOV
850
                if (!on_defer)
×
UNCOV
851
                        log_unit_debug(u, "Got notified about unit activation.");
×
852
        } else
853
                return;
33✔
854

855
        if (on_defer) {
15✔
856
                path_enter_waiting(p, /* initial= */ false, /* from_trigger_notify= */ true);
11✔
857
                return;
11✔
858
        }
859

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

880
                (void) sd_event_source_set_description(p->trigger_notify_event_source, "path-trigger-notify");
6✔
881
        }
882
}
883

884
static void path_trigger_notify(Unit *u, Unit *other) {
77✔
885
        path_trigger_notify_impl(u, other, /* on_defer= */ false);
77✔
886
}
77✔
887

888
static void path_reset_failed(Unit *u) {
2✔
889
        Path *p = ASSERT_PTR(PATH(u));
2✔
890

891
        if (p->state == PATH_FAILED)
2✔
892
                path_set_state(p, PATH_DEAD);
×
893

894
        p->result = PATH_SUCCESS;
2✔
895
}
2✔
896

897
static int path_test_startable(Unit *u) {
69✔
898
        Path *p = ASSERT_PTR(PATH(u));
69✔
899
        int r;
69✔
900

901
        r = unit_test_trigger_loaded(u);
69✔
902
        if (r < 0)
69✔
903
                return r;
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) {
14✔
915
        ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
14✔
916

917
        p->trigger_path_filename = mfree(p->trigger_path_filename);
14✔
918
}
14✔
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
        POINTER_MAY_BE_NULL(details);
×
935

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

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

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

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

950
        return 0;
951
}
952

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

958
        assert(strv);
15✔
959

960
        if (isempty(p->trigger_path_filename))
15✔
961
                return 0;
15✔
962

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

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

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

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

978
        assert(strv);
35✔
979

980
        if (isempty(p->trigger_path_filename))
35✔
981
                return 0;
982

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

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

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

998
DEFINE_STRING_TABLE_LOOKUP(path_type, PathType);
678✔
999

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

1008
DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult);
706✔
1009

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

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

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

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

1027
        .coldplug = path_coldplug,
1028

1029
        .dump = path_dump,
1030

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

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

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

1040
        .trigger_notify = path_trigger_notify,
1041

1042
        .reset_failed = path_reset_failed,
1043

1044
        .bus_set_property = bus_path_set_property,
1045

1046
        .test_startable = path_test_startable,
1047
};
1048

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

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