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

systemd / systemd / 15232239991

24 May 2025 08:01PM UTC coverage: 72.053% (-0.02%) from 72.07%
15232239991

push

github

web-flow
docs: add man pages for sd_device_enumerator_[new,ref,unref,unrefp] (#37586)

For #20929.

299160 of 415197 relevant lines covered (72.05%)

703671.29 hits per line

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

83.69
/src/shared/unit-file.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include "sd-id128.h"
4

5
#include "alloc-util.h"
6
#include "chase.h"
7
#include "dirent-util.h"
8
#include "fd-util.h"
9
#include "fs-util.h"
10
#include "glyph-util.h"
11
#include "initrd-util.h"
12
#include "log.h"
13
#include "path-lookup.h"
14
#include "set.h"
15
#include "siphash24.h"
16
#include "special.h"
17
#include "stat-util.h"
18
#include "string-util.h"
19
#include "strv.h"
20
#include "time-util.h"
21
#include "unit-file.h"
22
#include "unit-name.h"
23

24
int unit_symlink_name_compatible(const char *symlink, const char *target, bool instance_propagation) {
15,035✔
25
        _cleanup_free_ char *template = NULL;
15,035✔
26
        int r, un_type1, un_type2;
15,035✔
27

28
        un_type1 = unit_name_classify(symlink);
15,035✔
29

30
        /* The straightforward case: the symlink name matches the target and we have a valid unit */
31
        if (streq(symlink, target) &&
15,035✔
32
            (un_type1 & (UNIT_NAME_PLAIN | UNIT_NAME_INSTANCE)))
14,223✔
33
                return 1;
34

35
        r = unit_name_template(symlink, &template);
815✔
36
        if (r == -EINVAL)
815✔
37
                return 0; /* Not a template */
38
        if (r < 0)
787✔
39
                return r;
40

41
        un_type2 = unit_name_classify(target);
787✔
42

43
        /* An instance name points to a target that is just the template name */
44
        if (un_type1 == UNIT_NAME_INSTANCE &&
787✔
45
            un_type2 == UNIT_NAME_TEMPLATE &&
787✔
46
            streq(template, target))
754✔
47
                return 1;
48

49
        /* foo@.target.requires/bar@.service: instance will be propagated */
50
        if (instance_propagation &&
38✔
51
            un_type1 == UNIT_NAME_TEMPLATE &&
38✔
52
            un_type2 == UNIT_NAME_TEMPLATE &&
2✔
53
            streq(template, target))
2✔
54
                return 1;
1✔
55

56
        return 0;
57
}
58

59
int unit_validate_alias_symlink_or_warn(int log_level, const char *filename, const char *target) {
11,496✔
60
        _cleanup_free_ char *src = NULL, *dst = NULL;
×
61
        _cleanup_free_ char *src_instance = NULL, *dst_instance = NULL;
11,496✔
62
        UnitType src_unit_type, dst_unit_type;
11,496✔
63
        UnitNameFlags src_name_type, dst_name_type;
11,496✔
64
        int r;
11,496✔
65

66
        /* Check if the *alias* symlink is valid. This applies to symlinks like
67
         * /etc/systemd/system/dbus.service → dbus-broker.service, but not to .wants or .requires symlinks
68
         * and such. Neither does this apply to symlinks which *link* units, i.e. symlinks to outside of the
69
         * unit lookup path.
70
         *
71
         * -EINVAL is returned if the something is wrong with the source filename or the source unit type is
72
         *         not allowed to symlink,
73
         * -EXDEV if the target filename is not a valid unit name or doesn't match the source,
74
         * -ELOOP for an alias to self.
75
         */
76

77
        r = path_extract_filename(filename, &src);
11,496✔
78
        if (r < 0)
11,496✔
79
                return r;
80

81
        r = path_extract_filename(target, &dst);
11,496✔
82
        if (r < 0)
11,496✔
83
                return r;
84

85
        /* src checks */
86

87
        src_name_type = unit_name_to_instance(src, &src_instance);
11,496✔
88
        if (src_name_type < 0)
11,496✔
89
                return log_full_errno(log_level, src_name_type,
×
90
                                      "%s: not a valid unit name \"%s\": %m", filename, src);
91

92
        src_unit_type = unit_name_to_type(src);
11,496✔
93
        assert(src_unit_type >= 0); /* unit_name_to_instance() checked the suffix already */
11,496✔
94

95
        if (!unit_type_may_alias(src_unit_type))
11,496✔
96
                return log_full_errno(log_level, SYNTHETIC_ERRNO(EINVAL),
284✔
97
                                      "%s: symlinks are not allowed for units of this type, rejecting.",
98
                                      filename);
99

100
        if (src_name_type != UNIT_NAME_PLAIN &&
11,212✔
101
            !unit_type_may_template(src_unit_type))
11,212✔
102
                return log_full_errno(log_level, SYNTHETIC_ERRNO(EINVAL),
×
103
                                      "%s: templates not allowed for %s units, rejecting.",
104
                                      filename, unit_type_to_string(src_unit_type));
105

106
        /* dst checks */
107

108
        if (streq(src, dst))
11,212✔
109
                return log_debug_errno(SYNTHETIC_ERRNO(ELOOP),
1,698✔
110
                                       "%s: unit self-alias: %s → %s, ignoring.",
111
                                       filename, src, dst);
112

113
        dst_name_type = unit_name_to_instance(dst, &dst_instance);
9,514✔
114
        if (dst_name_type < 0)
9,514✔
115
                return log_full_errno(log_level, dst_name_type == -EINVAL ? SYNTHETIC_ERRNO(EXDEV) : dst_name_type,
1✔
116
                                      "%s points to \"%s\" which is not a valid unit name: %m",
117
                                      filename, dst);
118

119
        if (!(dst_name_type == src_name_type ||
9,513✔
120
              (src_name_type == UNIT_NAME_INSTANCE && dst_name_type == UNIT_NAME_TEMPLATE)))
114✔
121
                return log_full_errno(log_level, SYNTHETIC_ERRNO(EXDEV),
13✔
122
                                      "%s: symlink target name type \"%s\" does not match source, rejecting.",
123
                                      filename, dst);
124

125
        if (dst_name_type == UNIT_NAME_INSTANCE) {
9,399✔
126
                assert(src_instance);
13✔
127
                assert(dst_instance);
13✔
128
                if (!streq(src_instance, dst_instance))
13✔
129
                        return log_full_errno(log_level, SYNTHETIC_ERRNO(EXDEV),
4✔
130
                                              "%s: unit symlink target \"%s\" instance name doesn't match, rejecting.",
131
                                              filename, dst);
132
        }
133

134
        dst_unit_type = unit_name_to_type(dst);
9,496✔
135
        if (dst_unit_type != src_unit_type)
9,496✔
136
                return log_full_errno(log_level, SYNTHETIC_ERRNO(EXDEV),
11✔
137
                                      "%s: symlink target \"%s\" has incompatible suffix, rejecting.",
138
                                      filename, dst);
139

140
        return 0;
141
}
142

143
#define FOLLOW_MAX 8
144

145
static int unit_ids_map_get(
354,124✔
146
                Hashmap *unit_ids_map,
147
                const char *unit_name,
148
                const char **ret_fragment_path) {
149

150
        /* Resolve recursively until we hit an absolute path, i.e. a non-aliased unit.
151
         *
152
         * We distinguish the case where unit_name was not found in the hashmap at all, and the case where
153
         * some symlink was broken.
154
         *
155
         * If a symlink target points to an instance name, then we also check for the template. */
156

157
        const char *id = NULL;
354,124✔
158
        int r;
354,124✔
159

160
        for (unsigned n = 0; n < FOLLOW_MAX; n++) {
363,364✔
161
                const char *t = hashmap_get(unit_ids_map, id ?: unit_name);
363,364✔
162
                if (!t) {
363,364✔
163
                        _cleanup_free_ char *template = NULL;
56,774✔
164

165
                        if (!id)
56,774✔
166
                                return -ENOENT;
167

168
                        r = unit_name_template(id, &template);
×
169
                        if (r == -EINVAL)
×
170
                                return -ENXIO; /* we failed to find the symlink target */
171
                        if (r < 0)
×
172
                                return log_error_errno(r, "Failed to determine template name for %s: %m", id);
×
173

174
                        t = hashmap_get(unit_ids_map, template);
×
175
                        if (!t)
×
176
                                return -ENXIO;
177

178
                        /* We successfully switched from instanced name to a template, let's continue */
179
                }
180

181
                if (path_is_absolute(t)) {
306,590✔
182
                        if (ret_fragment_path)
297,350✔
183
                                *ret_fragment_path = t;
297,350✔
184
                        return 0;
297,350✔
185
                }
186

187
                id = t;
9,240✔
188
        }
189

190
        return -ELOOP;
191
}
192

193
static bool lookup_paths_mtime_exclude(const LookupPaths *lp, const char *path) {
1,254,115✔
194
        /* Paths that are under our exclusive control. Users shall not alter those directly. */
195

196
        return streq_ptr(path, lp->generator) ||
1,254,115✔
197
               streq_ptr(path, lp->generator_early) ||
1,172,748✔
198
               streq_ptr(path, lp->generator_late) ||
1,091,381✔
199
               streq_ptr(path, lp->transient) ||
1,010,014✔
200
               streq_ptr(path, lp->persistent_control) ||
2,182,762✔
201
               streq_ptr(path, lp->runtime_control);
847,280✔
202
}
203

204
#define HASH_KEY SD_ID128_MAKE(4e,86,1b,e3,39,b3,40,46,98,5d,b8,11,34,8f,c3,c1)
205

206
bool lookup_paths_timestamp_hash_same(const LookupPaths *lp, uint64_t timestamp_hash, uint64_t *ret_new) {
82,082✔
207
        struct siphash state;
82,082✔
208

209
        siphash24_init(&state, HASH_KEY.bytes);
82,082✔
210

211
        STRV_FOREACH(dir, lp->search_path) {
1,336,197✔
212
                struct stat st;
1,254,115✔
213

214
                if (lookup_paths_mtime_exclude(lp, *dir))
1,254,115✔
215
                        continue;
968,942✔
216

217
                /* Determine the latest lookup path modification time */
218
                if (stat(*dir, &st) < 0) {
765,913✔
219
                        if (errno == ENOENT)
480,740✔
220
                                continue;
480,740✔
221

222
                        log_debug_errno(errno, "Failed to stat %s, ignoring: %m", *dir);
×
223
                        continue;
×
224
                }
225

226
                siphash24_compress_usec_t(timespec_load(&st.st_mtim), &state);
285,173✔
227
        }
228

229
        uint64_t updated = siphash24_finalize(&state);
82,082✔
230
        if (ret_new)
82,082✔
231
                *ret_new = updated;
79,446✔
232
        if (updated != timestamp_hash)
82,082✔
233
                log_debug("Modification times have changed, need to update cache.");
722✔
234
        return updated == timestamp_hash;
82,082✔
235
}
236

237
static int directory_name_is_valid(const char *name) {
25,362✔
238

239
        /* Accept a directory whose name is a valid unit file name ending in .wants/, .requires/,
240
         * .upholds/ or .d/ */
241

242
        FOREACH_STRING(suffix, ".wants", ".requires", ".upholds", ".d") {
68,299✔
243
                _cleanup_free_ char *chopped = NULL;
19,835✔
244
                const char *e;
62,772✔
245

246
                e = endswith(name, suffix);
62,772✔
247
                if (!e)
62,772✔
248
                        continue;
42,937✔
249

250
                chopped = strndup(name, e - name);
19,835✔
251
                if (!chopped)
19,835✔
252
                        return log_oom();
×
253

254
                if (unit_name_is_valid(chopped, UNIT_NAME_ANY) ||
20,356✔
255
                    unit_type_from_string(chopped) >= 0)
521✔
256
                        return true;
257
        }
258

259
        return false;
5,527✔
260
}
261

262
int unit_file_resolve_symlink(
12,459✔
263
                const char *root_dir,
264
                char **search_path,
265
                const char *dir,
266
                int dirfd,
267
                const char *filename,
268
                bool resolve_destination_target,
269
                char **ret_destination) {
270

271
        _cleanup_free_ char *target = NULL, *simplified = NULL, *dst = NULL, *_dir = NULL, *_filename = NULL;
12,459✔
272
        int r;
12,459✔
273

274
        /* This can be called with either dir+dirfd valid and filename just a name,
275
         * or !dir && dirfd==AT_FDCWD, and filename being a full path.
276
         *
277
         * If resolve_destination_target is true, an absolute path will be returned.
278
         * If not, an absolute path is returned for linked unit files, and a relative
279
         * path otherwise.
280
         *
281
         * Returns an error, false if this is an alias, true if it's a linked unit file. */
282

283
        assert(filename);
12,459✔
284
        assert(ret_destination);
12,459✔
285
        assert(dir || path_is_absolute(filename));
13,075✔
286
        assert(dirfd >= 0 || dirfd == AT_FDCWD);
12,459✔
287

288
        r = readlinkat_malloc(dirfd, filename, &target);
12,459✔
289
        if (r < 0)
12,459✔
290
                return log_warning_errno(r, "Failed to read symlink %s%s%s: %m",
×
291
                                         dir, dir ? "/" : "", filename);
292

293
        if (!dir) {
12,459✔
294
                r = path_extract_directory(filename, &_dir);
616✔
295
                if (r < 0)
616✔
296
                        return r;
297
                dir = _dir;
616✔
298

299
                r = path_extract_filename(filename, &_filename);
616✔
300
                if (r < 0)
616✔
301
                        return r;
302
                if (r == O_DIRECTORY)
616✔
303
                        return log_warning_errno(SYNTHETIC_ERRNO(EISDIR),
×
304
                                                 "Unexpected path to a directory \"%s\", refusing.", filename);
305
                filename = _filename;
616✔
306
        }
307

308
        bool is_abs = path_is_absolute(target);
12,459✔
309
        if (root_dir || !is_abs) {
12,459✔
310
                char *target_abs = path_join(is_abs ? root_dir : dir, target);
16,422✔
311
                if (!target_abs)
8,228✔
312
                        return log_oom();
12,459✔
313

314
                free_and_replace(target, target_abs);
8,228✔
315
        }
316

317
        /* Get rid of "." and ".." components in target path */
318
        r = chase(target, root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL);
12,459✔
319
        if (r < 0)
12,459✔
320
                return log_warning_errno(r, "Failed to resolve symlink %s/%s pointing to %s: %m",
×
321
                                         dir, filename, target);
322

323
        assert(path_is_absolute(simplified));
12,459✔
324

325
        /* Check if the symlink remain inside of our search path.
326
         * If yes, it is an alias. Verify that it is valid.
327
         *
328
         * If no, then this is a linked unit file or mask, and we don't care about the target name
329
         * when loading units, and we return the link *source* (resolve_destination_target == false);
330
         * When this is called for installation purposes, we want the final destination,
331
         * so we return the *target*.
332
         */
333
        const char *tail = path_startswith_strv(simplified, search_path);
12,459✔
334
        if (tail) {  /* An alias */
12,459✔
335
                _cleanup_free_ char *target_name = NULL;
11,347✔
336

337
                r = path_extract_filename(simplified, &target_name);
11,347✔
338
                if (r < 0)
11,347✔
339
                        return r;
340

341
                r = unit_validate_alias_symlink_or_warn(LOG_NOTICE, filename, simplified);
11,347✔
342
                if (r < 0)
11,347✔
343
                        return r;
344
                if (is_path(tail))
9,367✔
345
                        log_warning("Suspicious symlink %s/%s %s %s, treating as alias.",
×
346
                                    dir, filename, glyph(GLYPH_ARROW_RIGHT), simplified);
347

348
                dst = resolve_destination_target ? TAKE_PTR(simplified) : TAKE_PTR(target_name);
9,367✔
349

350
        } else {
351
                log_debug("Linked unit file: %s/%s %s %s", dir, filename, glyph(GLYPH_ARROW_RIGHT), simplified);
1,112✔
352

353
                if (resolve_destination_target)
1,112✔
354
                        dst = TAKE_PTR(simplified);
166✔
355
                else {
356
                        dst = path_join(dir, filename);
946✔
357
                        if (!dst)
946✔
358
                                return log_oom();
×
359
                }
360
        }
361

362
        *ret_destination = TAKE_PTR(dst);
10,479✔
363
        return !tail;  /* true if linked unit file */
10,479✔
364
}
365

366
int unit_file_build_name_map(
79,459✔
367
                const LookupPaths *lp,
368
                uint64_t *cache_timestamp_hash,
369
                Hashmap **unit_ids_map,
370
                Hashmap **unit_names_map,
371
                Set **path_cache) {
372

373
        /* Build two mappings: any name → main unit (i.e. the end result of symlink resolution), unit name →
374
         * all aliases (i.e. the entry for a given key is a list of all names which point to this key). The
375
         * key is included in the value iff we saw a file or symlink with that name. In other words, if we
376
         * have a key, but it is not present in the value for itself, there was an alias pointing to it, but
377
         * the unit itself is not loadable.
378
         *
379
         * At the same, build a cache of paths where to find units. The non-const parameters are for input
380
         * and output. Existing contents will be freed before the new contents are stored.
381
         */
382

383
        _cleanup_hashmap_free_ Hashmap *ids = NULL, *names = NULL;
79,459✔
384
        _cleanup_set_free_ Set *paths = NULL;
79,459✔
385
        _cleanup_strv_free_ char **expanded_search_path = NULL;
×
386
        uint64_t timestamp_hash;
79,459✔
387
        int r;
79,459✔
388

389
        /* Before doing anything, check if the timestamp hash that was passed is still valid.
390
         * If yes, do nothing. */
391
        if (cache_timestamp_hash &&
158,905✔
392
            lookup_paths_timestamp_hash_same(lp, *cache_timestamp_hash, &timestamp_hash))
79,446✔
393
                return 0;
394

395
        /* The timestamp hash is now set based on the mtimes from before when we start reading files.
396
         * If anything is modified concurrently, we'll consider the cache outdated. */
397

398
        if (path_cache) {
735✔
399
                paths = set_new(&path_hash_ops_free);
723✔
400
                if (!paths)
723✔
401
                        return log_oom();
×
402
        }
403

404
        /* Go over all our search paths, chase their symlinks and store the result in the
405
         * expanded_search_path list.
406
         *
407
         * This is important for cases where any of the unit directories itself are symlinks into other
408
         * directories and would therefore cause all of the unit files to be recognized as linked units.
409
         *
410
         * This is important for distributions such as NixOS where most paths in /etc/ are symlinks to some
411
         * other location on the filesystem (e.g.  into /nix/store/).
412
         *
413
         * Search paths are ordered by priority (highest first), and we need to maintain this order.
414
         * If a resolved path is already in the list, we don't need to include.
415
         *
416
         * Note that we build a list that contains both the original paths and the resolved symlinks:
417
         * we need the latter for the case where the directory is symlinked, as described above, and
418
         * the former for the case where some unit file alias is a dangling symlink that points to one
419
         * of the "original" directories (and can't be followed).
420
         */
421
        STRV_FOREACH(dir, lp->search_path) {
10,611✔
422
                _cleanup_free_ char *resolved_dir = NULL;
9,876✔
423

424
                r = strv_extend(&expanded_search_path, *dir);
9,876✔
425
                if (r < 0)
9,876✔
426
                        return log_oom();
×
427

428
                r = chase(*dir, NULL, 0, &resolved_dir, NULL);
9,876✔
429
                if (r < 0) {
9,876✔
430
                        if (r != -ENOENT)
7,203✔
431
                                log_warning_errno(r, "Failed to resolve symlink %s, ignoring: %m", *dir);
×
432
                        continue;
7,203✔
433
                }
434

435
                if (strv_contains(expanded_search_path, resolved_dir))
2,673✔
436
                        continue;
2,670✔
437

438
                if (strv_consume(&expanded_search_path, TAKE_PTR(resolved_dir)) < 0)
3✔
439
                        return log_oom();
×
440
        }
441

442
        STRV_FOREACH(dir, lp->search_path) {
10,611✔
443
                _cleanup_closedir_ DIR *d = NULL;
89,335✔
444

445
                d = opendir(*dir);
9,876✔
446
                if (!d) {
9,876✔
447
                        if (errno != ENOENT)
7,203✔
448
                                log_warning_errno(errno, "Failed to open \"%s\", ignoring: %m", *dir);
×
449
                        continue;
7,203✔
450
                }
451

452
                FOREACH_DIRENT_ALL(de, d, log_warning_errno(errno, "Failed to read \"%s\", ignoring: %m", *dir)) {
368,793✔
453
                        _unused_ _cleanup_free_ char *_filename_free = NULL;
269,370✔
454
                        char *filename;
366,120✔
455
                        _cleanup_free_ char *dst = NULL;
96,750✔
456
                        bool symlink_to_dir = false;
366,120✔
457

458
                        /* We only care about valid units and dirs with certain suffixes, let's ignore the
459
                         * rest. */
460

461
                        if (de->d_type == DT_REG) {
366,120✔
462

463
                                /* Accept a regular file whose name is a valid unit file name. */
464
                                if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
326,811✔
465
                                        continue;
65,354✔
466

467
                        } else if (de->d_type == DT_DIR) {
39,309✔
468

469
                                if (!paths) /* Skip directories early unless path_cache is requested */
25,890✔
470
                                        continue;
528✔
471

472
                                r = directory_name_is_valid(de->d_name);
25,362✔
473
                                if (r < 0)
25,362✔
474
                                        return r;
475
                                if (r == 0)
25,362✔
476
                                        continue;
5,527✔
477

478
                        } else if (de->d_type == DT_LNK) {
13,419✔
479

480
                                /* Accept a symlink file whose name is a valid unit file name or
481
                                 * ending in .wants/, .requires/ or .d/. */
482

483
                                if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) {
13,419✔
484
                                        _cleanup_free_ char *target = NULL;
×
485

486
                                        if (!paths) /* Skip symlink to a directory early unless path_cache is requested */
×
487
                                                continue;
×
488

489
                                        r = directory_name_is_valid(de->d_name);
×
490
                                        if (r < 0)
×
491
                                                return r;
×
492
                                        if (r == 0)
×
493
                                                continue;
×
494

495
                                        r = readlinkat_malloc(dirfd(d), de->d_name, &target);
×
496
                                        if (r < 0) {
×
497
                                                log_warning_errno(r, "Failed to read symlink %s/%s, ignoring: %m",
×
498
                                                                  *dir, de->d_name);
499
                                                continue;
×
500
                                        }
501

502
                                        r = is_dir(target, /* follow = */ true);
×
503
                                        if (r <= 0)
×
504
                                                continue;
×
505

506
                                        symlink_to_dir = true;
×
507
                                }
508

509
                        } else
510
                                continue;
×
511

512
                        filename = path_join(*dir, de->d_name);
294,711✔
513
                        if (!filename)
294,711✔
514
                                return log_oom();
×
515

516
                        if (paths) {
294,711✔
517
                                r = set_put(paths, filename);
290,340✔
518
                                if (r < 0)
290,340✔
519
                                        return log_oom();
×
520
                                if (r == 0)
290,340✔
521
                                        _filename_free = filename; /* Make sure we free the filename. */
×
522
                        } else
523
                                _filename_free = filename; /* Make sure we free the filename. */
524

525
                        if (de->d_type == DT_DIR || (de->d_type == DT_LNK && symlink_to_dir))
294,711✔
526
                                continue;
19,835✔
527

528
                        assert(IN_SET(de->d_type, DT_REG, DT_LNK));
274,876✔
529

530
                        /* search_path is ordered by priority (highest first). If the name is already mapped
531
                         * to something (incl. itself), it means that we have already seen it, and we should
532
                         * ignore it here. */
533
                        if (hashmap_contains(ids, de->d_name))
274,876✔
534
                                continue;
3,527✔
535

536
                        if (de->d_type == DT_LNK) {
271,349✔
537
                                /* We don't explicitly check for alias loops here. unit_ids_map_get() which
538
                                 * limits the number of hops should be used to access the map. */
539

540
                                r = unit_file_resolve_symlink(lp->root_dir, expanded_search_path,
11,843✔
541
                                                              *dir, dirfd(d), de->d_name,
542
                                                              /* resolve_destination_target= */ false,
543
                                                              &dst);
544
                                if (r == -ENOMEM)
11,843✔
545
                                        return r;
546
                                if (r < 0)  /* we ignore other errors here */
11,843✔
547
                                        continue;
1,979✔
548

549
                        } else {
550
                                dst = TAKE_PTR(_filename_free); /* Grab the copy we made previously, if available. */
259,506✔
551
                                if (!dst) {
259,506✔
552
                                        dst = strdup(filename);
255,409✔
553
                                        if (!dst)
255,409✔
554
                                                return log_oom();
×
555
                                }
556

557
                                log_debug("%s: normal unit file: %s", __func__, dst);
259,506✔
558
                        }
559

560
                        _cleanup_free_ char *key = strdup(de->d_name);
269,370✔
561
                        if (!key)
269,370✔
562
                                return log_oom();
×
563

564
                        r = hashmap_ensure_put(&ids, &string_hash_ops_free_free, key, dst);
269,370✔
565
                        if (r < 0)
269,370✔
566
                                return log_warning_errno(r, "Failed to add entry to hashmap (%s%s%s): %m",
×
567
                                                         de->d_name, glyph(GLYPH_ARROW_RIGHT), dst);
568
                        key = dst = NULL;
269,370✔
569
                }
570
        }
571

572
        /* Let's also put the names in the reverse db. */
573
        const char *dummy, *src;
735✔
574
        HASHMAP_FOREACH_KEY(dummy, src, ids) {
270,105✔
575
                _cleanup_free_ char *inst = NULL, *dst_inst = NULL;
269,370✔
576
                const char *dst;
269,370✔
577

578
                r = unit_ids_map_get(ids, src, &dst);
269,370✔
579
                if (r < 0)
269,370✔
580
                        continue;
×
581

582
                if (null_or_empty_path(dst) != 0)
269,370✔
583
                        continue;
939✔
584

585
                dst = basename(dst);
268,431✔
586

587
                /* If we have an symlink from an instance name to a template name, it is an alias just for
588
                 * this specific instance, foo@id.service ↔ template@id.service. */
589
                if (unit_name_is_valid(dst, UNIT_NAME_TEMPLATE)) {
268,431✔
590
                        UnitNameFlags t = unit_name_to_instance(src, &inst);
34,998✔
591
                        if (t < 0)
34,998✔
592
                                return log_error_errno(t, "Failed to extract instance part from %s: %m", src);
×
593
                        if (t == UNIT_NAME_INSTANCE) {
34,998✔
594
                                r = unit_name_replace_instance(dst, inst, &dst_inst);
90✔
595
                                if (r < 0) {
90✔
596
                                        /* This might happen e.g. if the combined length is too large.
597
                                         * Let's not make too much of a fuss. */
598
                                        log_debug_errno(r, "Failed to build alias name (%s + %s), ignoring: %m",
×
599
                                                        dst, inst);
600
                                        continue;
×
601
                                }
602

603
                                dst = dst_inst;
90✔
604
                        }
605
                }
606

607
                r = string_strv_hashmap_put(&names, dst, src);
268,431✔
608
                if (r < 0)
268,431✔
609
                        return log_warning_errno(r, "Failed to add entry to hashmap (%s%s%s): %m",
×
610
                                                 dst, glyph(GLYPH_ARROW_RIGHT), src);
611
        }
612

613
        if (cache_timestamp_hash)
735✔
614
                *cache_timestamp_hash = timestamp_hash;
722✔
615

616
        hashmap_free_and_replace(*unit_ids_map, ids);
735✔
617
        hashmap_free_and_replace(*unit_names_map, names);
735✔
618
        if (path_cache)
735✔
619
                set_free_and_replace(*path_cache, paths);
723✔
620

621
        return 1;
622
}
623

624
int unit_file_remove_from_name_map(
129✔
625
                const LookupPaths *lp,
626
                uint64_t *cache_timestamp_hash,
627
                Hashmap **unit_ids_map,
628
                Hashmap **unit_names_map,
629
                Set **path_cache,
630
                const char *path) {
631

632
        int r;
129✔
633

634
        assert(path);
129✔
635

636
        /* This assumes the specified path is already removed, and drops the relevant entries from the maps. */
637

638
        /* If one of the lookup paths we are monitoring is already changed, let's rebuild the map. Then, the
639
         * new map should not contain entries relevant to the specified path. */
640
        r = unit_file_build_name_map(lp, cache_timestamp_hash, unit_ids_map, unit_names_map, path_cache);
129✔
641
        if (r != 0)
129✔
642
                return r;
129✔
643

644
        /* If not, drop the relevant entries. */
645

646
        _cleanup_free_ char *name = NULL;
129✔
647
        r = path_extract_filename(path, &name);
129✔
648
        if (r < 0)
129✔
649
                return log_warning_errno(r, "Failed to extract file name from '%s': %m", path);
×
650

651
        _unused_ _cleanup_free_ char *key = NULL;
129✔
652
        free(hashmap_remove2(*unit_ids_map, name, (void**) &key));
129✔
653
        string_strv_hashmap_remove(*unit_names_map, name, name);
129✔
654
        free(set_remove(*path_cache, path));
129✔
655

656
        return 0;
129✔
657
}
658

659
static int add_name(
111,593✔
660
                const char *unit_name,
661
                Set **names,
662
                const char *name) {
663
        int r;
111,593✔
664

665
        assert(names);
111,593✔
666
        assert(name);
111,593✔
667

668
        r = set_put_strdup(names, name);
111,593✔
669
        if (r < 0)
111,593✔
670
                return r;
671
        if (r > 0 && !streq(unit_name, name))
111,593✔
672
                log_debug("Unit %s has alias %s.", unit_name, name);
4,032✔
673
        return r;
674
}
675

676
static int add_names(
82,159✔
677
                Hashmap *unit_ids_map,
678
                Hashmap *unit_name_map,
679
                const char *unit_name,
680
                const char *fragment_basename,  /* Only set when adding additional names based on fragment path */
681
                UnitNameFlags name_type,
682
                const char *instance,
683
                Set **names,
684
                const char *name) {
685

686
        char **aliases;
82,159✔
687
        int r;
82,159✔
688

689
        assert(name_type == UNIT_NAME_PLAIN || instance);
82,159✔
690

691
        /* The unit has its own name if it's not a template. If we're looking at a fragment, the fragment
692
         * name (possibly with instance inserted), is also always one of the unit names. */
693
        if (name_type != UNIT_NAME_TEMPLATE) {
82,159✔
694
                r = add_name(unit_name, names, name);
82,154✔
695
                if (r < 0)
82,154✔
696
                        return r;
697
        }
698

699
        /* Add any aliases of the name to the set of names.
700
         *
701
         * We don't even need to know which fragment we will use. The unit_name_map should return the same
702
         * set of names for any of the aliases. */
703
        aliases = hashmap_get(unit_name_map, name);
82,159✔
704
        STRV_FOREACH(alias, aliases) {
111,598✔
705
                if (name_type == UNIT_NAME_INSTANCE && unit_name_is_valid(*alias, UNIT_NAME_TEMPLATE)) {
29,439✔
706
                        _cleanup_free_ char *inst = NULL;
2,679✔
707
                        const char *inst_fragment = NULL;
2,679✔
708

709
                        r = unit_name_replace_instance(*alias, instance, &inst);
2,679✔
710
                        if (r < 0)
2,679✔
711
                                return log_debug_errno(r, "Cannot build instance name %s + %s: %m",
×
712
                                                       *alias, instance);
713

714
                        /* Exclude any aliases that point in some other direction.
715
                         *
716
                         * See https://github.com/systemd/systemd/pull/13119#discussion_r308145418. */
717
                        r = unit_ids_map_get(unit_ids_map, inst, &inst_fragment);
2,679✔
718
                        if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
2,679✔
719
                                return log_debug_errno(r, "Cannot find instance fragment %s: %m", inst);
×
720

721
                        if (inst_fragment &&
2,679✔
722
                            fragment_basename &&
×
723
                            !path_equal_filename(inst_fragment, fragment_basename)) {
×
724
                                log_debug("Instance %s has fragment %s and is not an alias of %s.",
×
725
                                          inst, inst_fragment, unit_name);
726
                                continue;
×
727
                        }
728

729
                        r = add_name(unit_name, names, inst);
2,679✔
730
                } else
731
                        r = add_name(unit_name, names, *alias);
26,760✔
732
                if (r < 0)
29,439✔
733
                        return r;
734
        }
735

736
        return 0;
737
}
738

739
int unit_file_find_fragment(
79,329✔
740
                Hashmap *unit_ids_map,
741
                Hashmap *unit_name_map,
742
                const char *unit_name,
743
                const char **ret_fragment_path,
744
                Set **ret_names) {
745

746
        const char *fragment = NULL;
79,329✔
747
        _cleanup_free_ char *template = NULL, *instance = NULL;
79,329✔
748
        _cleanup_set_free_ Set *names = NULL;
79,329✔
749
        int r;
79,329✔
750

751
        /* Finds a fragment path, and returns the set of names:
752
         * if we have …/foo.service and …/foo-alias.service→foo.service,
753
         * and …/foo@.service and …/foo-alias@.service→foo@.service,
754
         * and …/foo@inst.service,
755
         * this should return:
756
         * foo.service → …/foo.service, {foo.service, foo-alias.service},
757
         * foo-alias.service → …/foo.service, {foo.service, foo-alias.service},
758
         * foo@.service → …/foo@.service, {foo@.service, foo-alias@.service},
759
         * foo-alias@.service → …/foo@.service, {foo@.service, foo-alias@.service},
760
         * foo@bar.service → …/foo@.service, {foo@bar.service, foo-alias@bar.service},
761
         * foo-alias@bar.service → …/foo@.service, {foo@bar.service, foo-alias@bar.service},
762
         * foo-alias@inst.service → …/foo@inst.service, {foo@inst.service, foo-alias@inst.service}.
763
         */
764

765
        UnitNameFlags name_type = unit_name_to_instance(unit_name, &instance);
79,329✔
766
        if (name_type < 0)
79,329✔
767
                return name_type;
768

769
        if (ret_names) {
79,329✔
770
                r = add_names(unit_ids_map, unit_name_map, unit_name, NULL, name_type, instance, &names, unit_name);
79,319✔
771
                if (r < 0)
79,319✔
772
                        return r;
773
        }
774

775
        /* First try to load fragment under the original name */
776
        r = unit_ids_map_get(unit_ids_map, unit_name, &fragment);
79,329✔
777
        if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
79,329✔
778
                return log_debug_errno(r, "Cannot load unit %s: %m", unit_name);
×
779

780
        if (!fragment && name_type == UNIT_NAME_INSTANCE) {
79,329✔
781
                /* Look for a fragment under the template name */
782

783
                r = unit_name_template(unit_name, &template);
2,746✔
784
                if (r < 0)
2,746✔
785
                        return log_debug_errno(r, "Failed to determine template name: %m");
×
786

787
                r = unit_ids_map_get(unit_ids_map, template, &fragment);
2,746✔
788
                if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
2,746✔
789
                        return log_debug_errno(r, "Cannot load template %s: %m", template);
×
790
        }
791

792
        if (fragment && ret_names) {
79,329✔
793
                _cleanup_free_ char *fragment_basename = NULL;
27,971✔
794
                r = path_extract_filename(fragment, &fragment_basename);
27,971✔
795
                if (r < 0)
27,971✔
796
                        return r;
797

798
                if (!streq(fragment_basename, unit_name)) {
27,971✔
799
                        /* Add names based on the fragment name to the set of names */
800
                        r = add_names(unit_ids_map, unit_name_map, unit_name, fragment_basename, name_type, instance, &names, fragment_basename);
2,840✔
801
                        if (r < 0)
2,840✔
802
                                return r;
803
                }
804
        }
805

806
        *ret_fragment_path = fragment;
79,329✔
807
        if (ret_names)
79,329✔
808
                *ret_names = TAKE_PTR(names);
79,319✔
809

810
        return 0;
811
}
812

813
static const char * const rlmap[] = {
814
        "emergency", SPECIAL_EMERGENCY_TARGET,
815
        "-b",        SPECIAL_EMERGENCY_TARGET,
816
        "rescue",    SPECIAL_RESCUE_TARGET,
817
        "single",    SPECIAL_RESCUE_TARGET,
818
        "-s",        SPECIAL_RESCUE_TARGET,
819
        "s",         SPECIAL_RESCUE_TARGET,
820
        "S",         SPECIAL_RESCUE_TARGET,
821
        "1",         SPECIAL_RESCUE_TARGET,
822
        "2",         SPECIAL_MULTI_USER_TARGET,
823
        "3",         SPECIAL_MULTI_USER_TARGET,
824
        "4",         SPECIAL_MULTI_USER_TARGET,
825
        "5",         SPECIAL_GRAPHICAL_TARGET,
826
        NULL
827
};
828

829
static const char * const rlmap_initrd[] = {
830
        "emergency", SPECIAL_EMERGENCY_TARGET,
831
        "rescue",    SPECIAL_RESCUE_TARGET,
832
        NULL
833
};
834

835
const char* runlevel_to_target(const char *word) {
208✔
836
        const char * const *rlmap_ptr;
208✔
837

838
        if (!word)
208✔
839
                return NULL;
840

841
        if (in_initrd()) {
206✔
842
                word = startswith(word, "rd.");
22✔
843
                if (!word)
22✔
844
                        return NULL;
845

846
                rlmap_ptr = rlmap_initrd;
847
        } else
848
                rlmap_ptr = rlmap;
849

850
        STRV_FOREACH_PAIR(rl, target, rlmap_ptr)
2,391✔
851
                if (streq(word, *rl))
2,208✔
852
                        return *target;
853

854
        return NULL;
855
}
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