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

systemd / systemd / 21652912420

03 Feb 2026 07:37PM UTC coverage: 72.767% (-0.04%) from 72.804%
21652912420

push

github

web-flow
Introduce name_to_handle_at_u64() helper function and use it for getting pidfd ID and cgroup ID (#40500)

26 of 28 new or added lines in 8 files covered. (92.86%)

2713 existing lines in 47 files now uncovered.

311340 of 427861 relevant lines covered (72.77%)

1144580.1 hits per line

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

85.67
/src/shared/install.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <fcntl.h>
4
#include <fnmatch.h>
5
#include <stdio.h>
6
#include <unistd.h>
7

8
#include "alloc-util.h"
9
#include "bus-common-errors.h"
10
#include "chase.h"
11
#include "conf-files.h"
12
#include "conf-parser.h"
13
#include "constants.h"
14
#include "dirent-util.h"
15
#include "errno-util.h"
16
#include "extract-word.h"
17
#include "fd-util.h"
18
#include "fileio.h"
19
#include "fs-util.h"
20
#include "glyph-util.h"
21
#include "hashmap.h"
22
#include "install.h"
23
#include "install-printf.h"
24
#include "log.h"
25
#include "mkdir-label.h"
26
#include "path-lookup.h"
27
#include "path-util.h"
28
#include "rm-rf.h"
29
#include "set.h"
30
#include "special.h"
31
#include "stat-util.h"
32
#include "string-table.h"
33
#include "string-util.h"
34
#include "strv.h"
35
#include "unit-file.h"
36
#include "unit-name.h"
37

38
#define UNIT_FILE_FOLLOW_SYMLINK_MAX 64
39

40
typedef struct {
41
        RuntimeScope scope;
42
        OrderedHashmap *will_process;
43
        OrderedHashmap *have_processed;
44
} InstallContext;
45

46
struct UnitFilePresetRule {
47
        char *pattern;
48
        PresetAction action;
49
        char **instances;
50
};
51

52
/* NB! strings use past tense. */
53
static const char *const preset_action_past_tense_table[_PRESET_ACTION_MAX] = {
54
        [PRESET_UNKNOWN] = "unknown",
55
        [PRESET_ENABLE]  = "enabled",
56
        [PRESET_DISABLE] = "disabled",
57
        [PRESET_IGNORE]  = "ignored",
58
};
59

60
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(preset_action_past_tense, PresetAction);
3,408✔
61

62
static bool install_info_has_rules(const InstallInfo *i) {
2,280✔
63
        assert(i);
2,280✔
64

65
        return !strv_isempty(i->aliases) ||
2,280✔
66
               !strv_isempty(i->wanted_by) ||
2,265✔
67
               !strv_isempty(i->required_by) ||
1,950✔
68
               !strv_isempty(i->upheld_by);
1,948✔
69
}
70

71
static bool install_info_has_also(const InstallInfo *i) {
1,948✔
72
        assert(i);
1,948✔
73

74
        return !strv_isempty(i->also);
1,948✔
75
}
76

77
static void unit_file_preset_rule_done(UnitFilePresetRule *rule) {
50,006✔
78
        assert(rule);
50,006✔
79

80
        free(rule->pattern);
50,006✔
81
        strv_free(rule->instances);
50,006✔
82
}
50,006✔
83

84
void unit_file_presets_done(UnitFilePresets *p) {
919✔
85
        if (!p)
919✔
86
                return;
87

88
        FOREACH_ARRAY(rule, p->rules, p->n_rules)
17,688✔
89
                unit_file_preset_rule_done(rule);
16,769✔
90

91
        free(p->rules);
919✔
92
        p->n_rules = 0;
919✔
93
}
94

95
static const char *const install_mode_table[_INSTALL_MODE_MAX] = {
96
        [INSTALL_MODE_REGULAR] = "regular",
97
        [INSTALL_MODE_LINKED]  = "linked",
98
        [INSTALL_MODE_ALIAS]   = "alias",
99
        [INSTALL_MODE_MASKED]  = "masked",
100
};
101

102
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(install_mode, InstallMode);
2✔
103

104
static int in_search_path(const LookupPaths *lp, const char *path) {
635✔
105
        _cleanup_free_ char *parent = NULL;
635✔
106
        int r;
635✔
107

108
        /* Check if 'path' is in lp->search_path. */
109

110
        assert(lp);
635✔
111
        assert(path);
635✔
112

113
        r = path_extract_directory(path, &parent);
635✔
114
        if (r < 0)
635✔
115
                return r;
116

117
        return path_strv_contains(lp->search_path, parent);
635✔
118
}
119

120
static bool underneath_search_path(const LookupPaths *lp, const char *path) {
1✔
121
        /* Check if 'path' is underneath lp->search_path. */
122

123
        assert(lp);
1✔
124
        assert(path);
1✔
125

126
        return path_startswith_strv(path, lp->search_path);
1✔
127
}
128

129
static const char* skip_root(const char *root_dir, const char *path) {
1,306✔
130
        assert(path);
1,306✔
131

132
        if (!root_dir)
1,306✔
133
                return path;
134

135
        const char *e = path_startswith(path, root_dir);
100✔
136
        if (!e)
100✔
137
                return NULL;
138

139
        /* Make sure the returned path starts with a slash */
140
        if (e[0] != '/') {
98✔
141
                if (e == path || e[-1] != '/')
98✔
142
                        return NULL;
143

144
                e--;
98✔
145
        }
146

147
        return e;
148
}
149

150
static int path_is_generator(const LookupPaths *lp, const char *path) {
2,881✔
151
        _cleanup_free_ char *parent = NULL;
2,881✔
152
        int r;
2,881✔
153

154
        assert(lp);
2,881✔
155
        assert(path);
2,881✔
156

157
        r = path_extract_directory(path, &parent);
2,881✔
158
        if (r < 0)
2,881✔
159
                return r;
160

161
        return PATH_IN_SET(parent,
2,881✔
162
                           lp->generator,
163
                           lp->generator_early,
164
                           lp->generator_late);
165
}
166

167
static int path_is_transient(const LookupPaths *lp, const char *path) {
2,871✔
168
        _cleanup_free_ char *parent = NULL;
2,871✔
169
        int r;
2,871✔
170

171
        assert(lp);
2,871✔
172
        assert(path);
2,871✔
173

174
        r = path_extract_directory(path, &parent);
2,871✔
175
        if (r < 0)
2,871✔
176
                return r;
177

178
        return path_equal(parent, lp->transient);
2,871✔
179
}
180

UNCOV
181
static int path_is_control(const LookupPaths *lp, const char *path) {
×
UNCOV
182
        _cleanup_free_ char *parent = NULL;
×
UNCOV
183
        int r;
×
184

UNCOV
185
        assert(lp);
×
UNCOV
186
        assert(path);
×
187

188
        r = path_extract_directory(path, &parent);
×
189
        if (r < 0)
×
190
                return r;
191

192
        return PATH_IN_SET(parent,
×
193
                           lp->persistent_control,
194
                           lp->runtime_control);
195
}
196

197
static int path_is_config(const LookupPaths *lp, const char *path, bool check_parent) {
13✔
198
        _cleanup_free_ char *parent = NULL;
13✔
199
        int r;
13✔
200

201
        assert(lp);
13✔
202
        assert(path);
13✔
203

204
        /* Note that we do *not* have generic checks for /etc or /run in place, since with
205
         * them we couldn't discern configuration from transient or generated units */
206

207
        if (check_parent) {
13✔
208
                r = path_extract_directory(path, &parent);
13✔
209
                if (r < 0)
13✔
210
                        return r;
211

212
                path = parent;
13✔
213
        }
214

215
        return PATH_IN_SET(path,
13✔
216
                           lp->persistent_config,
217
                           lp->runtime_config);
218
}
219

220
static int path_is_runtime(const LookupPaths *lp, const char *path, bool check_parent) {
614✔
221
        _cleanup_free_ char *parent = NULL;
614✔
222
        const char *rpath;
614✔
223
        int r;
614✔
224

225
        assert(lp);
614✔
226
        assert(path);
614✔
227

228
        /* Everything in /run is considered runtime. On top of that we also add
229
         * explicit checks for the various runtime directories, as safety net. */
230

231
        rpath = skip_root(lp->root_dir, path);
614✔
232
        if (rpath && path_startswith(rpath, "/run"))
1,228✔
233
                return true;
234

235
        if (check_parent) {
595✔
236
                r = path_extract_directory(path, &parent);
12✔
237
                if (r < 0)
12✔
238
                        return r;
239

240
                path = parent;
12✔
241
        }
242

243
        return PATH_IN_SET(path,
595✔
244
                           lp->runtime_config,
245
                           lp->generator,
246
                           lp->generator_early,
247
                           lp->generator_late,
248
                           lp->transient,
249
                           lp->runtime_control);
250
}
251

252
static int path_is_vendor_or_generator(const LookupPaths *lp, const char *path) {
5✔
253
        const char *rpath;
5✔
254

255
        assert(lp);
5✔
256
        assert(path);
5✔
257

258
        rpath = skip_root(lp->root_dir, path);
5✔
259
        if (!rpath)
5✔
260
                return 0;
261

262
        if (path_startswith(rpath, "/usr"))
5✔
263
                return true;
264

265
        if (path_is_generator(lp, rpath))
1✔
266
                return true;
267

268
        return path_equal(rpath, SYSTEM_DATA_UNIT_DIR);
1✔
269
}
270

271
static const char* config_path_from_flags(const LookupPaths *lp, UnitFileFlags flags) {
40✔
272
        assert(lp);
40✔
273

274
        if (FLAGS_SET(flags, UNIT_FILE_PORTABLE))
40✔
UNCOV
275
                return FLAGS_SET(flags, UNIT_FILE_RUNTIME) ? lp->runtime_attached : lp->persistent_attached;
×
276
        else
277
                return FLAGS_SET(flags, UNIT_FILE_RUNTIME) ? lp->runtime_config : lp->persistent_config;
40✔
278
}
279

280
InstallChangeType install_changes_add(
1,282✔
281
                InstallChange **changes,
282
                size_t *n_changes,
283
                InstallChangeType type, /* INSTALL_CHANGE_SYMLINK, _UNLINK, _IS_MASKED, _IS_DANGLING, … if positive or errno if negative */
284
                const char *path,
285
                const char *source) {
286

287
        _cleanup_free_ char *p = NULL, *s = NULL;
1,282✔
288
        int r;
1,282✔
289

290
        assert(!changes == !n_changes);
1,282✔
291
        assert(INSTALL_CHANGE_TYPE_VALID(type));
1,282✔
292

293
        /* Message formatting requires <path> to be set. */
294
        assert(path);
1,282✔
295

296
        /* Register a change or error. Note that the return value may be the error
297
         * that was passed in, or -ENOMEM generated internally. */
298

299
        if (!changes)
1,282✔
300
                return type;
301

302
        if (!GREEDY_REALLOC(*changes, *n_changes + 1))
190✔
303
                return -ENOMEM;
304

305
        r = path_simplify_alloc(path, &p);
190✔
306
        if (r < 0)
190✔
307
                return r;
308

309
        r = path_simplify_alloc(source, &s);
190✔
310
        if (r < 0)
190✔
311
                return r;
312

313
        (*changes)[(*n_changes)++] = (InstallChange) {
190✔
314
                .type = type,
315
                .path = TAKE_PTR(p),
190✔
316
                .source = TAKE_PTR(s),
190✔
317
        };
318

319
        return type;
190✔
320
}
321

322
void install_changes_free(InstallChange *changes, size_t n_changes) {
129✔
323
        assert(changes || n_changes == 0);
129✔
324

325
        FOREACH_ARRAY(i, changes, n_changes) {
319✔
326
                free(i->path);
190✔
327
                free(i->source);
190✔
328
        }
329

330
        free(changes);
129✔
331
}
129✔
332

333
static void install_change_dump_success(const InstallChange *change) {
93✔
334
        assert(change);
93✔
335
        assert(change->path);
93✔
336

337
        switch (change->type) {
93✔
338

339
        case INSTALL_CHANGE_SYMLINK:
340
                return log_info("Created symlink '%s' %s '%s'.",
38✔
341
                                change->path, glyph(GLYPH_ARROW_RIGHT), change->source);
342

343
        case INSTALL_CHANGE_UNLINK:
344
                return log_info("Removed '%s'.", change->path);
37✔
345

346
        case INSTALL_CHANGE_IS_MASKED:
347
                return log_info("Unit %s is masked, ignoring.", change->path);
18✔
348

349
        case INSTALL_CHANGE_IS_MASKED_GENERATOR:
UNCOV
350
                return log_info("Unit %s is masked via a generator and cannot be unmasked, skipping.", change->path);
×
351

352
        case INSTALL_CHANGE_IS_DANGLING:
UNCOV
353
                return log_info("Unit %s is an alias to a non-existent unit, ignoring.", change->path);
×
354

355
        case INSTALL_CHANGE_DESTINATION_NOT_PRESENT:
356
                return log_warning("Unit %s is added as a dependency to a non-existent unit %s.",
×
357
                                   change->source, change->path);
358

359
        case INSTALL_CHANGE_AUXILIARY_FAILED:
UNCOV
360
                return log_warning("Failed to enable auxiliary unit %s, ignoring.", change->path);
×
361

362
        default:
×
UNCOV
363
                assert_not_reached();
×
364
        }
365
}
366

367
/* Generated/transient/missing/invalid units when applying presets.
368
 * Coordinate with install_change_dump_error() below. */
369
static bool ERRNO_IS_NEG_UNIT_ISSUE(intmax_t r) {
18✔
370
        return IN_SET(r,
18✔
371
                      -EEXIST,
372
                      -ERFKILL,
373
                      -EADDRNOTAVAIL,
374
                      -ETXTBSY,
375
                      -EBADSLT,
376
                      -EIDRM,
377
                      -EUCLEAN,
378
                      -ELOOP,
379
                      -EXDEV,
380
                      -ENOENT,
381
                      -ENOLINK,
382
                      -EUNATCH);
383
}
384

385
int install_change_dump_error(const InstallChange *change, char **ret_errmsg, const char **ret_bus_error) {
18✔
386
        char *m;
18✔
387
        const char *bus_error;
18✔
388

389
        /* Returns 0:   known error and ret_errmsg formatted
390
         *         < 0: non-recognizable error */
391

392
        assert(change);
18✔
393
        assert(change->path);
18✔
394
        assert(change->type < 0);
18✔
395
        assert(ret_errmsg);
18✔
396

397
        switch (change->type) {
18✔
398

UNCOV
399
        case -EEXIST:
×
UNCOV
400
                m = strjoin("File '", change->path, "' already exists",
×
401
                            change->source ? " and is a symlink to " : NULL, change->source);
UNCOV
402
                bus_error = BUS_ERROR_UNIT_EXISTS;
×
UNCOV
403
                break;
×
404

405
        case -ERFKILL:
17✔
406
                m = strjoin("Unit ", change->path, " is masked");
17✔
407
                bus_error = BUS_ERROR_UNIT_MASKED;
17✔
408
                break;
17✔
409

UNCOV
410
        case -EADDRNOTAVAIL:
×
UNCOV
411
                m = strjoin("Unit ", change->path, " is transient or generated");
×
UNCOV
412
                bus_error = BUS_ERROR_UNIT_GENERATED;
×
UNCOV
413
                break;
×
414

UNCOV
415
        case -ETXTBSY:
×
416
                m = strjoin("File '", change->path, "' is under the systemd unit hierarchy already");
×
417
                bus_error = BUS_ERROR_UNIT_BAD_PATH;
×
418
                break;
×
419

UNCOV
420
        case -EBADSLT:
×
421
                m = strjoin("Invalid specifier in unit ", change->path);
×
422
                bus_error = BUS_ERROR_BAD_UNIT_SETTING;
×
423
                break;
×
424

UNCOV
425
        case -EIDRM:
×
426
                m = strjoin("Refusing to operate on template unit ", change->source,
×
427
                            " when destination unit ", change->path, " is a non-template unit");
428
                bus_error = BUS_ERROR_BAD_UNIT_SETTING;
×
429
                break;
×
430

431
        case -EUCLEAN:
×
432
                m = strjoin("Invalid unit name ", change->path);
×
UNCOV
433
                bus_error = BUS_ERROR_BAD_UNIT_SETTING;
×
434
                break;
×
435

UNCOV
436
        case -ELOOP:
×
437
                m = strjoin("Refusing to operate on linked unit file ", change->path);
×
438
                bus_error = BUS_ERROR_UNIT_LINKED;
×
439
                break;
×
440

UNCOV
441
        case -EXDEV:
×
442
                if (change->source)
×
443
                        m = strjoin("Cannot alias ", change->source, " as ", change->path);
×
444
                else
445
                        m = strjoin("Invalid unit reference ", change->path);
×
446
                bus_error = BUS_ERROR_BAD_UNIT_SETTING;
447
                break;
448

449
        case -ENOENT:
×
UNCOV
450
                m = strjoin("Unit ", change->path, " does not exist");
×
451
                bus_error = BUS_ERROR_NO_SUCH_UNIT;
×
UNCOV
452
                break;
×
453

UNCOV
454
        case -ENOLINK:
×
455
                m = strjoin("Unit ", change->path, " is an unresolvable alias");
×
456
                bus_error = BUS_ERROR_NO_SUCH_UNIT;
×
457
                break;
×
458

UNCOV
459
        case -EUNATCH:
×
460
                m = strjoin("Cannot resolve specifiers in unit ", change->path);
×
461
                bus_error = BUS_ERROR_BAD_UNIT_SETTING;
×
462
                break;
×
463

464
        default:
465
                return change->type;
466
        }
467
        if (!m)
17✔
468
                return -ENOMEM;
469

470
        *ret_errmsg = m;
17✔
471
        if (ret_bus_error)
17✔
UNCOV
472
                *ret_bus_error = bus_error;
×
473

474
        return 0;
475
}
476

477
int install_changes_dump(
105✔
478
                int error,
479
                const char *verb,
480
                const InstallChange *changes,
481
                size_t n_changes,
482
                bool quiet) {
483

484
        bool err_logged = false;
105✔
485
        int r;
105✔
486

487
        /* If verb is not specified, errors are not allowed! */
488
        assert(verb || error >= 0);
105✔
489
        assert(changes || n_changes == 0);
105✔
490

491
        /* An error is returned if 'error' contains an error or if any of the changes failed. */
492

493
        FOREACH_ARRAY(i, changes, n_changes)
216✔
494
                if (i->type >= 0) {
111✔
495
                        if (!quiet)
93✔
496
                                install_change_dump_success(i);
93✔
497
                } else {
498
                        _cleanup_free_ char *err_message = NULL;
18✔
499

500
                        assert(verb);
18✔
501

502
                        r = install_change_dump_error(i, &err_message, /* ret_bus_error= */ NULL);
18✔
503
                        if (r == -ENOMEM)
18✔
UNCOV
504
                                return log_oom();
×
505
                        if (r < 0)
18✔
506
                                RET_GATHER(error,
1✔
507
                                           log_error_errno(r, "Failed to %s unit %s: %m", verb, i->path));
508
                        else
509
                                RET_GATHER(error,
17✔
510
                                           log_error_errno(i->type, "Failed to %s unit: %s", verb, err_message));
511

512
                        err_logged = true;
18✔
513
                }
514

515
        if (error < 0 && !err_logged)
105✔
516
                log_error_errno(error, "Failed to %s units: %m.", verb);
105✔
517

518
        return error;
519
}
520

521
/**
522
 * Checks if two symlink targets (starting from src) are equivalent as far as the unit enablement logic is
523
 * concerned. If the target is in the unit search path, then anything with the same name is equivalent.
524
 * If outside the unit search path, paths must be identical.
525
 */
526
static int chroot_unit_symlinks_equivalent(
599✔
527
                const LookupPaths *lp,
528
                const char *src,
529
                const char *target_a,
530
                const char *target_b) {
531

532
        assert(lp);
599✔
533
        assert(src);
599✔
534
        assert(target_a);
599✔
535
        assert(target_b);
599✔
536

537
        /* This will give incorrect results if the paths are relative and go outside
538
         * of the chroot. False negatives are possible. */
539

540
        const char *root = lp->root_dir ?: "/";
599✔
541
        _cleanup_free_ char *dirname = NULL;
599✔
542
        int r;
599✔
543

544
        if (!path_is_absolute(target_a) || !path_is_absolute(target_b)) {
1,198✔
UNCOV
545
                r = path_extract_directory(src, &dirname);
×
UNCOV
546
                if (r < 0)
×
547
                        return r;
548
        }
549

550
        _cleanup_free_ char *a = path_join(path_is_absolute(target_a) ? root : dirname, target_a);
599✔
551
        _cleanup_free_ char *b = path_join(path_is_absolute(target_b) ? root : dirname, target_b);
1,198✔
552
        if (!a || !b)
599✔
UNCOV
553
                return log_oom();
×
554

555
        r = path_equal_or_inode_same(a, b, 0);
599✔
556
        if (r != 0)
599✔
557
                return r;
558

559
        _cleanup_free_ char *a_name = NULL, *b_name = NULL;
2✔
560
        r = path_extract_filename(a, &a_name);
2✔
561
        if (r < 0)
2✔
562
                return r;
563
        r = path_extract_filename(b, &b_name);
2✔
564
        if (r < 0)
2✔
565
                return r;
566

UNCOV
567
        return streq(a_name, b_name) &&
×
568
               path_startswith_strv(a, lp->search_path) &&
2✔
UNCOV
569
               path_startswith_strv(b, lp->search_path);
×
570
}
571

572
static int create_symlink(
648✔
573
                const LookupPaths *lp,
574
                const char *old_path,
575
                const char *new_path,
576
                bool force,
577
                InstallChange **changes,
578
                size_t *n_changes) {
579

580
        _cleanup_free_ char *dest = NULL;
648✔
581
        const char *rp;
648✔
582
        int r;
648✔
583

584
        assert(old_path);
648✔
585
        assert(new_path);
648✔
586

587
        rp = skip_root(lp->root_dir, old_path);
648✔
588
        if (rp)
648✔
589
                old_path = rp;
646✔
590

591
        /* Actually create a symlink, and remember that we did. This function is
592
         * smart enough to check if there's already a valid symlink in place.
593
         *
594
         * Returns 1 if a symlink was created or already exists and points to the
595
         * right place, or negative on error.
596
         */
597

598
        (void) mkdir_parents_label(new_path, 0755);
648✔
599

600
        if (symlink(old_path, new_path) >= 0) {
648✔
601
                r = install_changes_add(changes, n_changes, INSTALL_CHANGE_SYMLINK, new_path, old_path);
49✔
602
                if (r < 0)
49✔
603
                        return r;
604
                return 1;
49✔
605
        }
606

607
        if (errno != EEXIST)
599✔
UNCOV
608
                return install_changes_add(changes, n_changes, -errno, new_path, NULL);
×
609

610
        r = readlink_malloc(new_path, &dest);
599✔
611
        if (r < 0) {
599✔
612
                /* translate EINVAL (non-symlink exists) to EEXIST */
UNCOV
613
                if (r == -EINVAL)
×
614
                        r = -EEXIST;
×
615

UNCOV
616
                return install_changes_add(changes, n_changes, r, new_path, NULL);
×
617
        }
618

619
        if (chroot_unit_symlinks_equivalent(lp, new_path, dest, old_path)) {
599✔
620
                log_debug("Symlink %s %s %s already exists",
597✔
621
                          new_path, glyph(GLYPH_ARROW_RIGHT), dest);
622
                return 1;
597✔
623
        }
624

625
        if (!force)
2✔
UNCOV
626
                return install_changes_add(changes, n_changes, -EEXIST, new_path, dest);
×
627

628
        r = symlink_atomic(old_path, new_path);
2✔
629
        if (r < 0)
2✔
UNCOV
630
                return install_changes_add(changes, n_changes, r, new_path, NULL);
×
631

632
        r = install_changes_add(changes, n_changes, INSTALL_CHANGE_UNLINK, new_path, NULL);
2✔
633
        if (r < 0)
2✔
634
                return r;
635
        r = install_changes_add(changes, n_changes, INSTALL_CHANGE_SYMLINK, new_path, old_path);
2✔
636
        if (r < 0)
2✔
UNCOV
637
                return r;
×
638

639
        return 1;
640
}
641

642
static int mark_symlink_for_removal(
78✔
643
                Set **remove_symlinks_to,
644
                const char *p) {
645

646
        char *n;
78✔
647
        int r;
78✔
648

649
        assert(p);
78✔
650

651
        r = set_ensure_allocated(remove_symlinks_to, &path_hash_ops_free);
78✔
652
        if (r < 0)
78✔
653
                return r;
78✔
654

655
        r = path_simplify_alloc(p, &n);
78✔
656
        if (r < 0)
78✔
657
                return r;
658

659
        r = set_consume(*remove_symlinks_to, n);
78✔
660
        if (r == -EEXIST)
78✔
661
                return 0;
662
        if (r < 0)
78✔
UNCOV
663
                return r;
×
664

665
        return 1;
666
}
667

668
static int remove_marked_symlinks_fd(
260✔
669
                Set *remove_symlinks_to,
670
                int fd,
671
                const char *path,
672
                const char *config_path,
673
                const LookupPaths *lp,
674
                bool dry_run,
675
                bool *restart,
676
                InstallChange **changes,
677
                size_t *n_changes) {
678

679
        _cleanup_closedir_ DIR *d = NULL;
260✔
680
        int r, ret = 0;
260✔
681

682
        assert(remove_symlinks_to);
260✔
683
        assert(fd >= 0);
260✔
684
        assert(path);
260✔
685
        assert(config_path);
260✔
686
        assert(lp);
260✔
687
        assert(restart);
260✔
688

689
        d = fdopendir(fd);
260✔
690
        if (!d) {
260✔
UNCOV
691
                safe_close(fd);
×
UNCOV
692
                return -errno;
×
693
        }
694

695
        rewinddir(d);
260✔
696

697
        FOREACH_DIRENT(de, d, return -errno)
1,728✔
698
                if (de->d_type == DT_DIR) {
948✔
699
                        _cleanup_close_ int nfd = -EBADF;
1,728✔
700
                        _cleanup_free_ char *p = NULL;
217✔
701

702
                        nfd = RET_NERRNO(openat(fd, de->d_name, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW));
217✔
UNCOV
703
                        if (nfd < 0) {
×
UNCOV
704
                                if (nfd != -ENOENT)
×
UNCOV
705
                                        RET_GATHER(ret, nfd);
×
UNCOV
706
                                continue;
×
707
                        }
708

709
                        p = path_make_absolute(de->d_name, path);
217✔
710
                        if (!p)
217✔
711
                                return -ENOMEM;
×
712

713
                        /* This will close nfd, regardless whether it succeeds or not */
714
                        RET_GATHER(ret, remove_marked_symlinks_fd(remove_symlinks_to,
217✔
715
                                                                  TAKE_FD(nfd), p,
716
                                                                  config_path, lp,
717
                                                                  dry_run,
718
                                                                  restart,
719
                                                                  changes, n_changes));
720

721
                } else if (de->d_type == DT_LNK) {
731✔
722
                        _cleanup_free_ char *p = NULL;
621✔
723
                        bool found;
621✔
724

725
                        if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
621✔
UNCOV
726
                                continue;
×
727

728
                        p = path_make_absolute(de->d_name, path);
621✔
729
                        if (!p)
621✔
730
                                return -ENOMEM;
731
                        path_simplify(p);
621✔
732

733
                        /* We remove all links pointing to a file or path that is marked, as well as all
734
                         * files sharing the same name as a file that is marked, and files sharing the same
735
                         * name after the instance has been removed. Do path chasing only if we don't already
736
                         * know that we want to remove the symlink. */
737
                        found = set_contains(remove_symlinks_to, de->d_name);
621✔
738

739
                        if (!found) {
621✔
UNCOV
740
                                _cleanup_free_ char *template = NULL;
×
741

742
                                r = unit_name_template(de->d_name, &template);
599✔
743
                                if (r < 0 && r != -EINVAL)
599✔
UNCOV
744
                                        return r;
×
745
                                if (r >= 0)
599✔
746
                                        found = set_contains(remove_symlinks_to, template);
599✔
747
                        }
748

749
                        if (!found) {
599✔
750
                                _cleanup_free_ char *dest = NULL, *dest_name = NULL;
×
751

752
                                r = chase(p, lp->root_dir, CHASE_NONEXISTENT, &dest, NULL);
589✔
753
                                if (r == -ENOENT)
589✔
UNCOV
754
                                        continue;
×
755
                                if (r < 0) {
589✔
756
                                        log_debug_errno(r, "Failed to resolve symlink \"%s\": %m", p);
×
UNCOV
757
                                        RET_GATHER(ret, install_changes_add(changes, n_changes, r, p, NULL));
×
UNCOV
758
                                        continue;
×
759
                                }
760

761
                                r = path_extract_filename(dest, &dest_name);
589✔
762
                                if (r < 0)
589✔
763
                                        return r;
×
764

765
                                found = set_contains(remove_symlinks_to, dest) ||
1,178✔
766
                                        set_contains(remove_symlinks_to, dest_name);
589✔
767
                        }
768

769
                        if (!found)
589✔
770
                                continue;
588✔
771

772
                        if (!dry_run) {
33✔
773
                                if (unlinkat(fd, de->d_name, 0) < 0 && errno != ENOENT) {
32✔
UNCOV
774
                                        RET_GATHER(ret, install_changes_add(changes, n_changes, -errno, p, NULL));
×
UNCOV
775
                                        continue;
×
776
                                }
777

778
                                (void) rmdir_parents(p, config_path);
32✔
779
                        }
780

781
                        r = install_changes_add(changes, n_changes, INSTALL_CHANGE_UNLINK, p, NULL);
33✔
782
                        if (r < 0)
33✔
783
                                return r;
784

785
                        /* Now, remember the full path (but with the root prefix removed) of
786
                         * the symlink we just removed, and remove any symlinks to it, too. */
787

788
                        const char *rp = skip_root(lp->root_dir, p);
33✔
789
                        r = mark_symlink_for_removal(&remove_symlinks_to, rp ?: p);
33✔
790
                        if (r < 0)
33✔
791
                                return r;
792
                        if (r > 0 && !dry_run)
33✔
793
                                *restart = true;
32✔
794
                }
795

796
        return ret;
797
}
798

799
static int remove_marked_symlinks(
38✔
800
                Set *remove_symlinks_to,
801
                const char *config_path,
802
                const LookupPaths *lp,
803
                bool dry_run,
804
                InstallChange **changes,
805
                size_t *n_changes) {
806

807
        _cleanup_close_ int fd = -EBADF;
38✔
808
        bool restart;
38✔
809
        int r = 0;
38✔
810

811
        assert(config_path);
38✔
812
        assert(lp);
38✔
813

814
        if (set_isempty(remove_symlinks_to))
38✔
815
                return 0;
816

817
        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC);
30✔
818
        if (fd < 0)
30✔
UNCOV
819
                return errno == ENOENT ? 0 : -errno;
×
820

821
        do {
43✔
822
                int cfd;
43✔
823
                restart = false;
43✔
824

825
                cfd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
43✔
826
                if (cfd < 0)
43✔
UNCOV
827
                        return -errno;
×
828

829
                /* This takes possession of cfd and closes it */
830
                RET_GATHER(r, remove_marked_symlinks_fd(remove_symlinks_to,
43✔
831
                                                        cfd, config_path,
832
                                                        config_path, lp,
833
                                                        dry_run,
834
                                                        &restart,
835
                                                        changes, n_changes));
836
        } while (restart);
43✔
837

838
        return r;
839
}
840

841
static int is_symlink_with_known_name(const InstallInfo *i, const char *name) {
485✔
842
        int r;
485✔
843

844
        assert(i);
485✔
845
        assert(name);
485✔
846

847
        if (streq(name, i->name))
485✔
848
                return true;
849

850
        if (strv_contains(i->aliases, name))
77✔
851
                return true;
852

853
        /* Look for template symlink matching DefaultInstance */
854
        if (i->default_instance && unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) {
70✔
855
                _cleanup_free_ char *s = NULL;
10✔
856

857
                r = unit_name_replace_instance(i->name, i->default_instance, &s);
10✔
858
                if (r < 0) {
10✔
UNCOV
859
                        if (r != -EINVAL)
×
860
                                return r;
861

862
                } else if (streq(name, s))
10✔
863
                        return true;
864
        }
865

866
        return false;
867
}
868

869
static int find_symlinks_in_directory(
148,625✔
870
                DIR *dir,
871
                const char *dir_path,
872
                const char *root_dir,
873
                const InstallInfo *info,
874
                bool ignore_destination,
875
                bool match_name,
876
                bool ignore_same_name,
877
                const char *config_path,
878
                bool *same_name_link) {
879

880
        int r, ret = 0;
148,625✔
881

882
        assert(dir);
148,625✔
883
        assert(dir_path);
148,625✔
884
        assert(info);
148,625✔
885
        assert(unit_name_is_valid(info->name, UNIT_NAME_ANY));
148,625✔
886
        assert(config_path);
148,625✔
887
        assert(same_name_link);
148,625✔
888

889
        FOREACH_DIRENT(de, dir, return -errno) {
3,051,217✔
890
                bool found_path = false, found_dest = false, b = false;
2,606,019✔
891

892
                if (de->d_type != DT_LNK)
2,606,019✔
893
                        continue;
1,949,439✔
894

895
                if (!ignore_destination) {
656,580✔
896
                        _cleanup_free_ char *dest = NULL;
121,505✔
897

898
                        /* Acquire symlink destination */
899
                        r = readlinkat_malloc(dirfd(dir), de->d_name, &dest);
121,505✔
900
                        if (r < 0) {
121,505✔
UNCOV
901
                                if (r != -ENOENT)
×
UNCOV
902
                                        RET_GATHER(ret, r);
×
UNCOV
903
                                continue;
×
904
                        }
905

906
                        /* Check if what the symlink points to matches what we are looking for */
907
                        found_dest = path_equal_filename(dest, info->name);
121,505✔
908
                }
909

910
                /* Check if the symlink itself matches what we are looking for.
911
                 *
912
                 * If ignore_destination is specified, we only look at the source name.
913
                 *
914
                 * If ignore_same_name is specified, we are in one of the directories which
915
                 * have lower priority than the unit file, and even if a file or symlink with
916
                 * this name was found, we should ignore it. */
917

918
                if (ignore_destination || !ignore_same_name)
656,580✔
919
                        found_path = streq(de->d_name, info->name);
610,492✔
920

921
                if (!found_path && ignore_destination) {
656,580✔
922
                        _cleanup_free_ char *template = NULL;
534,409✔
923

924
                        r = unit_name_template(de->d_name, &template);
534,409✔
925
                        if (r < 0 && r != -EINVAL)
534,409✔
UNCOV
926
                                return r;
×
927
                        if (r >= 0)
534,409✔
928
                                found_dest = streq(template, info->name);
31,262✔
929
                }
930

931
                if (found_path && found_dest) {
656,580✔
932
                        _cleanup_free_ char *p = NULL, *t = NULL;
×
933

934
                        /* Filter out same name links in the main config path */
935
                        p = path_make_absolute(de->d_name, dir_path);
4✔
936
                        t = path_make_absolute(info->name, config_path);
4✔
937
                        if (!p || !t)
4✔
938
                                return -ENOMEM;
×
939

940
                        b = path_equal(p, t);
4✔
941
                }
942

943
                if (b)
4✔
944
                        *same_name_link = true;
4✔
945
                else if (found_path || found_dest) {
656,576✔
946
                        if (!match_name)
800✔
947
                                return 1;
948

949
                        /* Check if symlink name is in the set of names used by [Install] */
950
                        r = is_symlink_with_known_name(info, de->d_name);
485✔
951
                        if (r != 0)
485✔
952
                                return r;
953
                }
954
        }
955

956
        return ret;
957
}
958

959
static int find_symlinks(
57,319✔
960
                const char *root_dir,
961
                const InstallInfo *i,
962
                bool match_name,
963
                bool ignore_same_name,
964
                const char *config_path,
965
                bool *same_name_link) {
966

967
        _cleanup_closedir_ DIR *config_dir = NULL;
57,319✔
968
        int r;
57,319✔
969

970
        assert(i);
57,319✔
971
        assert(config_path);
57,319✔
972
        assert(same_name_link);
57,319✔
973

974
        config_dir = opendir(config_path);
57,319✔
975
        if (!config_dir) {
57,319✔
976
                if (IN_SET(errno, ENOENT, ENOTDIR, EACCES))
25,672✔
977
                        return 0;
UNCOV
978
                return -errno;
×
979
        }
980

981
        FOREACH_DIRENT(de, config_dir, return -errno) {
2,292,561✔
982
                const char *suffix;
2,198,366✔
983
                _cleanup_free_ const char *path = NULL;
2,198,366✔
984
                _cleanup_closedir_ DIR *d = NULL;
2,198,366✔
985

986
                if (de->d_type != DT_DIR)
2,198,366✔
987
                        continue;
1,999,567✔
988

989
                suffix = strrchr(de->d_name, '.');
198,799✔
990
                if (!STRPTR_IN_SET(suffix, ".wants", ".requires", ".upholds"))
198,799✔
991
                        continue;
81,138✔
992

993
                path = path_join(config_path, de->d_name);
117,661✔
994
                if (!path)
117,661✔
995
                        return -ENOMEM;
996

997
                d = opendir(path);
117,661✔
998
                if (!d) {
117,661✔
UNCOV
999
                        log_error_errno(errno, "Failed to open directory \"%s\" while scanning for symlinks, ignoring: %m", path);
×
UNCOV
1000
                        continue;
×
1001
                }
1002

1003
                r = find_symlinks_in_directory(d, path, root_dir, i,
117,661✔
1004
                                               /* ignore_destination= */ true,
1005
                                               /* match_name= */ match_name,
1006
                                               /* ignore_same_name= */ ignore_same_name,
1007
                                               config_path,
1008
                                               same_name_link);
1009
                if (r > 0)
117,661✔
1010
                        return 1;
683✔
1011
                if (r < 0)
116,978✔
1012
                        log_debug_errno(r, "Failed to look up symlinks in \"%s\": %m", path);
116,978✔
1013
        }
1014

1015
        /* We didn't find any suitable symlinks in .wants, .requires or .upholds directories,
1016
         * let's look for linked unit files in this directory. */
1017
        rewinddir(config_dir);
30,964✔
1018
        return find_symlinks_in_directory(config_dir, config_path, root_dir, i,
30,964✔
1019
                                          /* ignore_destination= */ false,
1020
                                          /* match_name= */ match_name,
1021
                                          /* ignore_same_name= */ ignore_same_name,
1022
                                          config_path,
1023
                                          same_name_link);
1024
}
1025

1026
static int find_symlinks_in_scope(
4,697✔
1027
                RuntimeScope scope,
1028
                const LookupPaths *lp,
1029
                const InstallInfo *info,
1030
                bool match_name,
1031
                UnitFileState *state) {
1032

1033
        bool same_name_link_runtime = false, same_name_link_config = false;
4,697✔
1034
        bool enabled_in_runtime = false, enabled_at_all = false;
4,697✔
1035
        bool ignore_same_name = false;
4,697✔
1036
        int r;
4,697✔
1037

1038
        assert(lp);
4,697✔
1039
        assert(info);
4,697✔
1040

1041
        /* As we iterate over the list of search paths in lp->search_path, we may encounter "same name"
1042
         * symlinks. The ones which are "below" (i.e. have lower priority) than the unit file itself are
1043
         * effectively masked, so we should ignore them. */
1044

1045
        STRV_FOREACH(p, lp->search_path)  {
61,877✔
1046
                bool same_name_link = false;
57,319✔
1047

1048
                r = find_symlinks(lp->root_dir, info, match_name, ignore_same_name, *p, &same_name_link);
57,319✔
1049
                if (r < 0)
57,319✔
1050
                        return r;
139✔
1051
                if (r > 0) {
57,319✔
1052
                        /* We found symlinks in this dir? Yay! Let's see where precisely it is enabled. */
1053

1054
                        if (path_equal(*p, lp->persistent_config)) {
733✔
1055
                                /* This is the best outcome, let's return it immediately. */
1056
                                *state = UNIT_FILE_ENABLED;
132✔
1057
                                return 1;
132✔
1058
                        }
1059

1060
                        /* look for global enablement of user units */
1061
                        if (scope == RUNTIME_SCOPE_USER && path_is_user_config_dir(*p)) {
601✔
1062
                                *state = UNIT_FILE_ENABLED;
7✔
1063
                                return 1;
7✔
1064
                        }
1065

1066
                        r = path_is_runtime(lp, *p, false);
594✔
1067
                        if (r < 0)
594✔
1068
                                return r;
1069
                        if (r > 0)
594✔
1070
                                enabled_in_runtime = true;
1071
                        else
1072
                                enabled_at_all = true;
581✔
1073

1074
                } else if (same_name_link) {
56,586✔
1075
                        if (path_equal(*p, lp->persistent_config))
4✔
1076
                                same_name_link_config = true;
1077
                        else {
1078
                                r = path_is_runtime(lp, *p, false);
2✔
1079
                                if (r < 0)
2✔
1080
                                        return r;
1081
                                if (r > 0)
2✔
UNCOV
1082
                                        same_name_link_runtime = true;
×
1083
                        }
1084
                }
1085

1086
                /* Check if next iteration will be "below" the unit file (either a regular file
1087
                 * or a symlink), and hence should be ignored */
1088
                if (!ignore_same_name && path_startswith(info->path, *p))
57,180✔
1089
                        ignore_same_name = true;
4,554✔
1090
        }
1091

1092
        if (enabled_in_runtime) {
4,558✔
1093
                *state = UNIT_FILE_ENABLED_RUNTIME;
13✔
1094
                return 1;
13✔
1095
        }
1096

1097
        /* Here's a special rule: if the unit we are looking for is an instance, and it symlinked in the search path
1098
         * outside of runtime and configuration directory, then we consider it statically enabled. Note we do that only
1099
         * for instance, not for regular names, as those are merely aliases, while instances explicitly instantiate
1100
         * something, and hence are a much stronger concept. */
1101
        if (enabled_at_all && unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
4,545✔
1102
                *state = UNIT_FILE_STATIC;
9✔
1103
                return 1;
9✔
1104
        }
1105

1106
        /* Hmm, we didn't find it, but maybe we found the same name
1107
         * link? */
1108
        if (same_name_link_config) {
4,536✔
1109
                *state = UNIT_FILE_LINKED;
2✔
1110
                return 1;
2✔
1111
        }
1112
        if (same_name_link_runtime) {
4,534✔
UNCOV
1113
                *state = UNIT_FILE_LINKED_RUNTIME;
×
UNCOV
1114
                return 1;
×
1115
        }
1116

1117
        return 0;
1118
}
1119

1120
static void install_info_clear(InstallInfo *i) {
24,610✔
1121
        if (!i)
24,610✔
1122
                return;
1123

1124
        i->name = mfree(i->name);
24,610✔
1125
        i->path = mfree(i->path);
24,610✔
1126
        i->root = mfree(i->root);
24,610✔
1127
        i->aliases = strv_free(i->aliases);
24,610✔
1128
        i->wanted_by = strv_free(i->wanted_by);
24,610✔
1129
        i->required_by = strv_free(i->required_by);
24,610✔
1130
        i->upheld_by = strv_free(i->upheld_by);
24,610✔
1131
        i->also = strv_free(i->also);
24,610✔
1132
        i->default_instance = mfree(i->default_instance);
24,610✔
1133
        i->symlink_target = mfree(i->symlink_target);
24,610✔
1134
}
1135

1136
static InstallInfo* install_info_free(InstallInfo *i) {
23,019✔
1137
        install_info_clear(i);
23,019✔
1138
        return mfree(i);
23,019✔
1139
}
1140

1141
DEFINE_TRIVIAL_CLEANUP_FUNC(InstallInfo*, install_info_free);
23,019✔
1142

1143
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
23,019✔
1144
                install_info_hash_ops,
1145
                char, string_hash_func, string_compare_func,
1146
                InstallInfo, install_info_free);
1147

1148
static void install_context_done(InstallContext *ctx) {
13,558✔
1149
        assert(ctx);
13,558✔
1150

1151
        ctx->will_process = ordered_hashmap_free(ctx->will_process);
13,558✔
1152
        ctx->have_processed = ordered_hashmap_free(ctx->have_processed);
13,558✔
1153
}
13,558✔
1154

1155
static InstallInfo *install_info_find(InstallContext *ctx, const char *name) {
41,766✔
1156
        InstallInfo *i;
41,766✔
1157

1158
        i = ordered_hashmap_get(ctx->have_processed, name);
41,766✔
1159
        if (i)
41,766✔
1160
                return i;
1161

1162
        return ordered_hashmap_get(ctx->will_process, name);
41,749✔
1163
}
1164

1165
static int install_info_may_process(
447✔
1166
                const InstallInfo *i,
1167
                const LookupPaths *lp,
1168
                InstallChange **changes,
1169
                size_t *n_changes) {
1170
        assert(i);
447✔
1171
        assert(lp);
447✔
1172

1173
        /* Checks whether the loaded unit file is one we should process, or is masked,
1174
         * transient or generated and thus not subject to enable/disable operations. */
1175

1176
        if (i->install_mode == INSTALL_MODE_MASKED)
447✔
1177
                return install_changes_add(changes, n_changes, -ERFKILL, i->path, NULL);
18✔
1178
        if (path_is_generator(lp, i->path) ||
858✔
1179
            path_is_transient(lp, i->path))
429✔
UNCOV
1180
                return install_changes_add(changes, n_changes, -EADDRNOTAVAIL, i->path, NULL);
×
1181

1182
        return 0;
1183
}
1184

1185
/**
1186
 * Adds a new InstallInfo entry under name in the InstallContext.will_process
1187
 * hashmap, or retrieves the existing one if already present.
1188
 *
1189
 * Returns negative on error, 0 if the unit was already known, 1 otherwise.
1190
 */
1191
static int install_info_add(
23,079✔
1192
                InstallContext *ctx,
1193
                const char *name,
1194
                const char *path,
1195
                const char *root,
1196
                bool auxiliary,
1197
                InstallInfo **ret) {
1198

1199
        _cleanup_free_ char *name_alloc = NULL;
23,079✔
1200
        int r;
23,079✔
1201

1202
        assert(ctx);
23,079✔
1203

1204
        if (!name) {
23,079✔
1205
                assert(path);
2✔
1206

1207
                r = path_extract_filename(path, &name_alloc);
2✔
1208
                if (r < 0)
2✔
1209
                        return r;
1210

1211
                name = name_alloc;
2✔
1212
        }
1213

1214
        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
23,079✔
1215
                return -EINVAL;
1216

1217
        InstallInfo *i = install_info_find(ctx, name);
23,079✔
1218
        if (i) {
23,079✔
1219
                i->auxiliary = i->auxiliary && auxiliary;
60✔
1220

1221
                if (ret)
60✔
1222
                        *ret = i;
9✔
1223
                return 0;
60✔
1224
        }
1225

1226
        _cleanup_(install_info_freep) InstallInfo *new_info = new(InstallInfo, 1);
46,038✔
1227
        if (!new_info)
23,019✔
1228
                return -ENOMEM;
1229

1230
        *new_info = (InstallInfo) {
23,019✔
1231
                .install_mode = _INSTALL_MODE_INVALID,
1232
                .auxiliary = auxiliary,
1233
        };
1234

1235
        if (name_alloc)
23,019✔
1236
                new_info->name = TAKE_PTR(name_alloc);
2✔
1237
        else {
1238
                new_info->name = strdup(name);
23,017✔
1239
                if (!new_info->name)
23,017✔
1240
                        return -ENOMEM;
1241
        }
1242

1243
        r = strdup_to(&new_info->root, root);
23,019✔
1244
        if (r < 0)
23,019✔
1245
                return r;
1246

1247
        r = strdup_to(&new_info->path, path);
23,019✔
1248
        if (r < 0)
23,019✔
1249
                return r;
1250

1251
        r = ordered_hashmap_ensure_put(&ctx->will_process, &install_info_hash_ops, new_info->name, new_info);
23,019✔
1252
        if (r < 0)
23,019✔
1253
                return r;
1254
        i = TAKE_PTR(new_info);
23,019✔
1255

1256
        if (ret)
23,019✔
1257
                *ret = i;
22,818✔
1258
        return 1;
1259
}
1260

1261
static int config_parse_alias(
161✔
1262
                const char *unit,
1263
                const char *filename,
1264
                unsigned line,
1265
                const char *section,
1266
                unsigned section_line,
1267
                const char *lvalue,
1268
                int ltype,
1269
                const char *rvalue,
1270
                void *data,
1271
                void *userdata) {
1272

1273
        UnitType type;
161✔
1274

1275
        assert(unit);
161✔
1276
        assert(filename);
161✔
1277
        assert(lvalue);
161✔
1278
        assert(rvalue);
161✔
1279

1280
        type = unit_name_to_type(unit);
161✔
1281
        if (!unit_type_may_alias(type))
161✔
UNCOV
1282
                return log_syntax(unit, LOG_WARNING, filename, line, 0,
×
1283
                                  "Alias= is not allowed for %s units, ignoring.",
1284
                                  unit_type_to_string(type));
1285

1286
        return config_parse_strv(unit, filename, line, section, section_line,
161✔
1287
                                 lvalue, ltype, rvalue, data, userdata);
1288
}
1289

1290
static int config_parse_also(
184✔
1291
                const char *unit,
1292
                const char *filename,
1293
                unsigned line,
1294
                const char *section,
1295
                unsigned section_line,
1296
                const char *lvalue,
1297
                int ltype,
1298
                const char *rvalue,
1299
                void *data,
1300
                void *userdata) {
1301

1302
        InstallInfo *info = ASSERT_PTR(userdata);
184✔
1303
        InstallContext *ctx = ASSERT_PTR(data);
184✔
1304
        int r;
184✔
1305

1306
        assert(unit);
184✔
1307
        assert(filename);
184✔
1308
        assert(lvalue);
184✔
1309
        assert(rvalue);
184✔
1310

1311
        for (;;) {
252✔
UNCOV
1312
                _cleanup_free_ char *word = NULL, *printed = NULL;
×
1313

1314
                r = extract_first_word(&rvalue, &word, NULL, 0);
436✔
1315
                if (r < 0)
436✔
1316
                        return r;
1317
                if (r == 0)
436✔
1318
                        break;
1319

1320
                r = install_name_printf(ctx->scope, info, word, &printed);
252✔
1321
                if (r < 0)
252✔
UNCOV
1322
                        return log_syntax(unit, LOG_WARNING, filename, line, r,
×
1323
                                          "Failed to resolve unit name in Also=\"%s\": %m", word);
1324

1325
                r = install_info_add(ctx, printed, NULL, info->root, /* auxiliary= */ true, NULL);
252✔
1326
                if (r < 0)
252✔
1327
                        return r;
1328

1329
                r = strv_push(&info->also, printed);
252✔
1330
                if (r < 0)
252✔
1331
                        return r;
1332

1333
                printed = NULL;
252✔
1334
        }
1335

1336
        return 0;
184✔
1337
}
1338

1339
static int config_parse_default_instance(
98✔
1340
                const char *unit,
1341
                const char *filename,
1342
                unsigned line,
1343
                const char *section,
1344
                unsigned section_line,
1345
                const char *lvalue,
1346
                int ltype,
1347
                const char *rvalue,
1348
                void *data,
1349
                void *userdata) {
1350

1351
        InstallContext *ctx = ASSERT_PTR(data);
98✔
1352
        InstallInfo *info = ASSERT_PTR(userdata);
98✔
1353
        _cleanup_free_ char *printed = NULL;
98✔
1354
        int r;
98✔
1355

1356
        assert(unit);
98✔
1357
        assert(filename);
98✔
1358
        assert(lvalue);
98✔
1359
        assert(rvalue);
98✔
1360

1361
        if (unit_name_is_valid(unit, UNIT_NAME_INSTANCE))
98✔
1362
                /* When enabling an instance, we might be using a template unit file,
1363
                 * but we should ignore DefaultInstance silently. */
1364
                return 0;
1365
        if (!unit_name_is_valid(unit, UNIT_NAME_TEMPLATE))
45✔
UNCOV
1366
                return log_syntax(unit, LOG_WARNING, filename, line, 0,
×
1367
                                  "DefaultInstance= only makes sense for template units, ignoring.");
1368

1369
        r = install_name_printf(ctx->scope, info, rvalue, &printed);
45✔
1370
        if (r < 0)
45✔
UNCOV
1371
                return log_syntax(unit, LOG_WARNING, filename, line, r,
×
1372
                                  "Failed to resolve instance name in DefaultInstance=\"%s\": %m", rvalue);
1373

1374
        if (isempty(printed))
45✔
UNCOV
1375
                printed = mfree(printed);
×
1376

1377
        if (printed && !unit_instance_is_valid(printed))
45✔
UNCOV
1378
                return log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL),
×
1379
                                  "Invalid DefaultInstance= value \"%s\".", printed);
1380

1381
        return free_and_replace(info->default_instance, printed);
45✔
1382
}
1383

1384
static int unit_file_load(
252,388✔
1385
                InstallContext *ctx,
1386
                InstallInfo *info,
1387
                const char *path,
1388
                const char *root_dir,
1389
                SearchFlags flags) {
1390

1391
        const ConfigTableItem items[] = {
252,388✔
1392
                { "Install", "Alias",           config_parse_alias,            0, &info->aliases           },
252,388✔
1393
                { "Install", "WantedBy",        config_parse_strv,             0, &info->wanted_by         },
252,388✔
1394
                { "Install", "RequiredBy",      config_parse_strv,             0, &info->required_by       },
252,388✔
1395
                { "Install", "UpheldBy",        config_parse_strv,             0, &info->upheld_by         },
252,388✔
1396
                { "Install", "DefaultInstance", config_parse_default_instance, 0, info                     },
1397
                { "Install", "Also",            config_parse_also,             0, ctx                      },
1398
                {}
1399
        };
1400

1401
        UnitType type;
252,388✔
1402
        _cleanup_fclose_ FILE *f = NULL;
252,388✔
1403
        _cleanup_close_ int fd = -EBADF;
252,388✔
1404
        struct stat st;
252,388✔
1405
        int r;
252,388✔
1406

1407
        assert(info);
252,388✔
1408
        assert(path);
252,388✔
1409

1410
        if (!(flags & SEARCH_DROPIN)) {
252,388✔
1411
                /* Loading or checking for the main unit file… */
1412

1413
                type = unit_name_to_type(info->name);
249,403✔
1414
                if (type < 0)
249,403✔
1415
                        return -EINVAL;
1416
                if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE|UNIT_NAME_INSTANCE) && !unit_type_may_template(type))
249,403✔
UNCOV
1417
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1418
                                               "%s: unit type %s cannot be templated, ignoring.", path, unit_type_to_string(type));
1419

1420
                if (!(flags & SEARCH_LOAD)) {
249,403✔
1421
                        if (lstat(path, &st) < 0)
214,648✔
1422
                                return -errno;
195,921✔
1423

1424
                        if (null_or_empty(&st))
18,727✔
UNCOV
1425
                                info->install_mode = INSTALL_MODE_MASKED;
×
1426
                        else if (S_ISREG(st.st_mode))
18,727✔
1427
                                info->install_mode = INSTALL_MODE_REGULAR;
18,136✔
1428
                        else if (S_ISLNK(st.st_mode))
591✔
1429
                                return -ELOOP;
UNCOV
1430
                        else if (S_ISDIR(st.st_mode))
×
1431
                                return -EISDIR;
1432
                        else
UNCOV
1433
                                return -ENOTTY;
×
1434

1435
                        return 0;
18,136✔
1436
                }
1437

1438
                fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
34,755✔
1439
                if (fd < 0)
34,755✔
1440
                        return -errno;
31,680✔
1441
        } else {
1442
                /* Operating on a drop-in file. If we aren't supposed to load the unit file drop-ins don't matter, let's hence shortcut this. */
1443

1444
                if (!(flags & SEARCH_LOAD))
2,985✔
1445
                        return 0;
1446

1447
                fd = chase_and_open(path, root_dir, 0, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
2,465✔
1448
                if (fd < 0)
2,465✔
1449
                        return fd;
1450
        }
1451

1452
        if (fstat(fd, &st) < 0)
5,540✔
UNCOV
1453
                return -errno;
×
1454

1455
        if (null_or_empty(&st)) {
5,540✔
UNCOV
1456
                if ((flags & SEARCH_DROPIN) == 0)
×
UNCOV
1457
                        info->install_mode = INSTALL_MODE_MASKED;
×
1458

1459
                return 0;
×
1460
        }
1461

1462
        r = stat_verify_regular(&st);
5,540✔
1463
        if (r < 0)
5,540✔
1464
                return r;
1465

1466
        f = take_fdopen(&fd, "r");
5,540✔
1467
        if (!f)
5,540✔
UNCOV
1468
                return -errno;
×
1469

1470
        /* ctx is only needed if we actually load the file (it's referenced from items[] btw, in case you wonder.) */
1471
        assert(ctx);
5,540✔
1472

1473
        r = config_parse(info->name, path, f,
5,540✔
1474
                         "Install\0"
1475
                         "-Unit\0"
1476
                         "-Automount\0"
1477
                         "-Device\0"
1478
                         "-Mount\0"
1479
                         "-Path\0"
1480
                         "-Scope\0"
1481
                         "-Service\0"
1482
                         "-Slice\0"
1483
                         "-Socket\0"
1484
                         "-Swap\0"
1485
                         "-Target\0"
1486
                         "-Timer\0",
1487
                         config_item_table_lookup, items,
1488
                         0, info,
1489
                         NULL);
1490
        if (r < 0)
5,540✔
UNCOV
1491
                return log_debug_errno(r, "Failed to parse \"%s\": %m", info->name);
×
1492

1493
        if ((flags & SEARCH_DROPIN) == 0)
5,540✔
1494
                info->install_mode = INSTALL_MODE_REGULAR;
3,075✔
1495

1496
        return
5,540✔
1497
                (int) strv_length(info->aliases) +
5,540✔
1498
                (int) strv_length(info->wanted_by) +
5,540✔
1499
                (int) strv_length(info->required_by) +
5,540✔
1500
                (int) strv_length(info->upheld_by);
5,540✔
1501
}
1502

1503
static int unit_file_load_or_readlink(
252,388✔
1504
                InstallContext *ctx,
1505
                InstallInfo *info,
1506
                const char *path,
1507
                const LookupPaths *lp,
1508
                SearchFlags flags) {
1509
        int r;
252,388✔
1510

1511
        r = unit_file_load(ctx, info, path, lp->root_dir, flags);
252,388✔
1512
        if (r != -ELOOP || (flags & SEARCH_DROPIN))
252,388✔
1513
                return r;
1514

1515
        /* This is a symlink, let's read and verify it. */
1516
        r = unit_file_resolve_symlink(lp->root_dir, lp->search_path,
750✔
1517
                                      NULL, AT_FDCWD, path,
1518
                                      true, &info->symlink_target);
1519
        if (r < 0)
750✔
1520
                return r;
1521
        bool outside_search_path = r > 0;
749✔
1522

1523
        r = null_or_empty_path_with_root(info->symlink_target, lp->root_dir);
749✔
1524
        if (r < 0 && r != -ENOENT)
749✔
UNCOV
1525
                return log_debug_errno(r, "Failed to stat %s: %m", info->symlink_target);
×
1526
        if (r > 0)
749✔
1527
                info->install_mode = INSTALL_MODE_MASKED;
157✔
1528
        else if (outside_search_path)
592✔
1529
                info->install_mode = INSTALL_MODE_LINKED;
9✔
1530
        else
1531
                info->install_mode = INSTALL_MODE_ALIAS;
583✔
1532

1533
        return 0;
1534
}
1535

1536
static int unit_file_search(
23,441✔
1537
                InstallContext *ctx,
1538
                InstallInfo *info,
1539
                const LookupPaths *lp,
1540
                SearchFlags flags) {
1541

1542
        const char *dropin_dir_name = NULL, *dropin_template_dir_name = NULL;
23,441✔
1543
        _cleanup_strv_free_ char **dirs = NULL, **files = NULL;
23,441✔
1544
        _cleanup_free_ char *template = NULL;
23,441✔
1545
        bool found_unit = false;
23,441✔
1546
        int r, result;
23,441✔
1547

1548
        assert(info);
23,441✔
1549
        assert(lp);
23,441✔
1550

1551
        /* Was this unit already loaded? */
1552
        if (info->install_mode != _INSTALL_MODE_INVALID)
23,441✔
1553
                return 0;
1554

1555
        if (info->path)
22,943✔
1556
                return unit_file_load_or_readlink(ctx, info, info->path, lp, flags);
2✔
1557

1558
        assert(info->name);
22,941✔
1559

1560
        if (!FLAGS_SET(flags, SEARCH_IGNORE_TEMPLATE) && unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
22,941✔
1561
                r = unit_name_template(info->name, &template);
159✔
1562
                if (r < 0)
159✔
1563
                        return r;
1564
        }
1565

1566
        STRV_FOREACH(p, lp->search_path) {
248,915✔
1567
                _cleanup_free_ char *path = NULL;
225,975✔
1568

1569
                path = path_join(*p, info->name);
247,788✔
1570
                if (!path)
247,788✔
1571
                        return -ENOMEM;
1572

1573
                r = unit_file_load_or_readlink(ctx, info, path, lp, flags);
247,788✔
1574
                if (r >= 0) {
247,788✔
1575
                        info->path = TAKE_PTR(path);
21,813✔
1576
                        result = r;
21,813✔
1577
                        found_unit = true;
21,813✔
1578
                        break;
21,813✔
1579
                } else if (!IN_SET(r, -ENOENT, -ENOTDIR, -EACCES))
225,975✔
1580
                        return r;
1581
        }
1582

1583
        if (!found_unit && template) {
1,127✔
1584

1585
                /* Unit file doesn't exist, however instance
1586
                 * enablement was requested.  We will check if it is
1587
                 * possible to load template unit file. */
1588

1589
                STRV_FOREACH(p, lp->search_path) {
1,603✔
1590
                        _cleanup_free_ char *path = NULL;
1,468✔
1591

1592
                        path = path_join(*p, template);
1,599✔
1593
                        if (!path)
1,599✔
1594
                                return -ENOMEM;
1595

1596
                        r = unit_file_load_or_readlink(ctx, info, path, lp, flags);
1,599✔
1597
                        if (r >= 0) {
1,599✔
1598
                                info->path = TAKE_PTR(path);
131✔
1599
                                result = r;
131✔
1600
                                found_unit = true;
131✔
1601
                                break;
131✔
1602
                        } else if (!IN_SET(r, -ENOENT, -ENOTDIR, -EACCES))
1,468✔
1603
                                return r;
1604
                }
1605
        }
1606

1607
        if (!found_unit)
21,944✔
1608
                return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
2,980✔
1609
                                       "Cannot find unit %s%s%s.",
1610
                                       info->name, template ? " or " : "", strempty(template));
1611

1612
        if (info->install_mode == INSTALL_MODE_MASKED)
21,944✔
1613
                return result;
1614

1615
        /* Search for drop-in directories */
1616

1617
        dropin_dir_name = strjoina(info->name, ".d");
108,935✔
1618
        STRV_FOREACH(p, lp->search_path) {
322,239✔
1619
                char *path;
300,452✔
1620

1621
                path = path_join(*p, dropin_dir_name);
300,452✔
1622
                if (!path)
300,452✔
1623
                        return -ENOMEM;
1624

1625
                r = strv_consume(&dirs, path);
300,452✔
1626
                if (r < 0)
300,452✔
1627
                        return r;
1628
        }
1629

1630
        if (template) {
21,787✔
1631
                dropin_template_dir_name = strjoina(template, ".d");
775✔
1632
                STRV_FOREACH(p, lp->search_path) {
2,159✔
1633
                        char *path;
2,004✔
1634

1635
                        path = path_join(*p, dropin_template_dir_name);
2,004✔
1636
                        if (!path)
2,004✔
1637
                                return -ENOMEM;
1638

1639
                        r = strv_consume(&dirs, path);
2,004✔
1640
                        if (r < 0)
2,004✔
1641
                                return r;
1642
                }
1643
        }
1644

1645
        /* Load drop-in conf files */
1646

1647
        r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) dirs);
21,787✔
1648
        if (r < 0)
21,787✔
UNCOV
1649
                return log_debug_errno(r, "Failed to get list of conf files: %m");
×
1650

1651
        STRV_FOREACH(p, files) {
24,772✔
1652
                r = unit_file_load_or_readlink(ctx, info, *p, lp, flags | SEARCH_DROPIN);
2,985✔
1653
                if (r < 0)
2,985✔
UNCOV
1654
                        return log_debug_errno(r, "Failed to load conf file \"%s\": %m", *p);
×
1655
        }
1656

1657
        return result;
1658
}
1659

1660
static int install_info_follow(
606✔
1661
                InstallContext *ctx,
1662
                InstallInfo *info,
1663
                const LookupPaths *lp,
1664
                SearchFlags flags,
1665
                bool ignore_different_name) {
1666

1667
        assert(ctx);
606✔
1668
        assert(info);
606✔
1669

1670
        if (!IN_SET(info->install_mode, INSTALL_MODE_ALIAS, INSTALL_MODE_LINKED))
606✔
1671
                return -EINVAL;
1672
        if (!info->symlink_target)
606✔
1673
                return -EINVAL;
1674

1675
        /* If the basename doesn't match, the caller should add a complete new entry for this. */
1676

1677
        if (!ignore_different_name && !path_equal_filename(info->symlink_target, info->name))
606✔
1678
                return -EXDEV;
1679

1680
        free_and_replace(info->path, info->symlink_target);
14✔
1681
        info->install_mode = _INSTALL_MODE_INVALID;
14✔
1682

1683
        return unit_file_load_or_readlink(ctx, info, info->path, lp, flags);
14✔
1684
}
1685

1686
/**
1687
 * Search for the unit file. If the unit name is a symlink, follow the symlink to the
1688
 * target, maybe more than once. Propagate the instance name if present.
1689
 */
1690
static int install_info_traverse(
22,834✔
1691
                InstallContext *ctx,
1692
                const LookupPaths *lp,
1693
                InstallInfo *start,
1694
                SearchFlags flags,
1695
                InstallInfo **ret) {
1696

1697
        InstallInfo *i;
22,834✔
1698
        unsigned k = 0;
22,834✔
1699
        int r;
22,834✔
1700

1701
        assert(ctx);
22,834✔
1702
        assert(lp);
22,834✔
1703
        assert(start);
22,834✔
1704

1705
        r = unit_file_search(ctx, start, lp, flags);
22,834✔
1706
        if (r < 0)
22,834✔
1707
                return r;
22,834✔
1708

1709
        i = start;
21,837✔
1710
        while (IN_SET(i->install_mode, INSTALL_MODE_ALIAS, INSTALL_MODE_LINKED)) {
22,438✔
1711
                /* Follow the symlink */
1712

1713
                if (++k > UNIT_FILE_FOLLOW_SYMLINK_MAX)
601✔
1714
                        return -ELOOP;
1715

1716
                if (!FLAGS_SET(flags, SEARCH_FOLLOW_CONFIG_SYMLINKS)) {
601✔
1717
                        r = path_is_config(lp, i->path, true);
6✔
1718
                        if (r < 0)
6✔
1719
                                return r;
1720
                        if (r > 0)
6✔
1721
                                return -ELOOP;
1722
                }
1723

1724
                r = install_info_follow(ctx, i, lp, flags,
1,202✔
1725
                                        /* If linked, don't look at the target name */
1726
                                        /* ignore_different_name= */ i->install_mode == INSTALL_MODE_LINKED);
601✔
1727
                if (r == -EXDEV && i->symlink_target) {
601✔
1728
                        _cleanup_free_ char *target_name = NULL, *unit_instance = NULL;
587✔
1729
                        const char *bn;
592✔
1730

1731
                        /* Target is an alias, create a new install info object and continue with that. */
1732

1733
                        r = path_extract_filename(i->symlink_target, &target_name);
592✔
1734
                        if (r < 0)
592✔
1735
                                return r;
1736

1737
                        if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE) &&
633✔
1738
                            unit_name_is_valid(target_name, UNIT_NAME_TEMPLATE)) {
41✔
1739

1740
                                _cleanup_free_ char *instance = NULL;
41✔
1741

1742
                                r = unit_name_to_instance(i->name, &instance);
41✔
1743
                                if (r < 0)
41✔
1744
                                        return r;
1745

1746
                                r = unit_name_replace_instance(target_name, instance, &unit_instance);
41✔
1747
                                if (r < 0)
41✔
1748
                                        return r;
1749

1750
                                if (streq(unit_instance, i->name)) {
41✔
1751
                                        /* We filled in the instance, and the target stayed the same? If so,
1752
                                         * then let's honour the link as it is. */
1753

1754
                                        r = install_info_follow(ctx, i, lp, flags, true);
5✔
1755
                                        if (r < 0)
5✔
1756
                                                return r;
1757

1758
                                        continue;
5✔
1759
                                }
1760

1761
                                bn = unit_instance;
36✔
1762
                        } else
1763
                                bn = target_name;
551✔
1764

1765
                        r = install_info_add(ctx, bn, NULL, lp->root_dir, /* auxiliary= */ false, &i);
587✔
1766
                        if (r < 0)
587✔
1767
                                return r;
1768

1769
                        /* Try again, with the new target we found. */
1770
                        r = unit_file_search(ctx, i, lp, flags);
587✔
1771
                        if (r == -ENOENT)
587✔
1772
                                /* Translate error code to highlight this specific case */
1773
                                return -ENOLINK;
1774
                }
1775
                if (r < 0)
596✔
1776
                        return r;
1777
        }
1778

1779
        if (ret)
21,837✔
1780
                *ret = i;
20,697✔
1781

1782
        return 0;
1783
}
1784

1785
/**
1786
 * Call install_info_add() with name_or_path as the path (if name_or_path starts with "/")
1787
 * or the name (otherwise). root_dir is prepended to the path.
1788
 */
1789
static int install_info_add_auto(
22,225✔
1790
                InstallContext *ctx,
1791
                const LookupPaths *lp,
1792
                const char *name_or_path,
1793
                InstallInfo **ret) {
1794

1795
        assert(ctx);
22,225✔
1796
        assert(name_or_path);
22,225✔
1797

1798
        if (path_is_absolute(name_or_path)) {
22,225✔
1799
                _cleanup_free_ char *pp = path_join(lp->root_dir, name_or_path);
4✔
1800
                if (!pp)
2✔
1801
                        return -ENOMEM;
1802

1803
                return install_info_add(ctx, NULL, pp, lp->root_dir, /* auxiliary= */ false, ret);
2✔
1804
        } else
1805
                return install_info_add(ctx, name_or_path, NULL, lp->root_dir, /* auxiliary= */ false, ret);
22,223✔
1806
}
1807

1808
static int install_info_discover(
22,225✔
1809
                InstallContext *ctx,
1810
                const LookupPaths *lp,
1811
                const char *name_or_path,
1812
                SearchFlags flags,
1813
                InstallInfo **ret,
1814
                InstallChange **changes,
1815
                size_t *n_changes) {
1816

1817
        InstallInfo *info;
22,225✔
1818
        int r;
22,225✔
1819

1820
        assert(ctx);
22,225✔
1821
        assert(lp);
22,225✔
1822
        assert(name_or_path);
22,225✔
1823

1824
        r = install_info_add_auto(ctx, lp, name_or_path, &info);
22,225✔
1825
        if (r >= 0)
22,225✔
1826
                r = install_info_traverse(ctx, lp, info, flags, ret);
22,225✔
1827

1828
        if (r < 0)
22,225✔
1829
                return install_changes_add(changes, n_changes, r, name_or_path, NULL);
997✔
1830

1831
        return r;
1832
}
1833

1834
static int install_info_discover_and_check(
448✔
1835
                InstallContext *ctx,
1836
                const LookupPaths *lp,
1837
                const char *name_or_path,
1838
                SearchFlags flags,
1839
                InstallInfo **ret,
1840
                InstallChange **changes,
1841
                size_t *n_changes) {
1842

1843
        int r;
448✔
1844

1845
        r = install_info_discover(ctx, lp, name_or_path, flags, ret, changes, n_changes);
448✔
1846
        if (r < 0)
448✔
1847
                return r;
1848

1849
        return install_info_may_process(ret ? *ret : NULL, lp, changes, n_changes);
447✔
1850
}
1851

1852
int unit_file_verify_alias(
224✔
1853
                const InstallInfo *info,
1854
                const char *dst,
1855
                char **ret_dst,
1856
                InstallChange **changes,
1857
                size_t *n_changes) {
1858

1859
        _cleanup_free_ char *dst_updated = NULL;
224✔
1860
        int r;
224✔
1861

1862
        /* Verify that dst is a valid either a valid alias or a valid .wants/.requires symlink for the target
1863
         * unit *i. Return negative on error or if not compatible, zero on success.
1864
         *
1865
         * ret_dst is set in cases where "instance propagation" happens, i.e. when the instance part is
1866
         * inserted into dst. It is not normally set, even on success, so that the caller can easily
1867
         * distinguish the case where instance propagation occurred.
1868
         *
1869
         * Returns:
1870
         * -EXDEV when the alias doesn't match the unit,
1871
         * -EUCLEAN when the name is invalid,
1872
         * -ELOOP when the alias it to the unit itself.
1873
         */
1874

1875
        const char *path_alias = strrchr(dst, '/');
224✔
1876
        if (path_alias) {
224✔
1877
                /* This branch covers legacy Alias= function of creating .wants and .requires symlinks. */
1878
                _cleanup_free_ char *dir = NULL;
89✔
1879
                char *p;
89✔
1880

1881
                path_alias++; /* skip over slash */
89✔
1882

1883
                r = path_extract_directory(dst, &dir);
89✔
1884
                if (r < 0)
89✔
UNCOV
1885
                        return log_error_errno(r, "Failed to extract parent directory from '%s': %m", dst);
×
1886

1887
                p = endswith(dir, ".wants");
89✔
1888
                if (!p)
89✔
1889
                        p = endswith(dir, ".requires");
57✔
1890
                if (!p) {
57✔
1891
                        r = install_changes_add(changes, n_changes, -EXDEV, dst, NULL);
10✔
1892
                        if (r != -EXDEV)
10✔
1893
                                return r;
1894

1895
                        return log_debug_errno(SYNTHETIC_ERRNO(EXDEV), "Invalid path \"%s\" in alias.", dir);
10✔
1896
                }
1897

1898
                *p = '\0'; /* dir should now be a unit name */
79✔
1899

1900
                UnitNameFlags type = unit_name_classify(dir);
79✔
1901
                if (type < 0) {
79✔
1902
                        r = install_changes_add(changes, n_changes, -EXDEV, dst, NULL);
1✔
1903
                        if (r != -EXDEV)
1✔
1904
                                return r;
1905
                        return log_debug_errno(SYNTHETIC_ERRNO(EXDEV),
1✔
1906
                                               "Invalid unit name component \"%s\" in alias.", dir);
1907
                }
1908

1909
                const bool instance_propagation = type == UNIT_NAME_TEMPLATE;
78✔
1910

1911
                /* That's the name we want to use for verification. */
1912
                r = unit_symlink_name_compatible(path_alias, info->name, instance_propagation);
78✔
1913
                if (r < 0)
78✔
UNCOV
1914
                        return log_error_errno(r, "Failed to verify alias validity: %m");
×
1915
                if (r == 0) {
78✔
1916
                        r = install_changes_add(changes, n_changes, -EXDEV, dst, info->name);
65✔
1917
                        if (r != -EXDEV)
65✔
1918
                                return r;
1919

1920
                        return log_debug_errno(SYNTHETIC_ERRNO(EXDEV),
65✔
1921
                                               "Invalid unit \"%s\" symlink \"%s\".",
1922
                                               info->name, dst);
1923
                }
1924

1925
        } else {
1926
                /* If the symlink target has an instance set and the symlink source doesn't, we "propagate
1927
                 * the instance", i.e. instantiate the symlink source with the target instance. */
1928
                if (unit_name_is_valid(dst, UNIT_NAME_TEMPLATE)) {
135✔
1929
                        _cleanup_free_ char *inst = NULL;
26✔
1930

1931
                        UnitNameFlags type = unit_name_to_instance(info->name, &inst);
26✔
1932
                        if (type < 0) {
26✔
UNCOV
1933
                                r = install_changes_add(changes, n_changes, -EUCLEAN, info->name, NULL);
×
UNCOV
1934
                                if (r != -EUCLEAN)
×
1935
                                        return r;
UNCOV
1936
                                return log_debug_errno(type, "Failed to extract instance name from \"%s\": %m", info->name);
×
1937
                        }
1938

1939
                        if (type == UNIT_NAME_INSTANCE) {
26✔
1940
                                r = unit_name_replace_instance(dst, inst, &dst_updated);
4✔
1941
                                if (r < 0)
4✔
1942
                                        return log_error_errno(r, "Failed to build unit name from %s+%s: %m",
×
1943
                                                               dst, inst);
1944
                        }
1945
                }
1946

1947
                r = unit_validate_alias_symlink_or_warn(LOG_DEBUG, dst_updated ?: dst, info->name);
266✔
1948
                if (r == -ELOOP)  /* -ELOOP means self-alias, which we (quietly) ignore */
135✔
1949
                        return r;
1950
                if (r < 0)
135✔
1951
                        return install_changes_add(changes, n_changes,
21✔
1952
                                                   r == -EINVAL ? -EXDEV : r,
1953
                                                   dst_updated ?: dst,
21✔
1954
                                                   info->name);
21✔
1955
        }
1956

1957
        *ret_dst = TAKE_PTR(dst_updated);
127✔
1958
        return 0;
127✔
1959
}
1960

1961
static int install_info_symlink_alias(
530✔
1962
                RuntimeScope scope,
1963
                UnitFileFlags file_flags,
1964
                InstallInfo *info,
1965
                const LookupPaths *lp,
1966
                const char *config_path,
1967
                bool force,
1968
                InstallChange **changes,
1969
                size_t *n_changes) {
1970

1971
        int r, ret = 0;
530✔
1972

1973
        assert(info);
530✔
1974
        assert(lp);
530✔
1975
        assert(config_path);
530✔
1976

1977
        STRV_FOREACH(s, info->aliases) {
634✔
1978
                _cleanup_free_ char *alias_path = NULL, *alias_target = NULL, *dst = NULL, *dst_updated = NULL;
104✔
1979

1980
                r = install_name_printf(scope, info, *s, &dst);
104✔
1981
                if (r < 0) {
104✔
UNCOV
1982
                        RET_GATHER(ret, install_changes_add(changes, n_changes, r, *s, NULL));
×
UNCOV
1983
                        continue;
×
1984
                }
1985

1986
                r = unit_file_verify_alias(info, dst, &dst_updated, changes, n_changes);
104✔
1987
                if (r < 0) {
104✔
1988
                        if (r != -ELOOP)
×
1989
                                RET_GATHER(ret, r);
×
UNCOV
1990
                        continue;
×
1991
                }
1992

1993
                alias_path = path_make_absolute(dst_updated ?: dst, config_path);
104✔
1994
                if (!alias_path)
104✔
1995
                        return -ENOMEM;
1996

1997
                r = in_search_path(lp, info->path);
104✔
1998
                if (r < 0)
104✔
1999
                        return r;
2000
                if (r == 0) {
104✔
2001
                        /* The unit path itself is outside of the search path. To
2002
                         * correctly apply the alias, we need the alias symlink to
2003
                         * point to the symlink that was created in the search path. */
2004
                        alias_target = path_join(config_path, info->name);
1✔
2005
                        if (!alias_target)
1✔
2006
                                return -ENOMEM;
2007
                }
2008

2009
                bool broken;
104✔
2010
                r = chase(alias_path, lp->root_dir, CHASE_NONEXISTENT, /* ret_path= */ NULL, /* ret_fd= */ NULL);
104✔
2011
                if (r < 0 && r != -ENOENT) {
104✔
UNCOV
2012
                        RET_GATHER(ret, r);
×
UNCOV
2013
                        continue;
×
2014
                }
2015
                broken = r == 0; /* symlink target does not exist? */
104✔
2016

2017
                r = create_symlink(lp, alias_target ?: info->path, alias_path, force || broken, changes, n_changes);
104✔
2018
                if (r == -EEXIST && FLAGS_SET(file_flags, UNIT_FILE_IGNORE_AUXILIARY_FAILURE))
104✔
2019
                        /* We cannot realize the alias because a conflicting alias exists.
2020
                         * Do not propagate this as error. */
UNCOV
2021
                        continue;
×
2022
                if (r != 0 && ret >= 0)
104✔
2023
                        ret = r;
104✔
2024
        }
2025

2026
        return ret;
2027
}
2028

2029
static int install_info_symlink_wants(
1,590✔
2030
                RuntimeScope scope,
2031
                UnitFileFlags file_flags,
2032
                InstallInfo *info,
2033
                const LookupPaths *lp,
2034
                const char *config_path,
2035
                char **list,
2036
                const char *suffix,
2037
                InstallChange **changes,
2038
                size_t *n_changes) {
2039

UNCOV
2040
        _cleanup_(install_info_clear) InstallInfo instance = {
×
2041
                .install_mode = _INSTALL_MODE_INVALID,
2042
        };
2043

2044
        UnitNameFlags valid_dst_type = UNIT_NAME_ANY;
1,590✔
2045
        const char *n;
1,590✔
2046
        int r, q;
1,590✔
2047

2048
        assert(info);
1,590✔
2049
        assert(lp);
1,590✔
2050
        assert(config_path);
1,590✔
2051

2052
        if (strv_isempty(list))
2,083✔
2053
                return 0;
2054

2055
        if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN | UNIT_NAME_INSTANCE))
493✔
2056
                /* Not a template unit. Use the name directly. */
2057
                n = info->name;
2058

2059
        else if (info->default_instance) {
19✔
2060
                /* If this is a template, and we have a default instance, use it. */
2061

2062
                r = unit_name_replace_instance(info->name, info->default_instance, &instance.name);
19✔
2063
                if (r < 0)
19✔
2064
                        return r;
2065

2066
                r = unit_file_search(NULL, &instance, lp, SEARCH_FOLLOW_CONFIG_SYMLINKS);
19✔
2067
                if (r < 0)
19✔
2068
                        return r;
2069

2070
                if (instance.install_mode == INSTALL_MODE_MASKED)
19✔
UNCOV
2071
                        return install_changes_add(changes, n_changes, -ERFKILL, instance.path, NULL);
×
2072

2073
                n = instance.name;
19✔
2074

2075
        } else {
2076
                /* We have a template, but no instance yet. When used with an instantiated unit, we will get
2077
                 * the instance from that unit. Cannot be used with non-instance units. */
2078

2079
                valid_dst_type = UNIT_NAME_INSTANCE | UNIT_NAME_TEMPLATE;
2080
                n = info->name;
2081
        }
2082

2083
        r = 0;
493✔
2084
        STRV_FOREACH(s, list) {
1,025✔
2085
                _cleanup_free_ char *path = NULL, *dst = NULL;
532✔
2086

2087
                q = install_name_printf(scope, info, *s, &dst);
532✔
2088
                if (q < 0) {
532✔
UNCOV
2089
                        RET_GATHER(r, install_changes_add(changes, n_changes, q, *s, NULL));
×
UNCOV
2090
                        continue;
×
2091
                }
2092

2093
                if (!unit_name_is_valid(dst, valid_dst_type)) {
532✔
2094
                        /* Generate a proper error here: EUCLEAN if the name is generally bad, EIDRM if the
2095
                         * template status doesn't match. If we are doing presets don't bother reporting the
2096
                         * error. This also covers cases like 'systemctl preset serial-getty@.service', which
2097
                         * has no DefaultInstance, so there is nothing we can do. At the same time,
2098
                         * 'systemctl enable serial-getty@.service' should fail, the user should specify an
2099
                         * instance like in 'systemctl enable serial-getty@ttyS0.service'.
2100
                         */
UNCOV
2101
                        if (FLAGS_SET(file_flags, UNIT_FILE_IGNORE_AUXILIARY_FAILURE))
×
UNCOV
2102
                                continue;
×
2103

UNCOV
2104
                        if (unit_name_is_valid(dst, UNIT_NAME_ANY))
×
UNCOV
2105
                                RET_GATHER(r, install_changes_add(changes, n_changes, -EIDRM, dst, n));
×
2106
                        else
2107
                                RET_GATHER(r, install_changes_add(changes, n_changes, -EUCLEAN, dst, NULL));
×
2108

UNCOV
2109
                        continue;
×
2110
                }
2111

2112
                path = strjoin(config_path, "/", dst, suffix, n);
532✔
2113
                if (!path)
532✔
2114
                        return -ENOMEM;
2115

2116
                q = create_symlink(lp, info->path, path, /* force= */ true, changes, n_changes);
532✔
2117
                if (q != 0 && r >= 0)
532✔
2118
                        r = q;
532✔
2119

2120
                if (unit_file_exists(scope, lp, dst) == 0) {
532✔
2121
                        q = install_changes_add(changes, n_changes, INSTALL_CHANGE_DESTINATION_NOT_PRESENT, dst, info->path);
1✔
2122
                        if (q < 0)
1✔
2123
                                return q;
2124
                }
2125
        }
2126

2127
        return r;
2128
}
2129

2130
static int install_info_symlink_link(
530✔
2131
                InstallInfo *info,
2132
                const LookupPaths *lp,
2133
                const char *config_path,
2134
                bool force,
2135
                InstallChange **changes,
2136
                size_t *n_changes) {
2137

2138
        _cleanup_free_ char *path = NULL;
530✔
2139
        int r;
530✔
2140

2141
        assert(info);
530✔
2142
        assert(lp);
530✔
2143
        assert(config_path);
530✔
2144
        assert(info->path);
530✔
2145

2146
        r = in_search_path(lp, info->path);
530✔
2147
        if (r < 0)
530✔
2148
                return r;
2149
        if (r > 0)
530✔
2150
                return 0;
2151

2152
        path = path_join(config_path, info->name);
3✔
2153
        if (!path)
3✔
2154
                return -ENOMEM;
2155

2156
        return create_symlink(lp, info->path, path, force, changes, n_changes);
3✔
2157
}
2158

2159
static int install_info_apply(
530✔
2160
                RuntimeScope scope,
2161
                UnitFileFlags file_flags,
2162
                InstallInfo *info,
2163
                const LookupPaths *lp,
2164
                const char *config_path,
2165
                InstallChange **changes,
2166
                size_t *n_changes) {
2167

2168
        int r, q;
530✔
2169

2170
        assert(info);
530✔
2171
        assert(lp);
530✔
2172
        assert(config_path);
530✔
2173

2174
        if (info->install_mode != INSTALL_MODE_REGULAR)
530✔
2175
                return 0;
2176

2177
        bool force = file_flags & UNIT_FILE_FORCE;
530✔
2178

2179
        r = install_info_symlink_link(info, lp, config_path, force, changes, n_changes);
530✔
2180
        /* Do not count links to the unit file towards the "carries_install_info" count */
2181
        if (r < 0)
530✔
2182
                /* If linking of the file failed, do not try to create other symlinks,
2183
                 * because they might would pointing to a non-existent or wrong unit. */
2184
                return r;
2185

2186
        r = install_info_symlink_alias(scope, file_flags, info, lp, config_path, force, changes, n_changes);
530✔
2187

2188
        q = install_info_symlink_wants(scope, file_flags, info, lp, config_path, info->wanted_by, ".wants/", changes, n_changes);
530✔
2189
        if (q != 0 && r >= 0)
530✔
2190
                r = q;
493✔
2191

2192
        q = install_info_symlink_wants(scope, file_flags, info, lp, config_path, info->required_by, ".requires/", changes, n_changes);
530✔
2193
        if (q != 0 && r >= 0)
530✔
UNCOV
2194
                r = q;
×
2195

2196
        q = install_info_symlink_wants(scope, file_flags, info, lp, config_path, info->upheld_by, ".upholds/", changes, n_changes);
530✔
2197
        if (q != 0 && r >= 0)
530✔
UNCOV
2198
                r = q;
×
2199

2200
        return r;
2201
}
2202

2203
static int install_context_apply(
54✔
2204
                InstallContext *ctx,
2205
                const LookupPaths *lp,
2206
                UnitFileFlags file_flags,
2207
                const char *config_path,
2208
                SearchFlags flags,
2209
                InstallChange **changes,
2210
                size_t *n_changes) {
2211

2212
        InstallInfo *i;
54✔
2213
        int r;
54✔
2214

2215
        assert(ctx);
54✔
2216
        assert(lp);
54✔
2217
        assert(config_path);
54✔
2218

2219
        if (ordered_hashmap_isempty(ctx->will_process))
54✔
2220
                return 0;
2221

2222
        r = ordered_hashmap_ensure_allocated(&ctx->have_processed, &install_info_hash_ops);
49✔
2223
        if (r < 0)
49✔
2224
                return r;
2225

2226
        r = 0;
2227
        while ((i = ordered_hashmap_first(ctx->will_process))) {
602✔
2228
                int q;
553✔
2229

2230
                q = ordered_hashmap_move_one(ctx->have_processed, ctx->will_process, i->name);
553✔
2231
                if (q < 0)
553✔
2232
                        return q;
2233

2234
                q = install_info_traverse(ctx, lp, i, flags, NULL);
553✔
2235
                if (q < 0) {
553✔
UNCOV
2236
                        if (i->auxiliary) {
×
UNCOV
2237
                                q = install_changes_add(changes, n_changes, INSTALL_CHANGE_AUXILIARY_FAILED, i->name, NULL);
×
UNCOV
2238
                                if (q < 0)
×
2239
                                        return q;
UNCOV
2240
                                continue;
×
2241
                        }
2242

2243
                        return install_changes_add(changes, n_changes, q, i->name, NULL);
×
2244
                }
2245

2246
                /* We can attempt to process a masked unit when a different unit
2247
                 * that we were processing specifies it in Also=. */
2248
                if (i->install_mode == INSTALL_MODE_MASKED) {
553✔
2249
                        q = install_changes_add(changes, n_changes, INSTALL_CHANGE_IS_MASKED, i->path, NULL);
17✔
2250
                        if (q < 0)
17✔
2251
                                return q;
2252
                        if (r >= 0)
17✔
2253
                                /* Assume that something *could* have been enabled here,
2254
                                 * avoid "empty [Install] section" warning. */
2255
                                r += 1;
17✔
2256
                        continue;
17✔
2257
                }
2258

2259
                if (i->install_mode != INSTALL_MODE_REGULAR)
536✔
2260
                        continue;
6✔
2261

2262
                q = install_info_apply(ctx->scope, file_flags, i, lp, config_path, changes, n_changes);
530✔
2263
                if (r >= 0) {
530✔
2264
                        if (q < 0)
530✔
2265
                                r = q;
2266
                        else
2267
                                r += q;
530✔
2268
                }
2269
        }
2270

2271
        return r;
2272
}
2273

2274
static int install_context_mark_for_removal(
25✔
2275
                InstallContext *ctx,
2276
                const LookupPaths *lp,
2277
                Set **remove_symlinks_to,
2278
                const char *config_path,
2279
                InstallChange **changes,
2280
                size_t *n_changes) {
2281

2282
        InstallInfo *i;
25✔
2283
        int r;
25✔
2284

2285
        assert(ctx);
25✔
2286
        assert(lp);
25✔
2287
        assert(config_path);
25✔
2288

2289
        /* Marks all items for removal */
2290

2291
        if (ordered_hashmap_isempty(ctx->will_process))
25✔
2292
                return 0;
2293

2294
        r = ordered_hashmap_ensure_allocated(&ctx->have_processed, &install_info_hash_ops);
21✔
2295
        if (r < 0)
21✔
2296
                return r;
2297

2298
        while ((i = ordered_hashmap_first(ctx->will_process))) {
62✔
2299

2300
                r = ordered_hashmap_move_one(ctx->have_processed, ctx->will_process, i->name);
41✔
2301
                if (r < 0)
41✔
2302
                        return r;
2303

2304
                r = install_info_traverse(ctx, lp, i, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL);
41✔
2305
                if (r == -ENOLINK) {
41✔
UNCOV
2306
                        log_debug_errno(r, "Name %s leads to a dangling symlink, removing name.", i->name);
×
UNCOV
2307
                        r = install_changes_add(changes, n_changes, INSTALL_CHANGE_IS_DANGLING, i->path ?: i->name, NULL);
×
UNCOV
2308
                        if (r < 0)
×
2309
                                return r;
2310
                } else if (r == -ENOENT) {
41✔
UNCOV
2311
                        if (i->auxiliary)  /* some unit specified in Also= or similar is missing */
×
2312
                                log_debug_errno(r, "Auxiliary unit of %s not found, removing name.", i->name);
×
2313
                        else {
2314
                                log_debug_errno(r, "Unit %s not found, removing name.", i->name);
×
UNCOV
2315
                                r = install_changes_add(changes, n_changes, r, i->path ?: i->name, NULL);
×
2316
                                /* In case there's no unit, we still want to remove any leftover symlink, even if
2317
                                 * the unit might have been removed already, hence treating ENOENT as non-fatal. */
2318
                                if (r != -ENOENT)
×
2319
                                        return r;
2320
                        }
2321
                } else if (r < 0) {
41✔
UNCOV
2322
                        log_debug_errno(r, "Failed to find unit %s, removing name: %m", i->name);
×
UNCOV
2323
                        int k = install_changes_add(changes, n_changes, r, i->path ?: i->name, NULL);
×
2324
                        if (k != r)
×
2325
                                return k;
2326
                } else if (i->install_mode == INSTALL_MODE_MASKED) {
41✔
UNCOV
2327
                        log_debug("Unit file %s is masked, ignoring.", i->name);
×
2328
                        r = install_changes_add(changes, n_changes, INSTALL_CHANGE_IS_MASKED, i->path ?: i->name, NULL);
×
2329
                        if (r < 0)
×
2330
                                return r;
UNCOV
2331
                        continue;
×
2332
                } else if (i->install_mode != INSTALL_MODE_REGULAR) {
41✔
2333
                        log_debug("Unit %s has install mode %s, ignoring.",
2✔
2334
                                  i->name, install_mode_to_string(i->install_mode) ?: "invalid");
2335
                        continue;
2✔
2336
                }
2337

2338
                r = mark_symlink_for_removal(remove_symlinks_to, i->name);
39✔
2339
                if (r < 0)
39✔
2340
                        return r;
2341
        }
2342

2343
        return 0;
2344
}
2345

2346
int unit_file_mask(
5✔
2347
                RuntimeScope scope,
2348
                UnitFileFlags flags,
2349
                const char *root_dir,
2350
                char * const *names,
2351
                InstallChange **changes,
2352
                size_t *n_changes) {
2353

2354
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
5✔
2355
        const char *config_path;
5✔
2356
        int r;
5✔
2357

2358
        assert(scope >= 0);
5✔
2359
        assert(scope < _RUNTIME_SCOPE_MAX);
5✔
2360

2361
        r = lookup_paths_init(&lp, scope, 0, root_dir);
5✔
2362
        if (r < 0)
5✔
2363
                return r;
2364

2365
        config_path = (flags & UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
5✔
2366
        if (!config_path)
5✔
2367
                return -ENXIO;
2368

2369
        r = 0;
2370

2371
        STRV_FOREACH(name, names) {
10✔
2372
                _cleanup_free_ char *path = NULL;
5✔
2373

2374
                if (!unit_name_is_valid(*name, UNIT_NAME_ANY)) {
5✔
UNCOV
2375
                        RET_GATHER(r, -EINVAL);
×
UNCOV
2376
                        continue;
×
2377
                }
2378

2379
                path = path_make_absolute(*name, config_path);
5✔
2380
                if (!path)
5✔
2381
                        return -ENOMEM;
×
2382

2383
                RET_GATHER(r, create_symlink(&lp, "/dev/null", path, flags & UNIT_FILE_FORCE, changes, n_changes));
5✔
2384
        }
2385

2386
        return r;
2387
}
2388

2389
int unit_file_unmask(
5✔
2390
                RuntimeScope scope,
2391
                UnitFileFlags flags,
2392
                const char *root_dir,
2393
                char * const *names,
2394
                InstallChange **changes,
2395
                size_t *n_changes) {
2396

2397
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
5✔
2398
        _cleanup_set_free_ Set *remove_symlinks_to = NULL;
5✔
UNCOV
2399
        _cleanup_strv_free_ char **todo = NULL;
×
2400
        const char *config_path;
5✔
2401
        size_t n_todo = 0;
5✔
2402
        int r, q;
5✔
2403

2404
        assert(scope >= 0);
5✔
2405
        assert(scope < _RUNTIME_SCOPE_MAX);
5✔
2406

2407
        r = lookup_paths_init(&lp, scope, 0, root_dir);
5✔
2408
        if (r < 0)
5✔
2409
                return r;
2410

2411
        config_path = (flags & UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
5✔
2412
        if (!config_path)
5✔
2413
                return -ENXIO;
2414

2415
        bool dry_run = flags & UNIT_FILE_DRY_RUN;
5✔
2416

2417
        STRV_FOREACH(name, names) {
10✔
2418
                if (!unit_name_is_valid(*name, UNIT_NAME_ANY))
5✔
2419
                        return -EINVAL;
5✔
2420

2421
                /* If root_dir is set, we don't care about kernel command line or generators.
2422
                 * But if it is not set, we need to check for interference. */
2423
                if (!root_dir) {
5✔
UNCOV
2424
                        _cleanup_(install_info_clear) InstallInfo info = {
×
2425
                                .name = *name,  /* We borrow *name temporarily… */
1✔
2426
                                .install_mode = _INSTALL_MODE_INVALID,
2427
                        };
2428

2429
                        r = unit_file_search(NULL, &info, &lp, 0);
1✔
2430
                        if (r < 0) {
1✔
UNCOV
2431
                                if (r != -ENOENT)
×
UNCOV
2432
                                        log_debug_errno(r, "Failed to look up unit %s, ignoring: %m", info.name);
×
2433
                        } else if (info.install_mode == INSTALL_MODE_MASKED &&
1✔
UNCOV
2434
                                   path_is_generator(&lp, info.path)) {
×
UNCOV
2435
                                r = install_changes_add(changes, n_changes,
×
UNCOV
2436
                                                        INSTALL_CHANGE_IS_MASKED_GENERATOR, info.name, info.path);
×
2437
                                if (r < 0)
×
2438
                                        return r;
×
2439
                        }
2440

2441
                        TAKE_PTR(info.name);  /* … and give it back here */
1✔
2442
                }
2443

2444
                _cleanup_free_ char *path = path_make_absolute(*name, config_path);
10✔
2445
                if (!path)
5✔
2446
                        return -ENOMEM;
2447

2448
                r = null_or_empty_path(path);
5✔
2449
                if (r == -ENOENT)
5✔
2450
                        continue;
2✔
2451
                if (r < 0)
3✔
2452
                        return r;
2453
                if (r == 0)
3✔
UNCOV
2454
                        continue;
×
2455

2456
                if (!GREEDY_REALLOC0(todo, n_todo + 2))
3✔
2457
                        return -ENOMEM;
2458

2459
                todo[n_todo] = strdup(*name);
3✔
2460
                if (!todo[n_todo])
3✔
2461
                        return -ENOMEM;
2462

2463
                n_todo++;
3✔
2464
        }
2465

2466
        strv_uniq(todo);
5✔
2467

2468
        r = 0;
5✔
2469
        STRV_FOREACH(i, todo) {
8✔
2470
                _cleanup_free_ char *path = NULL;
3✔
2471
                const char *rp;
3✔
2472

2473
                path = path_make_absolute(*i, config_path);
3✔
2474
                if (!path)
3✔
2475
                        return -ENOMEM;
2476

2477
                if (!dry_run && unlink(path) < 0) {
3✔
UNCOV
2478
                        if (errno != ENOENT)
×
UNCOV
2479
                                RET_GATHER(r, install_changes_add(changes, n_changes, -errno, path, NULL));
×
2480

UNCOV
2481
                        continue;
×
2482
                }
2483

2484
                q = install_changes_add(changes, n_changes, INSTALL_CHANGE_UNLINK, path, NULL);
3✔
2485
                if (q < 0)
3✔
2486
                        return q;
2487

2488
                rp = skip_root(lp.root_dir, path);
3✔
2489
                q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: path);
3✔
2490
                if (q < 0)
3✔
2491
                        return q;
2492
        }
2493

2494
        RET_GATHER(r, remove_marked_symlinks(remove_symlinks_to, config_path, &lp, dry_run, changes, n_changes));
5✔
2495

2496
        return r;
2497
}
2498

2499
int unit_file_link(
1✔
2500
                RuntimeScope scope,
2501
                UnitFileFlags flags,
2502
                const char *root_dir,
2503
                char * const *files,
2504
                InstallChange **changes,
2505
                size_t *n_changes) {
2506

2507
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
1✔
2508
        _cleanup_ordered_hashmap_free_ OrderedHashmap *todo = NULL;
1✔
2509
        const char *config_path;
1✔
2510
        int r;
1✔
2511

2512
        assert(scope >= 0);
1✔
2513
        assert(scope < _RUNTIME_SCOPE_MAX);
1✔
2514
        assert(changes);
1✔
2515
        assert(n_changes);
1✔
2516

2517
        r = lookup_paths_init(&lp, scope, 0, root_dir);
1✔
2518
        if (r < 0)
1✔
2519
                return r;
2520

2521
        config_path = FLAGS_SET(flags, UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
1✔
2522
        if (!config_path)
1✔
2523
                return -ENXIO;
2524

2525
        STRV_FOREACH(file, files) {
2✔
2526
                _cleanup_free_ char *fn = NULL, *path = NULL, *full = NULL;
1✔
2527

2528
                if (ordered_hashmap_contains(todo, *file))
1✔
UNCOV
2529
                        continue;
×
2530

2531
                if (!path_is_absolute(*file))
1✔
UNCOV
2532
                        return install_changes_add(changes, n_changes, -EINVAL, *file, NULL);
×
2533

2534
                r = path_extract_filename(*file, &fn);
1✔
2535
                if (r < 0)
1✔
UNCOV
2536
                        return install_changes_add(changes, n_changes, r, *file, NULL);
×
2537

2538
                if (!unit_name_is_valid(fn, UNIT_NAME_ANY))
1✔
UNCOV
2539
                        return install_changes_add(changes, n_changes, -EUCLEAN, *file, NULL);
×
2540

2541
                full = path_join(lp.root_dir, *file);
1✔
2542
                if (!full)
1✔
2543
                        return -ENOMEM;
2544

2545
                r = verify_regular_at(AT_FDCWD, full, /* follow= */ false);
1✔
2546
                if (r < 0)
1✔
UNCOV
2547
                        return install_changes_add(changes, n_changes, r, *file, NULL);
×
2548

2549
                r = in_search_path(&lp, *file);
1✔
2550
                if (r < 0)
1✔
UNCOV
2551
                        return install_changes_add(changes, n_changes, r, *file, NULL);
×
2552
                if (r > 0)
1✔
2553
                        /* A silent noop if the file is already in the search path. */
UNCOV
2554
                        continue;
×
2555

2556
                if (underneath_search_path(&lp, *file))
1✔
2557
                        return install_changes_add(changes, n_changes, -ETXTBSY, *file, NULL);
×
2558

2559
                path = strdup(*file);
1✔
2560
                if (!path)
1✔
2561
                        return -ENOMEM;
2562

2563
                r = ordered_hashmap_ensure_put(&todo, &path_hash_ops_free_free, path, fn);
1✔
2564
                if (r < 0)
1✔
2565
                        return r;
2566
                assert(r > 0);
1✔
2567

2568
                TAKE_PTR(path);
1✔
2569
                TAKE_PTR(fn);
1✔
2570
        }
2571

2572
        r = 0;
1✔
2573

2574
        const char *fn, *path;
1✔
2575
        ORDERED_HASHMAP_FOREACH_KEY(fn, path, todo) {
2✔
2576
                _cleanup_free_ char *new_path = NULL;
1✔
2577

2578
                new_path = path_make_absolute(fn, config_path);
1✔
2579
                if (!new_path)
1✔
UNCOV
2580
                        return -ENOMEM;
×
2581

2582
                RET_GATHER(r, create_symlink(&lp, path, new_path, FLAGS_SET(flags, UNIT_FILE_FORCE), changes, n_changes));
1✔
2583
        }
2584

2585
        return r;
1✔
2586
}
2587

2588
static int path_shall_revert(const LookupPaths *lp, const char *path) {
1✔
2589
        int r;
1✔
2590

2591
        assert(lp);
1✔
2592
        assert(path);
1✔
2593

2594
        /* Checks whether the path is one where the drop-in directories shall be removed. */
2595

2596
        r = path_is_config(lp, path, true);
1✔
2597
        if (r != 0)
1✔
2598
                return r;
2599

UNCOV
2600
        r = path_is_control(lp, path);
×
UNCOV
2601
        if (r != 0)
×
2602
                return r;
2603

UNCOV
2604
        return path_is_transient(lp, path);
×
2605
}
2606

2607
int unit_file_revert(
4✔
2608
                RuntimeScope scope,
2609
                const char *root_dir,
2610
                char * const *names,
2611
                InstallChange **changes,
2612
                size_t *n_changes) {
2613

2614
        _cleanup_set_free_ Set *remove_symlinks_to = NULL;
4✔
2615
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
4✔
2616
        _cleanup_strv_free_ char **todo = NULL;
4✔
2617
        size_t n_todo = 0;
4✔
2618
        int r, q;
4✔
2619

2620
        /* Puts a unit file back into vendor state. This means:
2621
         *
2622
         * a) we remove all drop-in snippets added by the user ("config"), add to transient units
2623
         *    ("transient"), and added via "systemctl set-property" ("control"), but not if the drop-in is
2624
         *    generated ("generated").
2625
         *
2626
         * c) if there's a vendor unit file (i.e. one in /usr) we remove any configured overriding unit files
2627
         *    (i.e. in "config", but not in "transient" or "control" or even "generated").
2628
         *
2629
         * We remove all that in both the runtime and the persistent directories, if that applies.
2630
         */
2631

2632
        r = lookup_paths_init(&lp, scope, 0, root_dir);
4✔
2633
        if (r < 0)
4✔
2634
                return r;
2635

2636
        STRV_FOREACH(name, names) {
8✔
2637
                bool has_vendor = false;
4✔
2638

2639
                if (!unit_name_is_valid(*name, UNIT_NAME_ANY))
4✔
2640
                        return -EINVAL;
2641

2642
                STRV_FOREACH(p, lp.search_path) {
52✔
2643
                        _cleanup_free_ char *path = NULL, *dropin = NULL;
48✔
2644
                        struct stat st;
48✔
2645

2646
                        path = path_make_absolute(*name, *p);
48✔
2647
                        if (!path)
48✔
2648
                                return -ENOMEM;
2649

2650
                        r = RET_NERRNO(lstat(path, &st));
48✔
2651
                        if (r < 0) {
42✔
2652
                                if (r != -ENOENT)
42✔
UNCOV
2653
                                        return install_changes_add(changes, n_changes, r, path, NULL);
×
2654
                        } else if (S_ISREG(st.st_mode)) {
6✔
2655
                                /* Check if there's a vendor version */
2656
                                r = path_is_vendor_or_generator(&lp, path);
5✔
2657
                                if (r < 0)
5✔
UNCOV
2658
                                        return install_changes_add(changes, n_changes, r, path, NULL);
×
2659
                                if (r > 0)
5✔
2660
                                        has_vendor = true;
4✔
2661
                        }
2662

2663
                        dropin = strjoin(path, ".d");
48✔
2664
                        if (!dropin)
48✔
2665
                                return -ENOMEM;
2666

2667
                        r = RET_NERRNO(lstat(dropin, &st));
48✔
2668
                        if (r < 0) {
47✔
2669
                                if (r != -ENOENT)
47✔
UNCOV
2670
                                        return install_changes_add(changes, n_changes, r, dropin, NULL);
×
2671
                        } else if (S_ISDIR(st.st_mode)) {
1✔
2672
                                /* Remove the drop-ins */
2673
                                r = path_shall_revert(&lp, dropin);
1✔
2674
                                if (r < 0)
1✔
UNCOV
2675
                                        return install_changes_add(changes, n_changes, r, dropin, NULL);
×
2676
                                if (r > 0) {
1✔
2677
                                        if (!GREEDY_REALLOC0(todo, n_todo + 2))
1✔
2678
                                                return -ENOMEM;
2679

2680
                                        todo[n_todo++] = TAKE_PTR(dropin);
1✔
2681
                                }
2682
                        }
2683
                }
2684

2685
                if (!has_vendor)
4✔
UNCOV
2686
                        continue;
×
2687

2688
                /* OK, there's a vendor version, hence drop all configuration versions */
2689
                STRV_FOREACH(p, lp.search_path) {
52✔
2690
                        _cleanup_free_ char *path = NULL;
48✔
2691
                        struct stat st;
48✔
2692

2693
                        path = path_make_absolute(*name, *p);
48✔
2694
                        if (!path)
48✔
2695
                                return -ENOMEM;
2696

2697
                        r = RET_NERRNO(lstat(path, &st));
48✔
2698
                        if (r < 0) {
42✔
2699
                                if (r != -ENOENT)
42✔
UNCOV
2700
                                        return install_changes_add(changes, n_changes, r, path, NULL);
×
2701
                        } else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
6✔
2702
                                r = path_is_config(&lp, path, true);
6✔
2703
                                if (r < 0)
6✔
UNCOV
2704
                                        return install_changes_add(changes, n_changes, r, path, NULL);
×
2705
                                if (r > 0) {
6✔
2706
                                        if (!GREEDY_REALLOC0(todo, n_todo + 2))
2✔
2707
                                                return -ENOMEM;
2708

2709
                                        todo[n_todo++] = TAKE_PTR(path);
2✔
2710
                                }
2711
                        }
2712
                }
2713
        }
2714

2715
        strv_uniq(todo);
4✔
2716

2717
        r = 0;
4✔
2718
        STRV_FOREACH(i, todo) {
7✔
2719
                _cleanup_strv_free_ char **fs = NULL;
3✔
2720
                const char *rp;
3✔
2721

2722
                (void) get_files_in_directory(*i, &fs);
3✔
2723

2724
                q = rm_rf(*i, REMOVE_ROOT|REMOVE_PHYSICAL);
3✔
2725
                if (q < 0 && q != -ENOENT && r >= 0) {
3✔
UNCOV
2726
                        r = q;
×
UNCOV
2727
                        continue;
×
2728
                }
2729

2730
                STRV_FOREACH(j, fs) {
4✔
2731
                        _cleanup_free_ char *t = NULL;
1✔
2732

2733
                        t = path_join(*i, *j);
1✔
2734
                        if (!t)
1✔
2735
                                return -ENOMEM;
2736

2737
                        q = install_changes_add(changes, n_changes, INSTALL_CHANGE_UNLINK, t, NULL);
1✔
2738
                        if (q < 0)
1✔
2739
                                return q;
2740
                }
2741

2742
                q = install_changes_add(changes, n_changes, INSTALL_CHANGE_UNLINK, *i, NULL);
3✔
2743
                if (q < 0)
3✔
2744
                        return q;
2745

2746
                rp = skip_root(lp.root_dir, *i);
3✔
2747
                q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: *i);
3✔
2748
                if (q < 0)
3✔
2749
                        return q;
2750
        }
2751

2752
        q = remove_marked_symlinks(remove_symlinks_to, lp.runtime_config, &lp, false, changes, n_changes);
4✔
2753
        if (r >= 0)
4✔
2754
                r = q;
4✔
2755

2756
        q = remove_marked_symlinks(remove_symlinks_to, lp.persistent_config, &lp, false, changes, n_changes);
4✔
2757
        if (r >= 0)
4✔
2758
                r = q;
4✔
2759

2760
        return r;
2761
}
2762

2763
int unit_file_add_dependency(
1✔
2764
                RuntimeScope scope,
2765
                UnitFileFlags file_flags,
2766
                const char *root_dir,
2767
                char * const *names,
2768
                const char *target,
2769
                UnitDependency dep,
2770
                InstallChange **changes,
2771
                size_t *n_changes) {
2772

2773
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
1✔
2774
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
1✔
2775
        InstallInfo *info, *target_info;
1✔
2776
        const char *config_path;
1✔
2777
        int r;
1✔
2778

2779
        assert(scope >= 0);
1✔
2780
        assert(scope < _RUNTIME_SCOPE_MAX);
1✔
2781
        assert(target);
1✔
2782
        assert(IN_SET(dep, UNIT_WANTS, UNIT_REQUIRES));
1✔
2783

2784
        if (!unit_name_is_valid(target, UNIT_NAME_ANY))
1✔
UNCOV
2785
                return install_changes_add(changes, n_changes, -EUCLEAN, target, NULL);
×
2786

2787
        r = lookup_paths_init(&lp, scope, 0, root_dir);
1✔
2788
        if (r < 0)
1✔
2789
                return r;
2790

2791
        config_path = (file_flags & UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
1✔
2792
        if (!config_path)
1✔
2793
                return -ENXIO;
2794

2795
        r = install_info_discover_and_check(&ctx, &lp, target, SEARCH_FOLLOW_CONFIG_SYMLINKS,
1✔
2796
                                            &target_info, changes, n_changes);
2797
        if (r < 0)
1✔
2798
                return r;
2799

2800
        assert(target_info->install_mode == INSTALL_MODE_REGULAR);
1✔
2801

2802
        STRV_FOREACH(name, names) {
2✔
2803
                char ***l;
1✔
2804

2805
                r = install_info_discover_and_check(&ctx, &lp, *name,
1✔
2806
                                                    SEARCH_FOLLOW_CONFIG_SYMLINKS,
2807
                                                    &info, changes, n_changes);
2808
                if (r < 0)
1✔
2809
                        return r;
2810

2811
                assert(info->install_mode == INSTALL_MODE_REGULAR);
1✔
2812

2813
                /* We didn't actually load anything from the unit
2814
                 * file, but instead just add in our new symlink to
2815
                 * create. */
2816

2817
                if (dep == UNIT_WANTS)
1✔
2818
                        l = &info->wanted_by;
1✔
UNCOV
2819
                else if (dep == UNIT_REQUIRES)
×
UNCOV
2820
                        l = &info->required_by;
×
2821
                else
UNCOV
2822
                        l = &info->upheld_by;
×
2823

2824
                strv_free(*l);
1✔
2825
                *l = strv_new(target_info->name);
1✔
2826
                if (!*l)
1✔
2827
                        return -ENOMEM;
2828
        }
2829

2830
        return install_context_apply(&ctx, &lp, file_flags, config_path,
1✔
2831
                                     SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes);
2832
}
2833

2834
static int do_unit_file_enable(
27✔
2835
                const LookupPaths *lp,
2836
                RuntimeScope scope,
2837
                UnitFileFlags flags,
2838
                const char *config_path,
2839
                char * const *names_or_paths,
2840
                InstallChange **changes,
2841
                size_t *n_changes) {
2842

2843
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
27✔
2844
        InstallInfo *info;
27✔
2845
        int r;
27✔
2846

2847
        STRV_FOREACH(name, names_or_paths) {
53✔
2848
                r = install_info_discover_and_check(&ctx, lp, *name,
27✔
2849
                                                    SEARCH_LOAD | SEARCH_FOLLOW_CONFIG_SYMLINKS,
2850
                                                    &info, changes, n_changes);
2851
                if (r < 0)
27✔
2852
                        return r;
2853

2854
                assert(info->install_mode == INSTALL_MODE_REGULAR);
26✔
2855
        }
2856

2857
        /* This will return the number of symlink rules that were
2858
           supposed to be created, not the ones actually created. This
2859
           is useful to determine whether the passed units had any
2860
           installation data at all. */
2861

2862
        return install_context_apply(&ctx, lp, flags, config_path,
26✔
2863
                                     SEARCH_LOAD, changes, n_changes);
2864
}
2865

2866
int unit_file_enable(
25✔
2867
                RuntimeScope scope,
2868
                UnitFileFlags flags,
2869
                const char *root_dir,
2870
                char * const *names_or_paths,
2871
                InstallChange **changes,
2872
                size_t *n_changes) {
2873

2874
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
25✔
2875
        int r;
25✔
2876

2877
        assert(scope >= 0);
25✔
2878
        assert(scope < _RUNTIME_SCOPE_MAX);
25✔
2879

2880
        r = lookup_paths_init(&lp, scope, 0, root_dir);
25✔
2881
        if (r < 0)
25✔
2882
                return r;
2883

2884
        const char *config_path = config_path_from_flags(&lp, flags);
25✔
2885
        if (!config_path)
25✔
2886
                return -ENXIO;
2887

2888
        return do_unit_file_enable(&lp, scope, flags, config_path, names_or_paths, changes, n_changes);
25✔
2889
}
2890

2891
static int do_unit_file_disable(
15✔
2892
                const LookupPaths *lp,
2893
                RuntimeScope scope,
2894
                UnitFileFlags flags,
2895
                const char *config_path,
2896
                char * const *names,
2897
                InstallChange **changes,
2898
                size_t *n_changes) {
2899

2900
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
15✔
2901
        bool has_install_info = false;
15✔
2902
        int r;
15✔
2903

2904
        STRV_FOREACH(name, names) {
30✔
2905
                InstallInfo *info;
15✔
2906

2907
                if (!unit_name_is_valid(*name, UNIT_NAME_ANY))
15✔
UNCOV
2908
                        return install_changes_add(changes, n_changes, -EUCLEAN, *name, NULL);
×
2909

2910
                r = install_info_add(&ctx, *name, NULL, lp->root_dir, /* auxiliary= */ false, &info);
15✔
2911
                if (r >= 0)
15✔
2912
                        r = install_info_traverse(&ctx, lp, info, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL);
15✔
2913
                if (r < 0) {
15✔
2914
                        r = install_changes_add(changes, n_changes, r, *name, NULL);
×
2915
                        /* In case there's no unit, we still want to remove any leftover symlink, even if
2916
                         * the unit might have been removed already, hence treating ENOENT as non-fatal. */
UNCOV
2917
                        if (r != -ENOENT)
×
2918
                                return r;
2919
                }
2920

2921
                /* If we enable multiple units, some with install info and others without,
2922
                 * the "empty [Install] section" warning is not shown. Let's make the behavior
2923
                 * of disable align with that. */
2924
                has_install_info = has_install_info || install_info_has_rules(info) || install_info_has_also(info);
15✔
2925
        }
2926

2927
        _cleanup_set_free_ Set *remove_symlinks_to = NULL;
15✔
2928
        r = install_context_mark_for_removal(&ctx, lp, &remove_symlinks_to, config_path, changes, n_changes);
15✔
2929
        if (r >= 0)
15✔
2930
                r = remove_marked_symlinks(remove_symlinks_to, config_path, lp, flags & UNIT_FILE_DRY_RUN, changes, n_changes);
15✔
2931
        if (r < 0)
15✔
UNCOV
2932
                return r;
×
2933

2934
        /* The warning is shown only if it's a no-op */
2935
        return install_changes_have_modification(*changes, *n_changes) || has_install_info;
15✔
2936
}
2937

2938
int unit_file_disable(
13✔
2939
                RuntimeScope scope,
2940
                UnitFileFlags flags,
2941
                const char *root_dir,
2942
                char * const *files,
2943
                InstallChange **changes,
2944
                size_t *n_changes) {
2945

2946
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
13✔
2947
        int r;
13✔
2948

2949
        assert(scope >= 0);
13✔
2950
        assert(scope < _RUNTIME_SCOPE_MAX);
13✔
2951

2952
        r = lookup_paths_init(&lp, scope, 0, root_dir);
13✔
2953
        if (r < 0)
13✔
2954
                return r;
2955

2956
        const char *config_path = config_path_from_flags(&lp, flags);
13✔
2957
        if (!config_path)
13✔
2958
                return -ENXIO;
2959

2960
        return do_unit_file_disable(&lp, scope, flags, config_path, files, changes, n_changes);
13✔
2961
}
2962

2963
static int normalize_linked_files(
2✔
2964
                RuntimeScope scope,
2965
                const LookupPaths *lp,
2966
                char * const *names_or_paths,
2967
                char ***ret_names,
2968
                char ***ret_files) {
2969

2970
        /* This is similar to normalize_filenames()/normalize_names() in src/systemctl/,
2971
         * but operates on real unit names. For each argument we look up the actual path
2972
         * where the unit is found. This way linked units can be re-enabled successfully. */
2973

2974
        _cleanup_strv_free_ char **files = NULL, **names = NULL;
2✔
2975
        int r;
2✔
2976

2977
        STRV_FOREACH(a, names_or_paths) {
4✔
UNCOV
2978
                _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
×
2979
                InstallInfo *i = NULL;
2✔
2980
                _cleanup_free_ char *n = NULL;
2✔
2981

2982
                r = path_extract_filename(*a, &n);
2✔
2983
                if (r < 0)
2✔
2984
                        return r;
2985
                if (r == O_DIRECTORY)
2✔
UNCOV
2986
                        return log_debug_errno(SYNTHETIC_ERRNO(EISDIR),
×
2987
                                               "Unexpected path to a directory \"%s\", refusing.", *a);
2988

2989
                if (!is_path(*a) && !unit_name_is_valid(*a, UNIT_NAME_INSTANCE)) {
2✔
2990
                        r = install_info_discover(&ctx, lp, n, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i, NULL, NULL);
2✔
2991
                        if (r < 0)
2✔
2992
                                log_debug_errno(r, "Failed to discover unit \"%s\", operating on name: %m", n);
×
2993
                }
2994

2995
                r = strv_consume(&names, TAKE_PTR(n));
2✔
2996
                if (r < 0)
2✔
2997
                        return r;
2998

2999
                const char *p = NULL;
2✔
3000
                if (i && i->path && i->root)
2✔
3001
                        /* Use startswith here, because we know that paths are normalized, and
3002
                         * path_startswith() would give us a relative path, but we need an absolute path
3003
                         * relative to i->root.
3004
                         *
3005
                         * In other words: /var/tmp/instroot.1234/etc/systemd/system/frobnicator.service
3006
                         * is replaced by /etc/systemd/system/frobnicator.service, which is "absolute"
3007
                         * in a sense, but only makes sense "relative" to /var/tmp/instroot.1234/.
3008
                         */
3009
                        p = startswith(i->path, i->root);
1✔
3010

3011
                r = strv_extend(&files, p ?: *a);
2✔
3012
                if (r < 0)
2✔
3013
                        return r;
3014
        }
3015

3016
        *ret_names = TAKE_PTR(names);
2✔
3017
        *ret_files = TAKE_PTR(files);
2✔
3018
        return 0;
2✔
3019
}
3020

3021
int unit_file_reenable(
2✔
3022
                RuntimeScope scope,
3023
                UnitFileFlags flags,
3024
                const char *root_dir,
3025
                char * const *names_or_paths,
3026
                InstallChange **changes,
3027
                size_t *n_changes) {
3028

3029
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
2✔
3030
        _cleanup_strv_free_ char **names = NULL, **files = NULL;
2✔
3031
        int r;
2✔
3032

3033
        assert(scope >= 0);
2✔
3034
        assert(scope < _RUNTIME_SCOPE_MAX);
2✔
3035

3036
        r = lookup_paths_init(&lp, scope, 0, root_dir);
2✔
3037
        if (r < 0)
2✔
3038
                return r;
3039

3040
        const char *config_path = config_path_from_flags(&lp, flags);
2✔
3041
        if (!config_path)
2✔
3042
                return -ENXIO;
3043

3044
        r = normalize_linked_files(scope, &lp, names_or_paths, &names, &files);
2✔
3045
        if (r < 0)
2✔
3046
                return r;
3047

3048
        /* First, we invoke the disable command with only the basename... */
3049
        r = do_unit_file_disable(&lp, scope, flags, config_path, names, changes, n_changes);
2✔
3050
        if (r < 0)
2✔
3051
                return r;
3052

3053
        /* But the enable command with the full name */
3054
        return do_unit_file_enable(&lp, scope, flags, config_path, files, changes, n_changes);
2✔
3055
}
3056

3057
int unit_file_set_default(
4✔
3058
                RuntimeScope scope,
3059
                UnitFileFlags flags,
3060
                const char *root_dir,
3061
                const char *name,
3062
                InstallChange **changes,
3063
                size_t *n_changes) {
3064

3065
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
4✔
3066
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
4✔
3067
        InstallInfo *info;
4✔
3068
        const char *new_path;
4✔
3069
        int r;
4✔
3070

3071
        assert(scope >= 0);
4✔
3072
        assert(scope < _RUNTIME_SCOPE_MAX);
4✔
3073
        assert(name);
4✔
3074

3075
        if (unit_name_to_type(name) != UNIT_TARGET) /* this also validates the name */
4✔
3076
                return -EINVAL;
3077
        if (streq(name, SPECIAL_DEFAULT_TARGET))
4✔
3078
                return -EINVAL;
3079

3080
        r = lookup_paths_init(&lp, scope, 0, root_dir);
4✔
3081
        if (r < 0)
4✔
3082
                return r;
3083

3084
        r = install_info_discover_and_check(&ctx, &lp, name, 0, &info, changes, n_changes);
4✔
3085
        if (r < 0)
4✔
3086
                return r;
3087

3088
        new_path = strjoina(lp.persistent_config, "/" SPECIAL_DEFAULT_TARGET);
15✔
3089
        return create_symlink(&lp, info->path, new_path, flags & UNIT_FILE_FORCE, changes, n_changes);
3✔
3090
}
3091

3092
int unit_file_get_default(
9✔
3093
                RuntimeScope scope,
3094
                const char *root_dir,
3095
                char **ret) {
3096

3097
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
9✔
3098
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
9✔
3099
        InstallInfo *info;
9✔
3100
        int r;
9✔
3101

3102
        assert(scope >= 0);
9✔
3103
        assert(scope < _RUNTIME_SCOPE_MAX);
9✔
3104
        assert(ret);
9✔
3105

3106
        r = lookup_paths_init(&lp, scope, 0, root_dir);
9✔
3107
        if (r < 0)
9✔
3108
                return r;
3109

3110
        r = install_info_discover(&ctx, &lp, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS,
9✔
3111
                                  &info, NULL, NULL);
3112
        if (r < 0)
9✔
3113
                return r;
3114

3115
        return strdup_to(ret, info->name);
7✔
3116
}
3117

3118
int unit_file_lookup_state(
2,587✔
3119
                RuntimeScope scope,
3120
                const LookupPaths *lp,
3121
                const char *name,
3122
                UnitFileState *ret) {
3123

3124
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
2,587✔
3125
        InstallInfo *info;
2,587✔
3126
        UnitFileState state;
2,587✔
3127
        int r;
2,587✔
3128

3129
        assert(lp);
2,587✔
3130
        assert(name);
2,587✔
3131

3132
        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
2,587✔
3133
                return -EINVAL;
3134

3135
        r = install_info_discover(&ctx, lp, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
2,587✔
3136
                                  &info, NULL, NULL);
3137
        if (r < 0)
2,587✔
3138
                return log_debug_errno(r, "Failed to discover unit %s: %m", name);
38✔
3139

3140
        assert(IN_SET(info->install_mode, INSTALL_MODE_REGULAR, INSTALL_MODE_MASKED));
2,549✔
3141
        log_debug("Found unit %s at %s (%s)", name, strna(info->path),
2,561✔
3142
                  info->install_mode == INSTALL_MODE_REGULAR ? "regular file" : "mask");
3143

3144
        /* Shortcut things, if the caller just wants to know if this unit exists. */
3145
        if (!ret)
2,549✔
3146
                return 0;
3147

3148
        switch (info->install_mode) {
2,541✔
3149

3150
        case INSTALL_MODE_MASKED:
18✔
3151
                r = path_is_runtime(lp, info->path, true);
18✔
3152
                if (r < 0)
18✔
3153
                        return r;
3154

3155
                state = r > 0 ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
18✔
3156
                break;
18✔
3157

3158
        case INSTALL_MODE_REGULAR:
2,523✔
3159
                /* Check if the name we were querying is actually an alias */
3160
                if (!path_equal_filename(name, info->path) && !unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
2,523✔
3161
                        state = UNIT_FILE_ALIAS;
72✔
3162
                        break;
72✔
3163
                }
3164

3165
                r = path_is_generator(lp, info->path);
2,451✔
3166
                if (r < 0)
2,451✔
3167
                        return r;
3168
                if (r > 0) {
2,451✔
3169
                        state = UNIT_FILE_GENERATED;
9✔
3170
                        break;
9✔
3171
                }
3172

3173
                r = path_is_transient(lp, info->path);
2,442✔
3174
                if (r < 0)
2,442✔
3175
                        return r;
3176
                if (r > 0) {
2,442✔
3177
                        state = UNIT_FILE_TRANSIENT;
14✔
3178
                        break;
14✔
3179
                }
3180

3181
                /* Check if any of the Alias= symlinks have been created.
3182
                 * We ignore other aliases, and only check those that would
3183
                 * be created by systemctl enable for this unit. */
3184
                r = find_symlinks_in_scope(scope, lp, info, true, &state);
2,428✔
3185
                if (r < 0)
2,428✔
3186
                        return r;
3187
                if (r > 0)
2,428✔
3188
                        break;
3189

3190
                /* Check if the file is known under other names. If it is,
3191
                 * it might be in use. Report that as UNIT_FILE_INDIRECT. */
3192
                r = find_symlinks_in_scope(scope, lp, info, false, &state);
2,269✔
3193
                if (r < 0)
2,269✔
3194
                        return r;
3195
                if (r > 0)
2,269✔
3196
                        state = UNIT_FILE_INDIRECT;
4✔
3197
                else {
3198
                        if (install_info_has_rules(info))
2,265✔
3199
                                state = UNIT_FILE_DISABLED;
319✔
3200
                        else if (install_info_has_also(info))
1,946✔
3201
                                state = UNIT_FILE_INDIRECT;
28✔
3202
                        else
3203
                                state = UNIT_FILE_STATIC;
1,918✔
3204
                }
3205

3206
                break;
3207

UNCOV
3208
        default:
×
UNCOV
3209
                assert_not_reached();
×
3210
        }
3211

3212
        *ret = state;
2,541✔
3213
        return 0;
2,541✔
3214
}
3215

3216
int unit_file_get_state(
478✔
3217
                RuntimeScope scope,
3218
                const char *root_dir,
3219
                const char *name,
3220
                UnitFileState *ret) {
3221

3222
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
478✔
3223
        int r;
478✔
3224

3225
        assert(scope >= 0);
478✔
3226
        assert(scope < _RUNTIME_SCOPE_MAX);
478✔
3227
        assert(name);
478✔
3228

3229
        r = lookup_paths_init(&lp, scope, 0, root_dir);
478✔
3230
        if (r < 0)
478✔
3231
                return r;
3232

3233
        return unit_file_lookup_state(scope, &lp, name, ret);
478✔
3234
}
3235

3236
int unit_file_exists_full(
1,486✔
3237
                RuntimeScope scope,
3238
                const LookupPaths *lp,
3239
                SearchFlags flags,
3240
                const char *name,
3241
                char **ret_path) {
3242

3243
        _cleanup_(install_context_done) InstallContext c = {
1,486✔
3244
                .scope = scope,
3245
        };
3246
        int r;
1,486✔
3247

3248
        assert(lp);
1,486✔
3249
        assert(name);
1,486✔
3250

3251
        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
1,486✔
3252
                return -EINVAL;
3253

3254
        InstallInfo *info = NULL;
1,486✔
3255
        r = install_info_discover(
2,972✔
3256
                        &c,
3257
                        lp,
3258
                        name,
3259
                        flags,
3260
                        ret_path ? &info : NULL,
3261
                        /* changes= */ NULL,
3262
                        /* n_changes= */ NULL);
3263
        if (r == -ENOENT) {
1,486✔
3264
                if (ret_path)
955✔
UNCOV
3265
                        *ret_path = NULL;
×
3266
                return 0;
955✔
3267
        }
3268
        if (r < 0)
531✔
3269
                return r;
3270

3271
        if (ret_path) {
531✔
UNCOV
3272
                assert(info);
×
3273

UNCOV
3274
                r = strdup_to(ret_path, info->path);
×
UNCOV
3275
                if (r < 0)
×
UNCOV
3276
                        return r;
×
3277
        }
3278

3279
        return 1;
3280
}
3281

3282
static int split_pattern_into_name_and_instances(const char *pattern, char **out_unit_name, char ***out_instances) {
7,440✔
UNCOV
3283
        _cleanup_strv_free_ char **instances = NULL;
×
3284
        _cleanup_free_ char *unit_name = NULL;
7,440✔
3285
        int r;
7,440✔
3286

3287
        assert(pattern);
7,440✔
3288
        assert(out_instances);
7,440✔
3289
        assert(out_unit_name);
7,440✔
3290

3291
        r = extract_first_word(&pattern, &unit_name, NULL, EXTRACT_RETAIN_ESCAPE);
7,440✔
3292
        if (r < 0)
7,440✔
3293
                return r;
3294

3295
        /* We handle the instances logic when unit name is extracted */
3296
        if (pattern) {
7,440✔
3297
                /* We only create instances when a rule of templated unit
3298
                 * is seen. A rule like enable foo@.service a b c will
3299
                 * result in an array of (a, b, c) as instance names */
3300
                if (!unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE))
2✔
3301
                        return -EINVAL;
3302

3303
                instances = strv_split(pattern, WHITESPACE);
2✔
3304
                if (!instances)
2✔
3305
                        return -ENOMEM;
3306

3307
                *out_instances = TAKE_PTR(instances);
2✔
3308
        }
3309

3310
        *out_unit_name = TAKE_PTR(unit_name);
7,440✔
3311

3312
        return 0;
7,440✔
3313
}
3314

3315
static int presets_find_config(RuntimeScope scope, const char *root_dir, char ***ret) {
290✔
3316
        static const char* const initrd_dirs[] = { CONF_PATHS("systemd/initrd-preset"), NULL };
290✔
3317
        static const char* const system_dirs[] = { CONF_PATHS("systemd/system-preset"), NULL };
290✔
3318
        static const char* const user_dirs[] = { CONF_PATHS("systemd/user-preset"), NULL };
290✔
3319
        const char* const* dirs;
290✔
3320
        int r;
290✔
3321

3322
        assert(scope >= 0);
290✔
3323
        assert(scope < _RUNTIME_SCOPE_MAX);
290✔
3324

3325
        if (scope == RUNTIME_SCOPE_SYSTEM) {
290✔
3326
                r = chase_and_access("/etc/initrd-release", root_dir, CHASE_PREFIX_ROOT, F_OK, /* ret_path= */ NULL);
253✔
3327
                if (r < 0 && r != -ENOENT)
253✔
3328
                        return r;
3329

3330
                /* Make sure that we fall back to the system preset directories if we're operating on a root
3331
                 * directory without initrd preset directories. This makes sure that we don't regress when
3332
                 * using a newer systemctl to operate on a root directory with an older version of systemd
3333
                 * installed that doesn't yet known about initrd preset directories. */
3334
                if (r >= 0)
253✔
UNCOV
3335
                        STRV_FOREACH(d, initrd_dirs) {
×
UNCOV
3336
                                r = chase_and_access(*d, root_dir, CHASE_PREFIX_ROOT, F_OK, /* ret_path= */ NULL);
×
UNCOV
3337
                                if (r >= 0)
×
UNCOV
3338
                                        return conf_files_list_strv(ret, ".preset", root_dir, 0, initrd_dirs);
×
UNCOV
3339
                                if (r != -ENOENT)
×
3340
                                        return r;
3341
                        }
3342

3343
                dirs = system_dirs;
3344
        } else if (IN_SET(scope, RUNTIME_SCOPE_GLOBAL, RUNTIME_SCOPE_USER))
37✔
3345
                dirs = user_dirs;
3346
        else
UNCOV
3347
                assert_not_reached();
×
3348

3349
        return conf_files_list_strv(ret, ".preset", root_dir, 0, dirs);
290✔
3350
}
3351

3352
static int read_presets(RuntimeScope scope, const char *root_dir, UnitFilePresets *presets) {
290✔
3353
        _cleanup_(unit_file_presets_done) UnitFilePresets ps = {};
290✔
UNCOV
3354
        _cleanup_strv_free_ char **files = NULL;
×
3355
        int r;
290✔
3356

3357
        assert(scope >= 0);
290✔
3358
        assert(scope < _RUNTIME_SCOPE_MAX);
290✔
3359
        assert(presets);
290✔
3360

3361
        r = presets_find_config(scope, root_dir, &files);
290✔
3362
        if (r < 0)
290✔
3363
                return r;
3364

3365
        STRV_FOREACH(p, files) {
1,322✔
3366
                _cleanup_fclose_ FILE *f = NULL;
1,322✔
3367
                int n = 0;
1,032✔
3368

3369
                f = fopen(*p, "re");
1,032✔
3370
                if (!f) {
1,032✔
UNCOV
3371
                        if (errno == ENOENT)
×
UNCOV
3372
                                continue;
×
3373

UNCOV
3374
                        return -errno;
×
3375
                }
3376

3377
                for (;;) {
33,237✔
3378
                        _cleanup_free_ char *line = NULL;
33,237✔
3379
                        _cleanup_(unit_file_preset_rule_done) UnitFilePresetRule rule = {};
33,237✔
3380
                        const char *parameter;
33,237✔
3381

3382
                        r = read_stripped_line(f, LONG_LINE_MAX, &line);
33,237✔
3383
                        if (r < 0)
33,237✔
3384
                                return r;
3385
                        if (r == 0)
33,237✔
3386
                                break;
3387

3388
                        n++;
32,205✔
3389

3390
                        if (isempty(line))
32,205✔
3391
                                continue;
6,450✔
3392
                        if (strchr(COMMENTS, line[0]))
25,755✔
3393
                                continue;
8,982✔
3394

3395
                        if ((parameter = first_word(line, "enable"))) {
16,773✔
3396
                                char *unit_name;
7,440✔
3397
                                char **instances = NULL;
7,440✔
3398

3399
                                /* Unit_name will remain the same as parameter when no instances are specified */
3400
                                r = split_pattern_into_name_and_instances(parameter, &unit_name, &instances);
7,440✔
3401
                                if (r < 0) {
7,440✔
UNCOV
3402
                                        log_syntax(NULL, LOG_WARNING, *p, n, r, "Couldn't parse line '%s'. Ignoring.", line);
×
UNCOV
3403
                                        continue;
×
3404
                                }
3405

3406
                                rule = (UnitFilePresetRule) {
7,440✔
3407
                                        .pattern = unit_name,
3408
                                        .action = PRESET_ENABLE,
3409
                                        .instances = instances,
3410
                                };
3411

3412
                        } else if ((parameter = first_word(line, "disable"))) {
9,333✔
3413
                                char *pattern;
9,325✔
3414

3415
                                pattern = strdup(parameter);
9,325✔
3416
                                if (!pattern)
9,325✔
3417
                                        return -ENOMEM;
3418

3419
                                rule = (UnitFilePresetRule) {
9,325✔
3420
                                        .pattern = pattern,
3421
                                        .action = PRESET_DISABLE,
3422
                                };
3423

3424
                        } else if ((parameter = first_word(line, "ignore"))) {
8✔
3425
                                char *pattern;
4✔
3426

3427
                                pattern = strdup(parameter);
4✔
3428
                                if (!pattern)
4✔
3429
                                        return -ENOMEM;
3430

3431
                                rule = (UnitFilePresetRule) {
4✔
3432
                                        .pattern = pattern,
3433
                                        .action = PRESET_IGNORE,
3434
                                };
3435
                        }
3436

3437
                        if (rule.action != 0) {
16,773✔
3438
                                if (!GREEDY_REALLOC(ps.rules, ps.n_rules + 1))
16,769✔
3439
                                        return -ENOMEM;
3440

3441
                                ps.rules[ps.n_rules++] = TAKE_STRUCT(rule);
16,769✔
3442
                                continue;
16,769✔
3443
                        }
3444

3445
                        log_syntax(NULL, LOG_WARNING, *p, n, 0, "Couldn't parse line '%s'. Ignoring.", line);
4✔
3446
                }
3447
        }
3448

3449
        ps.initialized = true;
290✔
3450
        *presets = TAKE_STRUCT(ps);
290✔
3451

3452
        return 0;
290✔
3453
}
3454

3455
static int pattern_match_multiple_instances(
591,901✔
3456
                        const UnitFilePresetRule rule,
3457
                        const char *unit_name,
3458
                        char ***ret) {
3459

3460
        _cleanup_free_ char *templated_name = NULL;
591,901✔
3461
        int r;
591,901✔
3462

3463
        assert(unit_name);
591,901✔
3464

3465
        /* If no ret is needed or the rule itself does not have instances
3466
         * initialized, we return not matching */
3467
        if (!ret || !rule.instances)
591,901✔
3468
                return 0;
3469

3470
        r = unit_name_template(unit_name, &templated_name);
4✔
3471
        if (r < 0)
4✔
3472
                return r;
3473
        if (!streq(rule.pattern, templated_name))
2✔
3474
                return 0;
3475

3476
        /* Compose a list of specified instances when unit name is a template  */
3477
        if (unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
2✔
3478
                _cleanup_strv_free_ char **out_strv = NULL;
1✔
3479

3480
                STRV_FOREACH(iter, rule.instances) {
4✔
3481
                        _cleanup_free_ char *name = NULL;
3✔
3482

3483
                        r = unit_name_replace_instance(unit_name, *iter, &name);
3✔
3484
                        if (r < 0)
3✔
3485
                                return r;
3486

3487
                        r = strv_consume(&out_strv, TAKE_PTR(name));
3✔
3488
                        if (r < 0)
3✔
3489
                                return r;
3490
                }
3491

3492
                *ret = TAKE_PTR(out_strv);
1✔
3493
                return 1;
1✔
3494
        } else {
3495
                /* We now know the input unit name is an instance name */
3496
                _cleanup_free_ char *instance_name = NULL;
1✔
3497

3498
                r = unit_name_to_instance(unit_name, &instance_name);
1✔
3499
                if (r < 0)
1✔
3500
                        return r;
3501

3502
                if (strv_find(rule.instances, instance_name))
1✔
3503
                        return 1;
3504
        }
UNCOV
3505
        return 0;
×
3506
}
3507

3508
static int query_presets(const char *name, const UnitFilePresets *presets, char ***instance_name_list) {
9,394✔
3509
        PresetAction action = PRESET_UNKNOWN;
9,394✔
3510

3511
        assert(name);
9,394✔
3512
        assert(presets);
9,394✔
3513

3514
        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
9,394✔
3515
                return -EINVAL;
3516

3517
        FOREACH_ARRAY(i, presets->rules, presets->n_rules)
591,936✔
3518
                if (pattern_match_multiple_instances(*i, name, instance_name_list) > 0 ||
1,183,800✔
3519
                    fnmatch(i->pattern, name, FNM_NOESCAPE) == 0) {
591,899✔
3520
                        action = i->action;
9,359✔
3521
                        break;
9,359✔
3522
                }
3523

3524
        switch (action) {
9,394✔
3525

3526
        case PRESET_UNKNOWN:
3527
                log_debug("Preset files don't specify rule for %s. Enabling.", name);
35✔
3528
                return PRESET_ENABLE;
3529

3530
        case PRESET_ENABLE:
490✔
3531
                if (instance_name_list && *instance_name_list)
490✔
3532
                        STRV_FOREACH(s, *instance_name_list)
4✔
3533
                                log_debug("Preset files say enable %s.", *s);
3✔
3534
                else
3535
                        log_debug("Preset files say enable %s.", name);
489✔
3536
                return PRESET_ENABLE;
3537

3538
        case PRESET_DISABLE:
3539
                log_debug("Preset files say disable %s.", name);
8,867✔
3540
                return PRESET_DISABLE;
3541

3542
        case PRESET_IGNORE:
3543
                log_debug("Preset files say ignore %s.", name);
2✔
3544
                return PRESET_IGNORE;
3545

UNCOV
3546
        default:
×
UNCOV
3547
                assert_not_reached();
×
3548
        }
3549
}
3550

3551
PresetAction unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached) {
570✔
3552
        _cleanup_(unit_file_presets_done) UnitFilePresets tmp = {};
570✔
3553
        int r;
570✔
3554

3555
        if (!cached)
570✔
3556
                cached = &tmp;
256✔
3557
        if (!cached->initialized) {
570✔
3558
                r = read_presets(scope, root_dir, cached);
261✔
3559
                if (r < 0)
261✔
3560
                        return r;
3561
        }
3562

3563
        return query_presets(name, cached, NULL);
570✔
3564
}
3565

3566
static int execute_preset(
28✔
3567
                UnitFileFlags file_flags,
3568
                InstallContext *plus,
3569
                InstallContext *minus,
3570
                const LookupPaths *lp,
3571
                const char *config_path,
3572
                char * const *files,
3573
                UnitFilePresetMode mode,
3574
                InstallChange **changes,
3575
                size_t *n_changes) {
3576

3577
        int r;
28✔
3578

3579
        assert(plus);
28✔
3580
        assert(minus);
28✔
3581
        assert(lp);
28✔
3582
        assert(config_path);
28✔
3583

3584
        if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) {
28✔
UNCOV
3585
                _cleanup_set_free_ Set *remove_symlinks_to = NULL;
×
3586

3587
                r = install_context_mark_for_removal(minus, lp, &remove_symlinks_to, config_path, changes, n_changes);
10✔
3588
                if (r < 0)
10✔
UNCOV
3589
                        return r;
×
3590

3591
                r = remove_marked_symlinks(remove_symlinks_to, config_path, lp, false, changes, n_changes);
10✔
3592
        } else
3593
                r = 0;
3594

3595
        if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) {
10✔
3596
                int q;
27✔
3597

3598
                /* Returns number of symlinks that where supposed to be installed. */
3599
                q = install_context_apply(plus, lp,
54✔
3600
                                          file_flags | UNIT_FILE_IGNORE_AUXILIARY_FAILURE,
27✔
3601
                                          config_path,
3602
                                          SEARCH_LOAD, changes, n_changes);
3603
                if (r >= 0) {
27✔
3604
                        if (q < 0)
27✔
3605
                                r = q;
3606
                        else
3607
                                r += q;
27✔
3608
                }
3609
        }
3610

3611
        return r;
3612
}
3613

3614
static int preset_prepare_one(
9,369✔
3615
                RuntimeScope scope,
3616
                InstallContext *plus,
3617
                InstallContext *minus,
3618
                LookupPaths *lp,
3619
                const char *name,
3620
                const UnitFilePresets *presets,
3621
                InstallChange **changes,
3622
                size_t *n_changes) {
3623

3624
        _cleanup_(install_context_done) InstallContext tmp = { .scope = scope };
9,369✔
UNCOV
3625
        _cleanup_strv_free_ char **instance_name_list = NULL;
×
3626
        InstallInfo *info;
9,369✔
3627
        int r;
9,369✔
3628

3629
        if (install_info_find(plus, name) || install_info_find(minus, name))
9,369✔
3630
                return 0;
85✔
3631

3632
        r = install_info_discover(&tmp, lp, name, SEARCH_FOLLOW_CONFIG_SYMLINKS,
9,284✔
3633
                                  &info, changes, n_changes);
3634
        if (r < 0)
9,284✔
3635
                return r;
3636

3637
        if (!streq(name, info->name)) {
9,283✔
3638
                log_debug("Skipping %s because it is an alias for %s.", name, info->name);
459✔
3639
                return 0;
459✔
3640
        }
3641

3642
        r = query_presets(name, presets, &instance_name_list);
8,824✔
3643
        if (r < 0)
8,824✔
3644
                return r;
3645

3646
        if (r == PRESET_ENABLE) {
8,824✔
3647
                if (instance_name_list)
413✔
3648
                        STRV_FOREACH(s, instance_name_list) {
4✔
3649
                                r = install_info_discover_and_check(plus, lp, *s, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
3✔
3650
                                                                    &info, changes, n_changes);
3651
                                if (r < 0)
3✔
3652
                                        return r;
3653
                        }
3654
                else {
3655
                        r = install_info_discover_and_check(plus, lp, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
9,369✔
3656
                                                            &info, changes, n_changes);
3657
                        if (r < 0)
3658
                                return r;
3659
                }
3660

3661
        } else if (r == PRESET_DISABLE)
8,411✔
3662
                r = install_info_discover(minus, lp, name, SEARCH_FOLLOW_CONFIG_SYMLINKS,
8,409✔
3663
                                          &info, changes, n_changes);
3664

3665
        return r;
3666
}
3667

3668
int unit_file_preset(
9✔
3669
                RuntimeScope scope,
3670
                UnitFileFlags file_flags,
3671
                const char *root_dir,
3672
                char * const *names,
3673
                UnitFilePresetMode mode,
3674
                InstallChange **changes,
3675
                size_t *n_changes) {
3676

3677
        _cleanup_(install_context_done) InstallContext plus = {}, minus = {};
9✔
3678
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
9✔
3679
        _cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
9✔
3680
        const char *config_path;
9✔
3681
        int r;
9✔
3682

3683
        assert(scope >= 0);
9✔
3684
        assert(scope < _RUNTIME_SCOPE_MAX);
9✔
3685
        assert(mode < _UNIT_FILE_PRESET_MODE_MAX);
9✔
3686

3687
        r = lookup_paths_init(&lp, scope, 0, root_dir);
9✔
3688
        if (r < 0)
9✔
3689
                return r;
3690

3691
        config_path = (file_flags & UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
9✔
3692
        if (!config_path)
9✔
3693
                return -ENXIO;
3694

3695
        r = read_presets(scope, root_dir, &presets);
9✔
3696
        if (r < 0)
9✔
3697
                return r;
3698

3699
        STRV_FOREACH(name, names) {
18✔
3700
                r = preset_prepare_one(scope, &plus, &minus, &lp, *name, &presets, changes, n_changes);
9✔
3701
                if (r < 0 && !ERRNO_IS_NEG_UNIT_ISSUE(r))
9✔
3702
                        return r;
3703
        }
3704

3705
        return execute_preset(file_flags, &plus, &minus, &lp, config_path, names, mode, changes, n_changes);
9✔
3706
}
3707

3708
int unit_file_preset_all(
20✔
3709
                RuntimeScope scope,
3710
                UnitFileFlags file_flags,
3711
                const char *root_dir,
3712
                UnitFilePresetMode mode,
3713
                InstallChange **changes,
3714
                size_t *n_changes) {
3715

3716
        _cleanup_(install_context_done) InstallContext plus = {}, minus = {};
20✔
3717
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
20✔
UNCOV
3718
        _cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
×
3719
        const char *config_path = NULL;
20✔
3720
        int r;
20✔
3721

3722
        assert(scope >= 0);
20✔
3723
        assert(scope < _RUNTIME_SCOPE_MAX);
20✔
3724
        assert(mode < _UNIT_FILE_PRESET_MODE_MAX);
20✔
3725

3726
        r = lookup_paths_init(&lp, scope, 0, root_dir);
20✔
3727
        if (r < 0)
20✔
3728
                return r;
3729

3730
        config_path = (file_flags & UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
20✔
3731
        if (!config_path)
20✔
3732
                return -ENXIO;
3733

3734
        r = read_presets(scope, root_dir, &presets);
20✔
3735
        if (r < 0)
20✔
3736
                return r;
3737

3738
        STRV_FOREACH(i, lp.search_path) {
282✔
3739
                _cleanup_closedir_ DIR *d = NULL;
282✔
3740

3741
                d = opendir(*i);
263✔
3742
                if (!d) {
263✔
3743
                        if (errno == ENOENT)
145✔
3744
                                continue;
145✔
3745

UNCOV
3746
                        return log_debug_errno(errno, "Failed to opendir %s: %m", *i);
×
3747
                }
3748

3749
                FOREACH_DIRENT(de, d,
15,134✔
3750
                               return log_debug_errno(errno, "Failed to read directory %s: %m", *i)) {
3751

3752
                        if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
14,782✔
3753
                                continue;
5,422✔
3754

3755
                        if (!IN_SET(de->d_type, DT_LNK, DT_REG))
9,360✔
UNCOV
3756
                                continue;
×
3757

3758
                        r = preset_prepare_one(scope, &plus, &minus, &lp, de->d_name, &presets, changes, n_changes);
9,360✔
3759
                        if (r < 0 && !ERRNO_IS_NEG_UNIT_ISSUE(r))
9,360✔
3760
                                return r;
3761
                }
3762
        }
3763

3764
        return execute_preset(file_flags, &plus, &minus, &lp, config_path, NULL, mode, changes, n_changes);
19✔
3765
}
3766

3767
static UnitFileList* unit_file_list_free(UnitFileList *f) {
932✔
3768
        if (!f)
932✔
3769
                return NULL;
3770

3771
        free(f->path);
932✔
3772
        return mfree(f);
932✔
3773
}
3774

3775
DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free);
932✔
3776

3777
DEFINE_PRIVATE_HASH_OPS_FULL(unit_file_list_hash_ops_free_free,
1,864✔
3778
                             char, string_hash_func, string_compare_func, free,
3779
                             UnitFileList, unit_file_list_free);
3780

3781
int unit_file_get_list(
4✔
3782
                RuntimeScope scope,
3783
                const char *root_dir,
3784
                char * const *states,
3785
                char * const *patterns,
3786
                Hashmap **ret) {
3787

3788
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
4✔
UNCOV
3789
        _cleanup_hashmap_free_ Hashmap *h = NULL;
×
3790
        int r;
4✔
3791

3792
        assert(scope >= 0);
4✔
3793
        assert(scope < _RUNTIME_SCOPE_MAX);
4✔
3794
        assert(ret);
4✔
3795

3796
        r = lookup_paths_init(&lp, scope, 0, root_dir);
4✔
3797
        if (r < 0)
4✔
3798
                return r;
3799

3800
        STRV_FOREACH(dirname, lp.search_path) {
52✔
3801
                _cleanup_closedir_ DIR *d = NULL;
52✔
3802

3803
                d = opendir(*dirname);
48✔
3804
                if (!d) {
48✔
3805
                        if (errno == ENOENT)
27✔
3806
                                continue;
27✔
UNCOV
3807
                        if (IN_SET(errno, ENOTDIR, EACCES)) {
×
UNCOV
3808
                                log_debug_errno(errno, "Failed to open \"%s\": %m", *dirname);
×
UNCOV
3809
                                continue;
×
3810
                        }
3811

UNCOV
3812
                        return -errno;
×
3813
                }
3814

3815
                FOREACH_DIRENT(de, d, return -errno) {
1,586✔
3816
                        if (!IN_SET(de->d_type, DT_LNK, DT_REG))
1,523✔
3817
                                continue;
591✔
3818

3819
                        if (hashmap_contains(h, de->d_name))
1,376✔
3820
                                continue;
10✔
3821

3822
                        if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
1,366✔
UNCOV
3823
                                continue;
×
3824

3825
                        if (!strv_fnmatch_or_empty(patterns, de->d_name, FNM_NOESCAPE))
1,366✔
3826
                                continue;
434✔
3827

3828
                        UnitFileState state;
932✔
3829

3830
                        r = unit_file_lookup_state(scope, &lp, de->d_name, &state);
932✔
3831
                        if (r < 0)
932✔
UNCOV
3832
                                state = UNIT_FILE_BAD;
×
3833

3834
                        if (!strv_isempty(states) &&
932✔
UNCOV
3835
                            !strv_contains(states, unit_file_state_to_string(state)))
×
UNCOV
3836
                                continue;
×
3837

3838
                        _cleanup_(unit_file_list_freep) UnitFileList *f = new(UnitFileList, 1);
932✔
3839
                        if (!f)
932✔
3840
                                return -ENOMEM;
3841

3842
                        *f = (UnitFileList) {
1,864✔
3843
                                .path = path_make_absolute(de->d_name, *dirname),
932✔
3844
                                .state = state,
3845
                        };
3846
                        if (!f->path)
932✔
3847
                                return -ENOMEM;
3848

3849
                        _cleanup_free_ char *unit_name = strdup(de->d_name);
932✔
3850
                        if (!unit_name)
932✔
3851
                                return -ENOMEM;
3852

3853
                        r = hashmap_ensure_put(&h, &unit_file_list_hash_ops_free_free, unit_name, f);
932✔
3854
                        if (r < 0)
932✔
3855
                                return r;
3856
                        assert(r > 0);
932✔
3857

3858
                        TAKE_PTR(unit_name);
932✔
3859
                        TAKE_PTR(f);
932✔
3860
                }
3861
        }
3862

3863
        *ret = TAKE_PTR(h);
4✔
3864
        return 0;
4✔
3865
}
3866

3867
static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = {
3868
        [UNIT_FILE_ENABLED]         = "enabled",
3869
        [UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtime",
3870
        [UNIT_FILE_LINKED]          = "linked",
3871
        [UNIT_FILE_LINKED_RUNTIME]  = "linked-runtime",
3872
        [UNIT_FILE_ALIAS]           = "alias",
3873
        [UNIT_FILE_MASKED]          = "masked",
3874
        [UNIT_FILE_MASKED_RUNTIME]  = "masked-runtime",
3875
        [UNIT_FILE_STATIC]          = "static",
3876
        [UNIT_FILE_DISABLED]        = "disabled",
3877
        [UNIT_FILE_INDIRECT]        = "indirect",
3878
        [UNIT_FILE_GENERATED]       = "generated",
3879
        [UNIT_FILE_TRANSIENT]       = "transient",
3880
        [UNIT_FILE_BAD]             = "bad",
3881
};
3882

3883
DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState);
5,742✔
3884

3885
static const char* const install_change_type_table[_INSTALL_CHANGE_TYPE_MAX] = {
3886
        [INSTALL_CHANGE_SYMLINK]                 = "symlink",
3887
        [INSTALL_CHANGE_UNLINK]                  = "unlink",
3888
        [INSTALL_CHANGE_IS_MASKED]               = "masked",
3889
        [INSTALL_CHANGE_IS_MASKED_GENERATOR]     = "masked by generator",
3890
        [INSTALL_CHANGE_IS_DANGLING]             = "dangling",
3891
        [INSTALL_CHANGE_DESTINATION_NOT_PRESENT] = "destination not present",
3892
        [INSTALL_CHANGE_AUXILIARY_FAILED]        = "auxiliary unit failed",
3893
};
3894

3895
DEFINE_STRING_TABLE_LOOKUP(install_change_type, InstallChangeType);
120✔
3896

3897
static const char* const unit_file_preset_mode_table[_UNIT_FILE_PRESET_MODE_MAX] = {
3898
        [UNIT_FILE_PRESET_FULL]         = "full",
3899
        [UNIT_FILE_PRESET_ENABLE_ONLY]  = "enable-only",
3900
        [UNIT_FILE_PRESET_DISABLE_ONLY] = "disable-only",
3901
};
3902

3903
DEFINE_STRING_TABLE_LOOKUP(unit_file_preset_mode, UnitFilePresetMode);
58✔
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