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

systemd / systemd / 14630481637

23 Apr 2025 07:04PM UTC coverage: 72.178% (-0.002%) from 72.18%
14630481637

push

github

DaanDeMeyer
mkosi: Run clangd within the tools tree instead of the build container

Running within the build sandbox has a number of disadvantages:
- We have a separate clangd cache for each distribution/release combo
- It requires to build the full image before clangd can be used
- It breaks every time the image becomes out of date and requires a
  rebuild
- We can't look at system headers as we don't have the knowledge to map
  them from inside the build sandbox to the corresponding path on the host

Instead, let's have mkosi.clangd run clangd within the tools tree. We
already require building systemd for both the host and the target anyway,
and all the dependencies to build systemd are installed in the tools tree
already for that, as well as clangd since it's installed together with the
other clang tooling we install in the tools tree. Unlike the previous approach,
this approach only requires the mkosi tools tree to be built upfront, which has
a much higher chance of not invalidating its cache. We can also trivially map
system header lookups from within the sandbox to the path within mkosi.tools
on the host so that starts working as well.

297054 of 411557 relevant lines covered (72.18%)

686269.58 hits per line

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

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

3
#include <errno.h>
4
#include <sys/epoll.h>
5
#include <sys/inotify.h>
6
#include <unistd.h>
7

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

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

38
static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata);
39

40
int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) {
174✔
41
        static const int flags_table[_PATH_TYPE_MAX] = {
174✔
42
                [PATH_EXISTS]              = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
43
                [PATH_EXISTS_GLOB]         = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
44
                [PATH_CHANGED]             = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO,
45
                [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,
46
                [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO,
47
        };
48

49
        bool exists = false;
174✔
50
        char *slash, *oldslash = NULL;
174✔
51
        int r;
174✔
52

53
        assert(s);
174✔
54
        assert(s->unit);
174✔
55
        assert(handler);
174✔
56

57
        path_spec_unwatch(s);
174✔
58

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

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

71
        (void) sd_event_source_set_description(s->event_source, "path");
174✔
72

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

76
        for (slash = strchr(s->path, '/'); ; slash = strchr(slash+1, '/')) {
174✔
77
                bool incomplete = false;
681✔
78
                int flags, wd = -1;
681✔
79
                char tmp, *cut;
681✔
80

81
                if (slash) {
681✔
82
                        cut = slash + (slash == s->path);
507✔
83
                        tmp = *cut;
507✔
84
                        *cut = '\0';
507✔
85

86
                        flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_CREATE | IN_MOVED_TO;
507✔
87
                } else {
88
                        cut = NULL;
174✔
89
                        flags = flags_table[s->type];
174✔
90
                }
91

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

98
                        SET_FLAG(f, IN_DONT_FOLLOW, !follow_symlink);
1,352✔
99

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

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

116
                                        r = wd;
×
117
                                        goto fail;
×
118
                                }
119

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

124
                if (incomplete) {
681✔
125
                        if (cut)
10✔
126
                                *cut = tmp;
×
127

128
                        break;
129
                }
130

131
                exists = true;
671✔
132

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

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

142
                        *cut2 = tmp2;
497✔
143
                }
144

145
                if (cut)
671✔
146
                        *cut = tmp;
507✔
147

148
                if (slash)
671✔
149
                        oldslash = slash;
507✔
150
                else {
151
                        /* whole path has been iterated over */
152
                        s->primary_wd = wd;
164✔
153
                        break;
164✔
154
                }
155
        }
156

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

163
        return 0;
164

165
fail:
×
166
        path_spec_unwatch(s);
×
167
        return r;
×
168
}
169

170
void path_spec_unwatch(PathSpec *s) {
457✔
171
        assert(s);
457✔
172

173
        s->event_source = sd_event_source_disable_unref(s->event_source);
457✔
174
        s->inotify_fd = asynchronous_close(s->inotify_fd);
457✔
175
}
457✔
176

177
int path_spec_fd_event(PathSpec *s, uint32_t revents) {
8✔
178
        union inotify_event_buffer buffer;
8✔
179
        ssize_t l;
8✔
180

181
        assert(s);
8✔
182

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

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

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

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

200
        return 0;
201
}
202

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

207
        assert(s);
357✔
208
        assert(ret_trigger_path);
357✔
209

210
        switch (s->type) {
357✔
211

212
        case PATH_EXISTS:
11✔
213
                good = access(s->path, F_OK) >= 0;
11✔
214
                break;
11✔
215

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

220
        case PATH_DIRECTORY_NOT_EMPTY: {
326✔
221
                int k;
326✔
222

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

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

235
        default:
17✔
236
                ;
31✔
237
        }
238

239
        if (good) {
31✔
240
                if (!trigger) {
9✔
241
                        trigger = strdup(s->path);
7✔
242
                        if (!trigger)
7✔
243
                                (void) log_oom_debug();
×
244
                }
245
                *ret_trigger_path = TAKE_PTR(trigger);
9✔
246
        }
247

248
        return good;
357✔
249
}
250

251
static void path_spec_mkdir(PathSpec *s, mode_t mode) {
58✔
252
        int r;
58✔
253

254
        assert(s);
58✔
255

256
        if (IN_SET(s->type, PATH_EXISTS, PATH_EXISTS_GLOB))
58✔
257
                return;
258

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

264
static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) {
×
265
        const char *type;
×
266

267
        assert(s);
×
268
        assert(f);
×
269
        assert(prefix);
×
270

271
        assert_se(type = path_type_to_string(s->type));
×
272
        fprintf(f, "%s%s: %s\n", prefix, type, s->path);
×
273
}
×
274

275
void path_spec_done(PathSpec *s) {
228✔
276
        assert(s);
228✔
277
        assert(s->inotify_fd == -EBADF);
228✔
278

279
        free(s->path);
228✔
280
}
228✔
281

282
static void path_init(Unit *u) {
226✔
283
        Path *p = ASSERT_PTR(PATH(u));
226✔
284

285
        assert(u->load_state == UNIT_STUB);
226✔
286

287
        p->directory_mode = 0755;
226✔
288

289
        p->trigger_limit = RATELIMIT_OFF;
226✔
290
}
226✔
291

292
void path_free_specs(Path *p) {
226✔
293
        PathSpec *s;
226✔
294

295
        assert(p);
226✔
296

297
        while ((s = LIST_POP(spec, p->specs))) {
454✔
298
                path_spec_unwatch(s);
228✔
299
                path_spec_done(s);
228✔
300
                free(s);
228✔
301
        }
302
}
226✔
303

304
static void path_done(Unit *u) {
226✔
305
        Path *p = ASSERT_PTR(PATH(u));
226✔
306

307
        p->trigger_notify_event_source = sd_event_source_disable_unref(p->trigger_notify_event_source);
226✔
308
        path_free_specs(p);
226✔
309
}
226✔
310

311
static int path_add_mount_dependencies(Path *p) {
226✔
312
        int r;
226✔
313

314
        assert(p);
226✔
315

316
        LIST_FOREACH(spec, s, p->specs) {
454✔
317
                r = unit_add_mounts_for(UNIT(p), s->path, UNIT_DEPENDENCY_FILE, UNIT_MOUNT_REQUIRES);
228✔
318
                if (r < 0)
228✔
319
                        return r;
320
        }
321

322
        return 0;
323
}
324

325
static int path_verify(Path *p) {
226✔
326
        assert(p);
226✔
327
        assert(UNIT(p)->load_state == UNIT_LOADED);
226✔
328

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

332
        return 0;
333
}
334

335
static int path_add_default_dependencies(Path *p) {
226✔
336
        int r;
226✔
337

338
        assert(p);
226✔
339

340
        if (!UNIT(p)->default_dependencies)
226✔
341
                return 0;
342

343
        r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_PATHS_TARGET, true, UNIT_DEPENDENCY_DEFAULT);
16✔
344
        if (r < 0)
8✔
345
                return r;
346

347
        if (MANAGER_IS_SYSTEM(UNIT(p)->manager)) {
8✔
348
                r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, true, UNIT_DEPENDENCY_DEFAULT);
1✔
349
                if (r < 0)
1✔
350
                        return r;
351
        }
352

353
        return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, true, UNIT_DEPENDENCY_DEFAULT);
8✔
354
}
355

356
static int path_add_trigger_dependencies(Path *p) {
226✔
357
        Unit *x;
226✔
358
        int r;
226✔
359

360
        assert(p);
226✔
361

362
        if (UNIT_TRIGGER(UNIT(p)))
226✔
363
                return 0;
226✔
364

365
        r = unit_load_related_unit(UNIT(p), ".service", &x);
450✔
366
        if (r < 0)
225✔
367
                return r;
368

369
        return unit_add_two_dependencies(UNIT(p), UNIT_BEFORE, UNIT_TRIGGERS, x, true, UNIT_DEPENDENCY_IMPLICIT);
225✔
370
}
371

372
static int path_add_extras(Path *p) {
226✔
373
        int r;
226✔
374

375
        assert(p);
226✔
376

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

382
        if (p->trigger_limit.burst == UINT_MAX)
226✔
383
                p->trigger_limit.burst = 200;
226✔
384

385
        r = path_add_trigger_dependencies(p);
226✔
386
        if (r < 0)
226✔
387
                return r;
388

389
        r = path_add_mount_dependencies(p);
226✔
390
        if (r < 0)
226✔
391
                return r;
392

393
        return path_add_default_dependencies(p);
226✔
394
}
395

396
static int path_load(Unit *u) {
226✔
397
        Path *p = ASSERT_PTR(PATH(u));
226✔
398
        int r;
226✔
399

400
        assert(u->load_state == UNIT_STUB);
226✔
401

402
        r = unit_load_fragment_and_dropin(u, true);
226✔
403
        if (r < 0)
226✔
404
                return r;
405

406
        if (u->load_state != UNIT_LOADED)
226✔
407
                return 0;
408

409
        r = path_add_extras(p);
226✔
410
        if (r < 0)
226✔
411
                return r;
412

413
        return path_verify(p);
226✔
414
}
415

416
static void path_dump(Unit *u, FILE *f, const char *prefix) {
×
417
        Path *p = ASSERT_PTR(PATH(u));
×
418
        Unit *trigger;
×
419

420
        assert(f);
×
421
        assert(prefix);
×
422

423
        trigger = UNIT_TRIGGER(u);
×
424

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

441
        LIST_FOREACH(spec, s, p->specs)
×
442
                path_spec_dump(s, f, prefix);
×
443
}
×
444

445
static void path_unwatch(Path *p) {
55✔
446
        assert(p);
55✔
447

448
        LIST_FOREACH(spec, s, p->specs)
110✔
449
                path_spec_unwatch(s);
55✔
450
}
55✔
451

452
static int path_watch(Path *p) {
174✔
453
        int r;
174✔
454

455
        assert(p);
174✔
456

457
        LIST_FOREACH(spec, s, p->specs) {
348✔
458
                r = path_spec_watch(s, path_dispatch_io);
174✔
459
                if (r < 0)
174✔
460
                        return r;
461
        }
462

463
        return 0;
464
}
465

466
static void path_set_state(Path *p, PathState state) {
229✔
467
        PathState old_state;
229✔
468

469
        assert(p);
229✔
470

471
        if (p->state != state)
229✔
472
                bus_unit_send_pending_change_signal(UNIT(p), false);
226✔
473

474
        old_state = p->state;
229✔
475
        p->state = state;
229✔
476

477
        if (!IN_SET(state, PATH_WAITING, PATH_RUNNING))
229✔
478
                path_unwatch(p);
44✔
479

480
        if (state != old_state)
229✔
481
                log_unit_debug(UNIT(p), "Changed %s -> %s", path_state_to_string(old_state), path_state_to_string(state));
226✔
482

483
        unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true);
229✔
484
}
229✔
485

486
static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify);
487

488
static int path_coldplug(Unit *u) {
168✔
489
        Path *p = ASSERT_PTR(PATH(u));
168✔
490

491
        assert(p->state == PATH_DEAD);
168✔
492

493
        if (p->deserialized_state != p->state) {
168✔
494

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

501
        return 0;
168✔
502
}
503

504
static void path_enter_dead(Path *p, PathResult f) {
44✔
505
        assert(p);
44✔
506

507
        if (p->result == PATH_SUCCESS)
44✔
508
                p->result = f;
44✔
509

510
        unit_log_result(UNIT(p), p->result == PATH_SUCCESS, path_result_to_string(p->result));
44✔
511
        path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD);
88✔
512
}
44✔
513

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

521
        assert(p);
11✔
522

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

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

533
        trigger = UNIT_TRIGGER(UNIT(p));
22✔
534
        if (!trigger) {
11✔
535
                log_unit_error(UNIT(p), "Unit to trigger vanished.");
×
536
                goto fail;
×
537
        }
538

539
        details = activation_details_new(UNIT(p));
11✔
540
        if (!details) {
11✔
541
                log_oom();
×
542
                goto fail;
×
543
        }
544

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

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

557
        job_set_activation_details(job, details);
11✔
558

559
        path_set_state(p, PATH_RUNNING);
11✔
560
        path_unwatch(p);
11✔
561

562
        return;
563

564
fail:
×
565
        path_enter_dead(p, PATH_FAILURE_RESOURCES);
×
566
}
567

568
static bool path_check_good(Path *p, bool initial, bool from_trigger_notify, char **ret_trigger_path) {
357✔
569
        assert(p);
357✔
570
        assert(ret_trigger_path);
357✔
571

572
        LIST_FOREACH(spec, s, p->specs)
705✔
573
                if (path_spec_check_good(s, initial, from_trigger_notify, ret_trigger_path))
357✔
574
                        return true;
575

576
        return false;
577
}
578

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

584
        if (p->trigger_notify_event_source)
183✔
585
                (void) event_source_disable(p->trigger_notify_event_source);
11✔
586

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

595
        if (path_check_good(p, initial, from_trigger_notify, &trigger_path)) {
183✔
596
                log_unit_debug(UNIT(p), "Got triggered by '%s'.", trigger_path);
18✔
597
                path_enter_running(p, trigger_path);
9✔
598
                return;
599
        }
600

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

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

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

618
        path_set_state(p, PATH_WAITING);
174✔
619
}
620

621
static void path_mkdir(Path *p) {
64✔
622
        assert(p);
64✔
623

624
        if (!p->make_directory)
64✔
625
                return;
626

627
        LIST_FOREACH(spec, s, p->specs)
116✔
628
                path_spec_mkdir(s, p->directory_mode);
58✔
629
}
630

631
static int path_start(Unit *u) {
64✔
632
        Path *p = ASSERT_PTR(PATH(u));
64✔
633
        int r;
64✔
634

635
        assert(IN_SET(p->state, PATH_DEAD, PATH_FAILED));
64✔
636

637
        r = unit_test_trigger_loaded(u);
64✔
638
        if (r < 0)
64✔
639
                return r;
640

641
        r = unit_acquire_invocation_id(u);
64✔
642
        if (r < 0)
64✔
643
                return r;
644

645
        path_mkdir(p);
64✔
646

647
        p->result = PATH_SUCCESS;
64✔
648
        path_enter_waiting(p, true, false);
64✔
649

650
        return 1;
64✔
651
}
652

653
static int path_stop(Unit *u) {
44✔
654
        Path *p = ASSERT_PTR(PATH(u));
44✔
655

656
        assert(IN_SET(p->state, PATH_WAITING, PATH_RUNNING));
44✔
657

658
        path_enter_dead(p, PATH_SUCCESS);
44✔
659
        return 1;
44✔
660
}
661

662
static int path_serialize(Unit *u, FILE *f, FDSet *fds) {
140✔
663
        Path *p = ASSERT_PTR(PATH(u));
140✔
664

665
        assert(f);
140✔
666
        assert(fds);
140✔
667

668
        (void) serialize_item(f, "state", path_state_to_string(p->state));
140✔
669
        (void) serialize_item(f, "result", path_result_to_string(p->result));
140✔
670

671
        LIST_FOREACH(spec, s, p->specs) {
280✔
672
                const char *type;
140✔
673
                _cleanup_free_ char *escaped = NULL;
140✔
674

675
                escaped = cescape(s->path);
140✔
676
                if (!escaped)
140✔
677
                        return log_oom();
×
678

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

686
        (void) serialize_ratelimit(f, "trigger-ratelimit", &p->trigger_limit);
140✔
687

688
        return 0;
140✔
689
}
690

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

694
        assert(key);
432✔
695
        assert(value);
432✔
696
        assert(fds);
432✔
697

698
        if (streq(key, "state")) {
432✔
699
                PathState state;
108✔
700

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

707
        } else if (streq(key, "result")) {
324✔
708
                PathResult f;
108✔
709

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

716
        } else if (streq(key, "path-spec")) {
216✔
717
                int previous_exists, skip = 0;
108✔
718
                _cleanup_free_ char *type_str = NULL;
108✔
719

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

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

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

739
                        LIST_FOREACH(spec, s, p->specs)
108✔
740
                                if (s->type == type &&
108✔
741
                                    path_equal(s->path, unescaped)) {
108✔
742

743
                                        s->previous_exists = previous_exists;
108✔
744
                                        break;
108✔
745
                                }
746
                }
747

748
        } else if (streq(key, "trigger-ratelimit"))
108✔
749
                deserialize_ratelimit(&p->trigger_limit, key, value);
108✔
750

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

754
        return 0;
755
}
756

757
static UnitActiveState path_active_state(Unit *u) {
4,944✔
758
        Path *p = ASSERT_PTR(PATH(u));
4,944✔
759

760
        return state_translation_table[p->state];
4,944✔
761
}
762

763
static const char *path_sub_state_to_string(Unit *u) {
131✔
764
        Path *p = ASSERT_PTR(PATH(u));
131✔
765

766
        return path_state_to_string(p->state);
131✔
767
}
768

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

774
        assert(fd >= 0);
8✔
775

776
        if (!IN_SET(p->state, PATH_WAITING, PATH_RUNNING))
8✔
777
                return 0;
778

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

785
        if (!found) {
8✔
786
                log_error("Got event on unknown fd.");
×
787
                goto fail;
×
788
        }
789

790
        changed = path_spec_fd_event(found, revents);
8✔
791
        if (changed < 0)
8✔
792
                goto fail;
×
793

794
        if (changed)
8✔
795
                path_enter_running(p, found->path);
2✔
796
        else
797
                path_enter_waiting(p, false, false);
6✔
798

799
        return 0;
800

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

806
static void path_trigger_notify_impl(Unit *u, Unit *other, bool on_defer);
807

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

812
        assert(s);
11✔
813

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

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

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

829
        assert(other);
66✔
830

831
        /* Invoked whenever the unit we trigger changes state or gains or loses a job */
832

833
        /* Filter out invocations with bogus state */
834
        assert(UNIT_IS_LOAD_COMPLETE(other->load_state));
66✔
835

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

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

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

850
        if (p->state == PATH_RUNNING &&
53✔
851
            UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
47✔
852
                if (!on_defer)
25✔
853
                        log_unit_debug(u, "Got notified about unit deactivation.");
14✔
854
        } else if (p->state == PATH_WAITING &&
28✔
855
                   !UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) {
6✔
856
                if (!on_defer)
×
857
                        log_unit_debug(u, "Got notified about unit activation.");
×
858
        } else
859
                return;
28✔
860

861
        if (on_defer) {
14✔
862
                path_enter_waiting(p, /* initial = */ false, /* from_trigger_notify = */ true);
11✔
863
                return;
11✔
864
        }
865

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

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

890
static void path_trigger_notify(Unit *u, Unit *other) {
55✔
891
        path_trigger_notify_impl(u, other, /* on_defer = */ false);
55✔
892
}
55✔
893

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

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

900
        p->result = PATH_SUCCESS;
2✔
901
}
2✔
902

903
static int path_can_start(Unit *u) {
64✔
904
        Path *p = ASSERT_PTR(PATH(u));
64✔
905
        int r;
64✔
906

907
        r = unit_test_start_limit(u);
64✔
908
        if (r < 0) {
64✔
909
                path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT);
×
910
                return r;
×
911
        }
912

913
        return 1;
914
}
915

916
static void activation_details_path_done(ActivationDetails *details) {
11✔
917
        ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
11✔
918

919
        p->trigger_path_filename = mfree(p->trigger_path_filename);
11✔
920
}
11✔
921

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

925
        assert(f);
×
926

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

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

934
        assert(key);
×
935
        assert(value);
×
936

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

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

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

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

951
        return 0;
952
}
953

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

959
        assert(strv);
11✔
960

961
        if (isempty(p->trigger_path_filename))
11✔
962
                return 0;
11✔
963

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

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

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

975
static int activation_details_path_append_pair(ActivationDetails *details, char ***strv) {
×
976
        ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details));
×
977
        int r;
×
978

979
        assert(strv);
×
980

981
        if (isempty(p->trigger_path_filename))
×
982
                return 0;
983

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

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

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

999
DEFINE_STRING_TABLE_LOOKUP(path_type, PathType);
538✔
1000

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

1009
DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult);
588✔
1010

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

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

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

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

1028
        .coldplug = path_coldplug,
1029

1030
        .dump = path_dump,
1031

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

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

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

1041
        .trigger_notify = path_trigger_notify,
1042

1043
        .reset_failed = path_reset_failed,
1044

1045
        .bus_set_property = bus_path_set_property,
1046

1047
        .can_start = path_can_start,
1048
};
1049

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

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