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

systemd / systemd / 20321298054

17 Dec 2025 11:19PM UTC coverage: 72.709% (-0.03%) from 72.736%
20321298054

push

github

YHNdnzj
core: set Result=start-limit-hit when a unit is rate limited

There is currently no way to figure out a rate limit was hit on a unit,
as the last result is stripped in order to keep reporting the first
result, which is useful in case of a watchdog failure, which is the
reason why it was changed as such.

But rate limiting is also an important information to provide to
users, so allow the Result property to reflect it when it
happens.

6 of 7 new or added lines in 7 files covered. (85.71%)

1358 existing lines in 49 files now uncovered.

309654 of 425883 relevant lines covered (72.71%)

1138050.05 hits per line

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

83.21
/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) {
467✔
169
        assert(s);
467✔
170

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

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

179
        assert(s);
12✔
180

181
        if (revents != EPOLLIN)
12✔
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));
12✔
186
        if (l < 0) {
12✔
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))
12✔
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) {
338✔
202
        _cleanup_free_ char *trigger = NULL;
338✔
203
        bool b, good = false;
338✔
204

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

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

221
                k = dir_is_empty(s->path, /* ignore_hidden_or_backup= */ true);
304✔
222
                good = !(IN_SET(k, -ENOENT, -ENOTDIR) || k > 0);
304✔
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;
338✔
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) {
232✔
281
        Path *p = ASSERT_PTR(PATH(u));
232✔
282

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

285
        p->directory_mode = 0755;
232✔
286

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

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

293
        assert(p);
232✔
294

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

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

305
        p->trigger_notify_event_source = sd_event_source_disable_unref(p->trigger_notify_event_source);
232✔
306
        path_free_specs(p);
232✔
307
}
232✔
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) {
233✔
395
        Path *p = ASSERT_PTR(PATH(u));
233✔
396
        int r;
233✔
397

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

400
        r = unit_load_fragment_and_dropin(u, true);
233✔
401
        if (r < 0)
233✔
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) {
68✔
444
        assert(p);
68✔
445

446
        LIST_FOREACH(spec, s, p->specs)
140✔
447
                path_spec_unwatch(s);
72✔
448
}
68✔
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) {
230✔
465
        PathState old_state;
230✔
466

467
        assert(p);
230✔
468

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

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

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

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

481
        unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], /* reload_success= */ true);
230✔
482
}
230✔
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))
84✔
494
                        path_enter_waiting(p, true, false);
84✔
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) {
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) {
336✔
567
        assert(p);
336✔
568
        assert(ret_trigger_path);
336✔
569

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

574
        return false;
575
}
576

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

582
        if (p->trigger_notify_event_source)
180✔
583
                (void) event_source_disable(p->trigger_notify_event_source);
17✔
584

585
        /* If the triggered unit is already running, so are we */
586
        trigger = UNIT_TRIGGER(UNIT(p));
180✔
587
        if (trigger && !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(trigger))) {
180✔
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)) {
174✔
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);
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✔
UNCOV
611
                log_unit_debug(UNIT(p), "Got triggered by '%s'.", trigger_path);
×
UNCOV
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) {
48✔
652
        Path *p = ASSERT_PTR(PATH(u));
48✔
653

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

656
        path_enter_dead(p, PATH_SUCCESS);
48✔
657
        return 1;
48✔
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) {
11,515✔
756
        Path *p = ASSERT_PTR(PATH(u));
11,515✔
757

758
        return state_translation_table[p->state];
11,515✔
759
}
760

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

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

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

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

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

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

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

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

792
        if (changed)
12✔
793
                path_enter_running(p, found->path);
2✔
794
        else
795
                path_enter_waiting(p, false, false);
10✔
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) {
17✔
807
        Path *p = ASSERT_PTR(userdata);
17✔
808
        Unit *trigger;
17✔
809

810
        assert(s);
17✔
811

812
        trigger = UNIT_TRIGGER(UNIT(p));
17✔
813
        if (!trigger) {
17✔
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);
17✔
820
        return 0;
17✔
821
}
822

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

827
        assert(other);
90✔
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));
90✔
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))
90✔
836
                return;
837

838
        /* Propagate start limit hit state */
839
        if (other->start_limit_hit) {
86✔
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)
86✔
846
                return;
847

848
        if (p->state == PATH_RUNNING &&
69✔
849
            UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
54✔
850
                if (!on_defer)
27✔
851
                        log_unit_debug(u, "Got notified about unit deactivation.");
16✔
852
        } else if (p->state == PATH_WAITING &&
42✔
853
                   !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
15✔
854
                if (!on_defer)
12✔
855
                        log_unit_debug(u, "Got notified about unit activation.");
6✔
856
        } else
857
                return;
30✔
858

859
        if (on_defer) {
22✔
860
                path_enter_waiting(p, /* initial= */ false, /* from_trigger_notify= */ true);
17✔
861
                return;
17✔
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) {
22✔
870
                r = sd_event_source_set_enabled(p->trigger_notify_event_source, SD_EVENT_ONESHOT);
10✔
871
                if (r < 0) {
10✔
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);
12✔
878
                if (r < 0) {
12✔
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");
12✔
885
        }
886
}
887

888
static void path_trigger_notify(Unit *u, Unit *other) {
73✔
889
        path_trigger_notify_impl(u, other, /* on_defer= */ false);
73✔
890
}
73✔
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) {
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

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) {
15✔
953
        const ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
15✔
954
        char *s;
15✔
955
        int r;
15✔
956

957
        assert(strv);
15✔
958

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

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

966
        r = strv_consume(strv, TAKE_PTR(s));
15✔
967
        if (r < 0)
15✔
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) {
36✔
974
        const ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
36✔
975
        int r;
36✔
976

977
        assert(strv);
36✔
978

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

982
        r = strv_extend_many(strv, "trigger_path", p->trigger_path_filename);
36✔
983
        if (r < 0)
36✔
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);
592✔
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