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

systemd / systemd / 20800761525

07 Jan 2026 08:58PM UTC coverage: 72.582% (-0.1%) from 72.714%
20800761525

push

github

yuwata
meson: do not install standalone binaries if the meson option is disabled

A recent commit made the standalone binaries always buildable
on demand, but as a side effect due to how 'meson install' works,
they are always built and installed by 'meson install' even
if the standalone-binaries= option is disabled.
Fix it so that 'meson install' only installs them if the
option is explicitly enabled, while still allowing
building them on demand.

Follow-up for 54492552a

309720 of 426715 relevant lines covered (72.58%)

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

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

55
        path_spec_unwatch(s);
156✔
56

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

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

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

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

84
                        flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_CREATE | IN_MOVED_TO;
453✔
85
                } else {
86
                        cut = NULL;
156✔
87
                        flags = flags_table[s->type];
156✔
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,807✔
94
                        uint32_t f = flags;
1,208✔
95

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

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

126
                        break;
127
                }
128

129
                exists = true;
599✔
130

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

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

140
                        *cut2 = tmp2;
443✔
141
                }
142

143
                if (cut)
599✔
144
                        *cut = tmp;
453✔
145

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

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

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

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

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

221
                k = dir_is_empty(s->path, /* ignore_hidden_or_backup= */ true);
291✔
222
                good = !(IN_SET(k, -ENOENT, -ENOTDIR) || k > 0);
291✔
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;
325✔
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) {
67✔
444
        assert(p);
67✔
445

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

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

453
        assert(p);
156✔
454

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

461
        return 0;
462
}
463

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

467
        assert(p);
223✔
468

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

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

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

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

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

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

574
        return false;
575
}
576

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

582
        if (p->trigger_notify_event_source)
173✔
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));
173✔
587
        if (trigger && !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(trigger))) {
173✔
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)) {
167✔
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);
156✔
600
        if (r < 0) {
156✔
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)) {
156✔
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);
156✔
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) {
134✔
657
        Path *p = ASSERT_PTR(PATH(u));
134✔
658

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

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

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

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

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

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

682
        return 0;
134✔
683
}
684

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

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

692
        if (streq(key, "state")) {
384✔
693
                PathState state;
96✔
694

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

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

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

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

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

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

727
                        l = cunescape(value+skip, 0, &unescaped);
96✔
728
                        if (l < 0) {
96✔
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)
96✔
734
                                if (s->type == type &&
96✔
735
                                    path_equal(s->path, unescaped)) {
96✔
736

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

742
        } else if (streq(key, "trigger-ratelimit"))
96✔
743
                deserialize_ratelimit(&p->trigger_limit, key, value);
96✔
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) {
11,984✔
752
        Path *p = ASSERT_PTR(PATH(u));
11,984✔
753

754
        return state_translation_table[p->state];
11,984✔
755
}
756

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

760
        return path_state_to_string(p->state);
136✔
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) {
80✔
820
        Path *p = ASSERT_PTR(PATH(u));
80✔
821
        int r;
80✔
822

823
        assert(other);
80✔
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));
80✔
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))
80✔
832
                return;
833

834
        /* Propagate start limit hit state */
835
        if (other->start_limit_hit) {
73✔
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)
73✔
842
                return;
843

844
        if (p->state == PATH_RUNNING &&
57✔
845
            UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
53✔
846
                if (!on_defer)
26✔
847
                        log_unit_debug(u, "Got notified about unit deactivation.");
15✔
848
        } else if (p->state == PATH_WAITING &&
31✔
849
                   !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
4✔
850
                if (!on_defer)
×
851
                        log_unit_debug(u, "Got notified about unit activation.");
×
852
        } else
853
                return;
31✔
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) {
69✔
885
        path_trigger_notify_impl(u, other, /* on_defer= */ false);
69✔
886
}
69✔
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) {
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) {
14✔
953
        const ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
14✔
954
        char *s;
14✔
955
        int r;
14✔
956

957
        assert(strv);
14✔
958

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

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

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

977
        assert(strv);
22✔
978

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

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