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

systemd / systemd / 21611229246

02 Feb 2026 11:18PM UTC coverage: 72.804% (+0.05%) from 72.759%
21611229246

push

github

yuwata
import-generator: fix sysext/confext ordering in initrd

In the initrd, order the generated `systemd-import@.service` units before the
initrd-specific sysext/confext units.

0 of 4 new or added lines in 1 file covered. (0.0%)

195 existing lines in 33 files now uncovered.

311471 of 427821 relevant lines covered (72.8%)

1151368.5 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 enum SearchFlags {
41
        SEARCH_LOAD                   = 1 << 0,
42
        SEARCH_FOLLOW_CONFIG_SYMLINKS = 1 << 1,
43
        SEARCH_DROPIN                 = 1 << 2,
44
} SearchFlags;
45

46
typedef struct {
47
        RuntimeScope scope;
48
        OrderedHashmap *will_process;
49
        OrderedHashmap *have_processed;
50
} InstallContext;
51

52
struct UnitFilePresetRule {
53
        char *pattern;
54
        PresetAction action;
55
        char **instances;
56
};
57

58
/* NB! strings use past tense. */
59
static const char *const preset_action_past_tense_table[_PRESET_ACTION_MAX] = {
60
        [PRESET_UNKNOWN] = "unknown",
61
        [PRESET_ENABLE]  = "enabled",
62
        [PRESET_DISABLE] = "disabled",
63
        [PRESET_IGNORE]  = "ignored",
64
};
65

66
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(preset_action_past_tense, PresetAction);
3,378✔
67

68
static bool install_info_has_rules(const InstallInfo *i) {
2,280✔
69
        assert(i);
2,280✔
70

71
        return !strv_isempty(i->aliases) ||
2,280✔
72
               !strv_isempty(i->wanted_by) ||
2,265✔
73
               !strv_isempty(i->required_by) ||
1,950✔
74
               !strv_isempty(i->upheld_by);
1,948✔
75
}
76

77
static bool install_info_has_also(const InstallInfo *i) {
1,948✔
78
        assert(i);
1,948✔
79

80
        return !strv_isempty(i->also);
1,948✔
81
}
82

83
static void unit_file_preset_rule_done(UnitFilePresetRule *rule) {
50,006✔
84
        assert(rule);
50,006✔
85

86
        free(rule->pattern);
50,006✔
87
        strv_free(rule->instances);
50,006✔
88
}
50,006✔
89

90
void unit_file_presets_done(UnitFilePresets *p) {
919✔
91
        if (!p)
919✔
92
                return;
93

94
        FOREACH_ARRAY(rule, p->rules, p->n_rules)
17,688✔
95
                unit_file_preset_rule_done(rule);
16,769✔
96

97
        free(p->rules);
919✔
98
        p->n_rules = 0;
919✔
99
}
100

101
static const char *const install_mode_table[_INSTALL_MODE_MAX] = {
102
        [INSTALL_MODE_REGULAR] = "regular",
103
        [INSTALL_MODE_LINKED]  = "linked",
104
        [INSTALL_MODE_ALIAS]   = "alias",
105
        [INSTALL_MODE_MASKED]  = "masked",
106
};
107

108
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(install_mode, InstallMode);
2✔
109

110
static int in_search_path(const LookupPaths *lp, const char *path) {
635✔
111
        _cleanup_free_ char *parent = NULL;
635✔
112
        int r;
635✔
113

114
        /* Check if 'path' is in lp->search_path. */
115

116
        assert(lp);
635✔
117
        assert(path);
635✔
118

119
        r = path_extract_directory(path, &parent);
635✔
120
        if (r < 0)
635✔
121
                return r;
122

123
        return path_strv_contains(lp->search_path, parent);
635✔
124
}
125

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

129
        assert(lp);
1✔
130
        assert(path);
1✔
131

132
        return path_startswith_strv(path, lp->search_path);
1✔
133
}
134

135
static const char* skip_root(const char *root_dir, const char *path) {
1,306✔
136
        assert(path);
1,306✔
137

138
        if (!root_dir)
1,306✔
139
                return path;
140

141
        const char *e = path_startswith(path, root_dir);
100✔
142
        if (!e)
100✔
143
                return NULL;
144

145
        /* Make sure the returned path starts with a slash */
146
        if (e[0] != '/') {
98✔
147
                if (e == path || e[-1] != '/')
98✔
148
                        return NULL;
149

150
                e--;
98✔
151
        }
152

153
        return e;
154
}
155

156
static int path_is_generator(const LookupPaths *lp, const char *path) {
2,881✔
157
        _cleanup_free_ char *parent = NULL;
2,881✔
158
        int r;
2,881✔
159

160
        assert(lp);
2,881✔
161
        assert(path);
2,881✔
162

163
        r = path_extract_directory(path, &parent);
2,881✔
164
        if (r < 0)
2,881✔
165
                return r;
166

167
        return PATH_IN_SET(parent,
2,881✔
168
                           lp->generator,
169
                           lp->generator_early,
170
                           lp->generator_late);
171
}
172

173
static int path_is_transient(const LookupPaths *lp, const char *path) {
2,871✔
174
        _cleanup_free_ char *parent = NULL;
2,871✔
175
        int r;
2,871✔
176

177
        assert(lp);
2,871✔
178
        assert(path);
2,871✔
179

180
        r = path_extract_directory(path, &parent);
2,871✔
181
        if (r < 0)
2,871✔
182
                return r;
183

184
        return path_equal(parent, lp->transient);
2,871✔
185
}
186

187
static int path_is_control(const LookupPaths *lp, const char *path) {
×
188
        _cleanup_free_ char *parent = NULL;
×
189
        int r;
×
190

191
        assert(lp);
×
192
        assert(path);
×
193

194
        r = path_extract_directory(path, &parent);
×
195
        if (r < 0)
×
196
                return r;
197

198
        return PATH_IN_SET(parent,
×
199
                           lp->persistent_control,
200
                           lp->runtime_control);
201
}
202

203
static int path_is_config(const LookupPaths *lp, const char *path, bool check_parent) {
13✔
204
        _cleanup_free_ char *parent = NULL;
13✔
205
        int r;
13✔
206

207
        assert(lp);
13✔
208
        assert(path);
13✔
209

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

213
        if (check_parent) {
13✔
214
                r = path_extract_directory(path, &parent);
13✔
215
                if (r < 0)
13✔
216
                        return r;
217

218
                path = parent;
13✔
219
        }
220

221
        return PATH_IN_SET(path,
13✔
222
                           lp->persistent_config,
223
                           lp->runtime_config);
224
}
225

226
static int path_is_runtime(const LookupPaths *lp, const char *path, bool check_parent) {
614✔
227
        _cleanup_free_ char *parent = NULL;
614✔
228
        const char *rpath;
614✔
229
        int r;
614✔
230

231
        assert(lp);
614✔
232
        assert(path);
614✔
233

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

237
        rpath = skip_root(lp->root_dir, path);
614✔
238
        if (rpath && path_startswith(rpath, "/run"))
1,228✔
239
                return true;
240

241
        if (check_parent) {
595✔
242
                r = path_extract_directory(path, &parent);
12✔
243
                if (r < 0)
12✔
244
                        return r;
245

246
                path = parent;
12✔
247
        }
248

249
        return PATH_IN_SET(path,
595✔
250
                           lp->runtime_config,
251
                           lp->generator,
252
                           lp->generator_early,
253
                           lp->generator_late,
254
                           lp->transient,
255
                           lp->runtime_control);
256
}
257

258
static int path_is_vendor_or_generator(const LookupPaths *lp, const char *path) {
5✔
259
        const char *rpath;
5✔
260

261
        assert(lp);
5✔
262
        assert(path);
5✔
263

264
        rpath = skip_root(lp->root_dir, path);
5✔
265
        if (!rpath)
5✔
266
                return 0;
267

268
        if (path_startswith(rpath, "/usr"))
5✔
269
                return true;
270

271
        if (path_is_generator(lp, rpath))
1✔
272
                return true;
273

274
        return path_equal(rpath, SYSTEM_DATA_UNIT_DIR);
1✔
275
}
276

277
static const char* config_path_from_flags(const LookupPaths *lp, UnitFileFlags flags) {
40✔
278
        assert(lp);
40✔
279

280
        if (FLAGS_SET(flags, UNIT_FILE_PORTABLE))
40✔
281
                return FLAGS_SET(flags, UNIT_FILE_RUNTIME) ? lp->runtime_attached : lp->persistent_attached;
×
282
        else
283
                return FLAGS_SET(flags, UNIT_FILE_RUNTIME) ? lp->runtime_config : lp->persistent_config;
40✔
284
}
285

286
InstallChangeType install_changes_add(
1,282✔
287
                InstallChange **changes,
288
                size_t *n_changes,
289
                InstallChangeType type, /* INSTALL_CHANGE_SYMLINK, _UNLINK, _IS_MASKED, _IS_DANGLING, … if positive or errno if negative */
290
                const char *path,
291
                const char *source) {
292

293
        _cleanup_free_ char *p = NULL, *s = NULL;
1,282✔
294
        int r;
1,282✔
295

296
        assert(!changes == !n_changes);
1,282✔
297
        assert(INSTALL_CHANGE_TYPE_VALID(type));
1,282✔
298

299
        /* Message formatting requires <path> to be set. */
300
        assert(path);
1,282✔
301

302
        /* Register a change or error. Note that the return value may be the error
303
         * that was passed in, or -ENOMEM generated internally. */
304

305
        if (!changes)
1,282✔
306
                return type;
307

308
        if (!GREEDY_REALLOC(*changes, *n_changes + 1))
190✔
309
                return -ENOMEM;
310

311
        r = path_simplify_alloc(path, &p);
190✔
312
        if (r < 0)
190✔
313
                return r;
314

315
        r = path_simplify_alloc(source, &s);
190✔
316
        if (r < 0)
190✔
317
                return r;
318

319
        (*changes)[(*n_changes)++] = (InstallChange) {
190✔
320
                .type = type,
321
                .path = TAKE_PTR(p),
190✔
322
                .source = TAKE_PTR(s),
190✔
323
        };
324

325
        return type;
190✔
326
}
327

328
void install_changes_free(InstallChange *changes, size_t n_changes) {
129✔
329
        assert(changes || n_changes == 0);
129✔
330

331
        FOREACH_ARRAY(i, changes, n_changes) {
319✔
332
                free(i->path);
190✔
333
                free(i->source);
190✔
334
        }
335

336
        free(changes);
129✔
337
}
129✔
338

339
static void install_change_dump_success(const InstallChange *change) {
93✔
340
        assert(change);
93✔
341
        assert(change->path);
93✔
342

343
        switch (change->type) {
93✔
344

345
        case INSTALL_CHANGE_SYMLINK:
346
                return log_info("Created symlink '%s' %s '%s'.",
38✔
347
                                change->path, glyph(GLYPH_ARROW_RIGHT), change->source);
348

349
        case INSTALL_CHANGE_UNLINK:
350
                return log_info("Removed '%s'.", change->path);
37✔
351

352
        case INSTALL_CHANGE_IS_MASKED:
353
                return log_info("Unit %s is masked, ignoring.", change->path);
18✔
354

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

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

361
        case INSTALL_CHANGE_DESTINATION_NOT_PRESENT:
362
                return log_warning("Unit %s is added as a dependency to a non-existent unit %s.",
×
363
                                   change->source, change->path);
364

365
        case INSTALL_CHANGE_AUXILIARY_FAILED:
366
                return log_warning("Failed to enable auxiliary unit %s, ignoring.", change->path);
×
367

368
        default:
×
369
                assert_not_reached();
×
370
        }
371
}
372

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

391
int install_change_dump_error(const InstallChange *change, char **ret_errmsg, const char **ret_bus_error) {
18✔
392
        char *m;
18✔
393
        const char *bus_error;
18✔
394

395
        /* Returns 0:   known error and ret_errmsg formatted
396
         *         < 0: non-recognizable error */
397

398
        assert(change);
18✔
399
        assert(change->path);
18✔
400
        assert(change->type < 0);
18✔
401
        assert(ret_errmsg);
18✔
402

403
        switch (change->type) {
18✔
404

405
        case -EEXIST:
×
406
                m = strjoin("File '", change->path, "' already exists",
×
407
                            change->source ? " and is a symlink to " : NULL, change->source);
408
                bus_error = BUS_ERROR_UNIT_EXISTS;
×
409
                break;
×
410

411
        case -ERFKILL:
17✔
412
                m = strjoin("Unit ", change->path, " is masked");
17✔
413
                bus_error = BUS_ERROR_UNIT_MASKED;
17✔
414
                break;
17✔
415

416
        case -EADDRNOTAVAIL:
×
417
                m = strjoin("Unit ", change->path, " is transient or generated");
×
418
                bus_error = BUS_ERROR_UNIT_GENERATED;
×
419
                break;
×
420

421
        case -ETXTBSY:
×
422
                m = strjoin("File '", change->path, "' is under the systemd unit hierarchy already");
×
423
                bus_error = BUS_ERROR_UNIT_BAD_PATH;
×
424
                break;
×
425

426
        case -EBADSLT:
×
427
                m = strjoin("Invalid specifier in unit ", change->path);
×
428
                bus_error = BUS_ERROR_BAD_UNIT_SETTING;
×
429
                break;
×
430

431
        case -EIDRM:
×
432
                m = strjoin("Refusing to operate on template unit ", change->source,
×
433
                            " when destination unit ", change->path, " is a non-template unit");
434
                bus_error = BUS_ERROR_BAD_UNIT_SETTING;
×
435
                break;
×
436

437
        case -EUCLEAN:
×
438
                m = strjoin("Invalid unit name ", change->path);
×
439
                bus_error = BUS_ERROR_BAD_UNIT_SETTING;
×
440
                break;
×
441

442
        case -ELOOP:
×
443
                m = strjoin("Refusing to operate on linked unit file ", change->path);
×
444
                bus_error = BUS_ERROR_UNIT_LINKED;
×
445
                break;
×
446

447
        case -EXDEV:
×
448
                if (change->source)
×
449
                        m = strjoin("Cannot alias ", change->source, " as ", change->path);
×
450
                else
451
                        m = strjoin("Invalid unit reference ", change->path);
×
452
                bus_error = BUS_ERROR_BAD_UNIT_SETTING;
453
                break;
454

455
        case -ENOENT:
×
456
                m = strjoin("Unit ", change->path, " does not exist");
×
457
                bus_error = BUS_ERROR_NO_SUCH_UNIT;
×
458
                break;
×
459

460
        case -ENOLINK:
×
461
                m = strjoin("Unit ", change->path, " is an unresolvable alias");
×
462
                bus_error = BUS_ERROR_NO_SUCH_UNIT;
×
463
                break;
×
464

465
        case -EUNATCH:
×
466
                m = strjoin("Cannot resolve specifiers in unit ", change->path);
×
467
                bus_error = BUS_ERROR_BAD_UNIT_SETTING;
×
468
                break;
×
469

470
        default:
471
                return change->type;
472
        }
473
        if (!m)
17✔
474
                return -ENOMEM;
475

476
        *ret_errmsg = m;
17✔
477
        if (ret_bus_error)
17✔
478
                *ret_bus_error = bus_error;
×
479

480
        return 0;
481
}
482

483
int install_changes_dump(
105✔
484
                int error,
485
                const char *verb,
486
                const InstallChange *changes,
487
                size_t n_changes,
488
                bool quiet) {
489

490
        bool err_logged = false;
105✔
491
        int r;
105✔
492

493
        /* If verb is not specified, errors are not allowed! */
494
        assert(verb || error >= 0);
105✔
495
        assert(changes || n_changes == 0);
105✔
496

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

499
        FOREACH_ARRAY(i, changes, n_changes)
216✔
500
                if (i->type >= 0) {
111✔
501
                        if (!quiet)
93✔
502
                                install_change_dump_success(i);
93✔
503
                } else {
504
                        _cleanup_free_ char *err_message = NULL;
18✔
505

506
                        assert(verb);
18✔
507

508
                        r = install_change_dump_error(i, &err_message, /* ret_bus_error= */ NULL);
18✔
509
                        if (r == -ENOMEM)
18✔
510
                                return log_oom();
×
511
                        if (r < 0)
18✔
512
                                RET_GATHER(error,
1✔
513
                                           log_error_errno(r, "Failed to %s unit %s: %m", verb, i->path));
514
                        else
515
                                RET_GATHER(error,
17✔
516
                                           log_error_errno(i->type, "Failed to %s unit: %s", verb, err_message));
517

518
                        err_logged = true;
18✔
519
                }
520

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

524
        return error;
525
}
526

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

538
        assert(lp);
599✔
539
        assert(src);
599✔
540
        assert(target_a);
599✔
541
        assert(target_b);
599✔
542

543
        /* This will give incorrect results if the paths are relative and go outside
544
         * of the chroot. False negatives are possible. */
545

546
        const char *root = lp->root_dir ?: "/";
599✔
547
        _cleanup_free_ char *dirname = NULL;
599✔
548
        int r;
599✔
549

550
        if (!path_is_absolute(target_a) || !path_is_absolute(target_b)) {
1,198✔
551
                r = path_extract_directory(src, &dirname);
×
552
                if (r < 0)
×
553
                        return r;
554
        }
555

556
        _cleanup_free_ char *a = path_join(path_is_absolute(target_a) ? root : dirname, target_a);
599✔
557
        _cleanup_free_ char *b = path_join(path_is_absolute(target_b) ? root : dirname, target_b);
1,198✔
558
        if (!a || !b)
599✔
559
                return log_oom();
×
560

561
        r = path_equal_or_inode_same(a, b, 0);
599✔
562
        if (r != 0)
599✔
563
                return r;
564

565
        _cleanup_free_ char *a_name = NULL, *b_name = NULL;
2✔
566
        r = path_extract_filename(a, &a_name);
2✔
567
        if (r < 0)
2✔
568
                return r;
569
        r = path_extract_filename(b, &b_name);
2✔
570
        if (r < 0)
2✔
571
                return r;
572

573
        return streq(a_name, b_name) &&
×
574
               path_startswith_strv(a, lp->search_path) &&
2✔
575
               path_startswith_strv(b, lp->search_path);
×
576
}
577

578
static int create_symlink(
648✔
579
                const LookupPaths *lp,
580
                const char *old_path,
581
                const char *new_path,
582
                bool force,
583
                InstallChange **changes,
584
                size_t *n_changes) {
585

586
        _cleanup_free_ char *dest = NULL;
648✔
587
        const char *rp;
648✔
588
        int r;
648✔
589

590
        assert(old_path);
648✔
591
        assert(new_path);
648✔
592

593
        rp = skip_root(lp->root_dir, old_path);
648✔
594
        if (rp)
648✔
595
                old_path = rp;
646✔
596

597
        /* Actually create a symlink, and remember that we did. This function is
598
         * smart enough to check if there's already a valid symlink in place.
599
         *
600
         * Returns 1 if a symlink was created or already exists and points to the
601
         * right place, or negative on error.
602
         */
603

604
        (void) mkdir_parents_label(new_path, 0755);
648✔
605

606
        if (symlink(old_path, new_path) >= 0) {
648✔
607
                r = install_changes_add(changes, n_changes, INSTALL_CHANGE_SYMLINK, new_path, old_path);
49✔
608
                if (r < 0)
49✔
609
                        return r;
610
                return 1;
49✔
611
        }
612

613
        if (errno != EEXIST)
599✔
614
                return install_changes_add(changes, n_changes, -errno, new_path, NULL);
×
615

616
        r = readlink_malloc(new_path, &dest);
599✔
617
        if (r < 0) {
599✔
618
                /* translate EINVAL (non-symlink exists) to EEXIST */
619
                if (r == -EINVAL)
×
620
                        r = -EEXIST;
×
621

622
                return install_changes_add(changes, n_changes, r, new_path, NULL);
×
623
        }
624

625
        if (chroot_unit_symlinks_equivalent(lp, new_path, dest, old_path)) {
599✔
626
                log_debug("Symlink %s %s %s already exists",
597✔
627
                          new_path, glyph(GLYPH_ARROW_RIGHT), dest);
628
                return 1;
597✔
629
        }
630

631
        if (!force)
2✔
632
                return install_changes_add(changes, n_changes, -EEXIST, new_path, dest);
×
633

634
        r = symlink_atomic(old_path, new_path);
2✔
635
        if (r < 0)
2✔
636
                return install_changes_add(changes, n_changes, r, new_path, NULL);
×
637

638
        r = install_changes_add(changes, n_changes, INSTALL_CHANGE_UNLINK, new_path, NULL);
2✔
639
        if (r < 0)
2✔
640
                return r;
641
        r = install_changes_add(changes, n_changes, INSTALL_CHANGE_SYMLINK, new_path, old_path);
2✔
642
        if (r < 0)
2✔
643
                return r;
×
644

645
        return 1;
646
}
647

648
static int mark_symlink_for_removal(
78✔
649
                Set **remove_symlinks_to,
650
                const char *p) {
651

652
        char *n;
78✔
653
        int r;
78✔
654

655
        assert(p);
78✔
656

657
        r = set_ensure_allocated(remove_symlinks_to, &path_hash_ops_free);
78✔
658
        if (r < 0)
78✔
659
                return r;
78✔
660

661
        r = path_simplify_alloc(p, &n);
78✔
662
        if (r < 0)
78✔
663
                return r;
664

665
        r = set_consume(*remove_symlinks_to, n);
78✔
666
        if (r == -EEXIST)
78✔
667
                return 0;
668
        if (r < 0)
78✔
669
                return r;
×
670

671
        return 1;
672
}
673

674
static int remove_marked_symlinks_fd(
260✔
675
                Set *remove_symlinks_to,
676
                int fd,
677
                const char *path,
678
                const char *config_path,
679
                const LookupPaths *lp,
680
                bool dry_run,
681
                bool *restart,
682
                InstallChange **changes,
683
                size_t *n_changes) {
684

685
        _cleanup_closedir_ DIR *d = NULL;
260✔
686
        int r, ret = 0;
260✔
687

688
        assert(remove_symlinks_to);
260✔
689
        assert(fd >= 0);
260✔
690
        assert(path);
260✔
691
        assert(config_path);
260✔
692
        assert(lp);
260✔
693
        assert(restart);
260✔
694

695
        d = fdopendir(fd);
260✔
696
        if (!d) {
260✔
697
                safe_close(fd);
×
698
                return -errno;
×
699
        }
700

701
        rewinddir(d);
260✔
702

703
        FOREACH_DIRENT(de, d, return -errno)
1,728✔
704
                if (de->d_type == DT_DIR) {
948✔
705
                        _cleanup_close_ int nfd = -EBADF;
1,728✔
706
                        _cleanup_free_ char *p = NULL;
217✔
707

708
                        nfd = RET_NERRNO(openat(fd, de->d_name, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW));
217✔
709
                        if (nfd < 0) {
×
710
                                if (nfd != -ENOENT)
×
711
                                        RET_GATHER(ret, nfd);
×
712
                                continue;
×
713
                        }
714

715
                        p = path_make_absolute(de->d_name, path);
217✔
716
                        if (!p)
217✔
717
                                return -ENOMEM;
×
718

719
                        /* This will close nfd, regardless whether it succeeds or not */
720
                        RET_GATHER(ret, remove_marked_symlinks_fd(remove_symlinks_to,
217✔
721
                                                                  TAKE_FD(nfd), p,
722
                                                                  config_path, lp,
723
                                                                  dry_run,
724
                                                                  restart,
725
                                                                  changes, n_changes));
726

727
                } else if (de->d_type == DT_LNK) {
731✔
728
                        _cleanup_free_ char *p = NULL;
621✔
729
                        bool found;
621✔
730

731
                        if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
621✔
732
                                continue;
×
733

734
                        p = path_make_absolute(de->d_name, path);
621✔
735
                        if (!p)
621✔
736
                                return -ENOMEM;
737
                        path_simplify(p);
621✔
738

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

745
                        if (!found) {
621✔
746
                                _cleanup_free_ char *template = NULL;
×
747

748
                                r = unit_name_template(de->d_name, &template);
599✔
749
                                if (r < 0 && r != -EINVAL)
599✔
750
                                        return r;
×
751
                                if (r >= 0)
599✔
752
                                        found = set_contains(remove_symlinks_to, template);
599✔
753
                        }
754

755
                        if (!found) {
599✔
756
                                _cleanup_free_ char *dest = NULL, *dest_name = NULL;
×
757

758
                                r = chase(p, lp->root_dir, CHASE_NONEXISTENT, &dest, NULL);
589✔
759
                                if (r == -ENOENT)
589✔
760
                                        continue;
×
761
                                if (r < 0) {
589✔
762
                                        log_debug_errno(r, "Failed to resolve symlink \"%s\": %m", p);
×
763
                                        RET_GATHER(ret, install_changes_add(changes, n_changes, r, p, NULL));
×
764
                                        continue;
×
765
                                }
766

767
                                r = path_extract_filename(dest, &dest_name);
589✔
768
                                if (r < 0)
589✔
769
                                        return r;
×
770

771
                                found = set_contains(remove_symlinks_to, dest) ||
1,178✔
772
                                        set_contains(remove_symlinks_to, dest_name);
589✔
773
                        }
774

775
                        if (!found)
589✔
776
                                continue;
588✔
777

778
                        if (!dry_run) {
33✔
779
                                if (unlinkat(fd, de->d_name, 0) < 0 && errno != ENOENT) {
32✔
780
                                        RET_GATHER(ret, install_changes_add(changes, n_changes, -errno, p, NULL));
×
781
                                        continue;
×
782
                                }
783

784
                                (void) rmdir_parents(p, config_path);
32✔
785
                        }
786

787
                        r = install_changes_add(changes, n_changes, INSTALL_CHANGE_UNLINK, p, NULL);
33✔
788
                        if (r < 0)
33✔
789
                                return r;
790

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

794
                        const char *rp = skip_root(lp->root_dir, p);
33✔
795
                        r = mark_symlink_for_removal(&remove_symlinks_to, rp ?: p);
33✔
796
                        if (r < 0)
33✔
797
                                return r;
798
                        if (r > 0 && !dry_run)
33✔
799
                                *restart = true;
32✔
800
                }
801

802
        return ret;
803
}
804

805
static int remove_marked_symlinks(
38✔
806
                Set *remove_symlinks_to,
807
                const char *config_path,
808
                const LookupPaths *lp,
809
                bool dry_run,
810
                InstallChange **changes,
811
                size_t *n_changes) {
812

813
        _cleanup_close_ int fd = -EBADF;
38✔
814
        bool restart;
38✔
815
        int r = 0;
38✔
816

817
        assert(config_path);
38✔
818
        assert(lp);
38✔
819

820
        if (set_isempty(remove_symlinks_to))
38✔
821
                return 0;
822

823
        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC);
30✔
824
        if (fd < 0)
30✔
825
                return errno == ENOENT ? 0 : -errno;
×
826

827
        do {
43✔
828
                int cfd;
43✔
829
                restart = false;
43✔
830

831
                cfd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
43✔
832
                if (cfd < 0)
43✔
833
                        return -errno;
×
834

835
                /* This takes possession of cfd and closes it */
836
                RET_GATHER(r, remove_marked_symlinks_fd(remove_symlinks_to,
43✔
837
                                                        cfd, config_path,
838
                                                        config_path, lp,
839
                                                        dry_run,
840
                                                        &restart,
841
                                                        changes, n_changes));
842
        } while (restart);
43✔
843

844
        return r;
845
}
846

847
static int is_symlink_with_known_name(const InstallInfo *i, const char *name) {
485✔
848
        int r;
485✔
849

850
        assert(i);
485✔
851
        assert(name);
485✔
852

853
        if (streq(name, i->name))
485✔
854
                return true;
855

856
        if (strv_contains(i->aliases, name))
77✔
857
                return true;
858

859
        /* Look for template symlink matching DefaultInstance */
860
        if (i->default_instance && unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) {
70✔
861
                _cleanup_free_ char *s = NULL;
10✔
862

863
                r = unit_name_replace_instance(i->name, i->default_instance, &s);
10✔
864
                if (r < 0) {
10✔
865
                        if (r != -EINVAL)
×
866
                                return r;
867

868
                } else if (streq(name, s))
10✔
869
                        return true;
870
        }
871

872
        return false;
873
}
874

875
static int find_symlinks_in_directory(
148,474✔
876
                DIR *dir,
877
                const char *dir_path,
878
                const char *root_dir,
879
                const InstallInfo *info,
880
                bool ignore_destination,
881
                bool match_name,
882
                bool ignore_same_name,
883
                const char *config_path,
884
                bool *same_name_link) {
885

886
        int r, ret = 0;
148,474✔
887

888
        assert(dir);
148,474✔
889
        assert(dir_path);
148,474✔
890
        assert(info);
148,474✔
891
        assert(unit_name_is_valid(info->name, UNIT_NAME_ANY));
148,474✔
892
        assert(config_path);
148,474✔
893
        assert(same_name_link);
148,474✔
894

895
        FOREACH_DIRENT(de, dir, return -errno) {
3,050,849✔
896
                bool found_path = false, found_dest = false, b = false;
2,606,024✔
897

898
                if (de->d_type != DT_LNK)
2,606,024✔
899
                        continue;
1,949,522✔
900

901
                if (!ignore_destination) {
656,502✔
902
                        _cleanup_free_ char *dest = NULL;
121,504✔
903

904
                        /* Acquire symlink destination */
905
                        r = readlinkat_malloc(dirfd(dir), de->d_name, &dest);
121,504✔
906
                        if (r < 0) {
121,504✔
907
                                if (r != -ENOENT)
×
908
                                        RET_GATHER(ret, r);
×
909
                                continue;
×
910
                        }
911

912
                        /* Check if what the symlink points to matches what we are looking for */
913
                        found_dest = path_equal_filename(dest, info->name);
121,504✔
914
                }
915

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

924
                if (ignore_destination || !ignore_same_name)
656,502✔
925
                        found_path = streq(de->d_name, info->name);
610,414✔
926

927
                if (!found_path && ignore_destination) {
656,502✔
928
                        _cleanup_free_ char *template = NULL;
534,332✔
929

930
                        r = unit_name_template(de->d_name, &template);
534,332✔
931
                        if (r < 0 && r != -EINVAL)
534,332✔
932
                                return r;
×
933
                        if (r >= 0)
534,332✔
934
                                found_dest = streq(template, info->name);
31,136✔
935
                }
936

937
                if (found_path && found_dest) {
656,502✔
938
                        _cleanup_free_ char *p = NULL, *t = NULL;
×
939

940
                        /* Filter out same name links in the main config path */
941
                        p = path_make_absolute(de->d_name, dir_path);
4✔
942
                        t = path_make_absolute(info->name, config_path);
4✔
943
                        if (!p || !t)
4✔
944
                                return -ENOMEM;
×
945

946
                        b = path_equal(p, t);
4✔
947
                }
948

949
                if (b)
4✔
950
                        *same_name_link = true;
4✔
951
                else if (found_path || found_dest) {
656,498✔
952
                        if (!match_name)
800✔
953
                                return 1;
954

955
                        /* Check if symlink name is in the set of names used by [Install] */
956
                        r = is_symlink_with_known_name(info, de->d_name);
485✔
957
                        if (r != 0)
485✔
958
                                return r;
959
                }
960
        }
961

962
        return ret;
963
}
964

965
static int find_symlinks(
57,319✔
966
                const char *root_dir,
967
                const InstallInfo *i,
968
                bool match_name,
969
                bool ignore_same_name,
970
                const char *config_path,
971
                bool *same_name_link) {
972

973
        _cleanup_closedir_ DIR *config_dir = NULL;
57,319✔
974
        int r;
57,319✔
975

976
        assert(i);
57,319✔
977
        assert(config_path);
57,319✔
978
        assert(same_name_link);
57,319✔
979

980
        config_dir = opendir(config_path);
57,319✔
981
        if (!config_dir) {
57,319✔
982
                if (IN_SET(errno, ENOENT, ENOTDIR, EACCES))
25,672✔
983
                        return 0;
984
                return -errno;
×
985
        }
986

987
        FOREACH_DIRENT(de, config_dir, return -errno) {
2,291,890✔
988
                const char *suffix;
2,197,777✔
989
                _cleanup_free_ const char *path = NULL;
2,197,777✔
990
                _cleanup_closedir_ DIR *d = NULL;
2,197,777✔
991

992
                if (de->d_type != DT_DIR)
2,197,777✔
993
                        continue;
1,999,149✔
994

995
                suffix = strrchr(de->d_name, '.');
198,628✔
996
                if (!STRPTR_IN_SET(suffix, ".wants", ".requires", ".upholds"))
198,628✔
997
                        continue;
81,118✔
998

999
                path = path_join(config_path, de->d_name);
117,510✔
1000
                if (!path)
117,510✔
1001
                        return -ENOMEM;
1002

1003
                d = opendir(path);
117,510✔
1004
                if (!d) {
117,510✔
1005
                        log_error_errno(errno, "Failed to open directory \"%s\" while scanning for symlinks, ignoring: %m", path);
×
1006
                        continue;
×
1007
                }
1008

1009
                r = find_symlinks_in_directory(d, path, root_dir, i,
117,510✔
1010
                                               /* ignore_destination= */ true,
1011
                                               /* match_name= */ match_name,
1012
                                               /* ignore_same_name= */ ignore_same_name,
1013
                                               config_path,
1014
                                               same_name_link);
1015
                if (r > 0)
117,510✔
1016
                        return 1;
683✔
1017
                if (r < 0)
116,827✔
1018
                        log_debug_errno(r, "Failed to look up symlinks in \"%s\": %m", path);
116,827✔
1019
        }
1020

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

1032
static int find_symlinks_in_scope(
4,697✔
1033
                RuntimeScope scope,
1034
                const LookupPaths *lp,
1035
                const InstallInfo *info,
1036
                bool match_name,
1037
                UnitFileState *state) {
1038

1039
        bool same_name_link_runtime = false, same_name_link_config = false;
4,697✔
1040
        bool enabled_in_runtime = false, enabled_at_all = false;
4,697✔
1041
        bool ignore_same_name = false;
4,697✔
1042
        int r;
4,697✔
1043

1044
        assert(lp);
4,697✔
1045
        assert(info);
4,697✔
1046

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

1051
        STRV_FOREACH(p, lp->search_path)  {
61,877✔
1052
                bool same_name_link = false;
57,319✔
1053

1054
                r = find_symlinks(lp->root_dir, info, match_name, ignore_same_name, *p, &same_name_link);
57,319✔
1055
                if (r < 0)
57,319✔
1056
                        return r;
139✔
1057
                if (r > 0) {
57,319✔
1058
                        /* We found symlinks in this dir? Yay! Let's see where precisely it is enabled. */
1059

1060
                        if (path_equal(*p, lp->persistent_config)) {
733✔
1061
                                /* This is the best outcome, let's return it immediately. */
1062
                                *state = UNIT_FILE_ENABLED;
132✔
1063
                                return 1;
132✔
1064
                        }
1065

1066
                        /* look for global enablement of user units */
1067
                        if (scope == RUNTIME_SCOPE_USER && path_is_user_config_dir(*p)) {
601✔
1068
                                *state = UNIT_FILE_ENABLED;
7✔
1069
                                return 1;
7✔
1070
                        }
1071

1072
                        r = path_is_runtime(lp, *p, false);
594✔
1073
                        if (r < 0)
594✔
1074
                                return r;
1075
                        if (r > 0)
594✔
1076
                                enabled_in_runtime = true;
1077
                        else
1078
                                enabled_at_all = true;
581✔
1079

1080
                } else if (same_name_link) {
56,586✔
1081
                        if (path_equal(*p, lp->persistent_config))
4✔
1082
                                same_name_link_config = true;
1083
                        else {
1084
                                r = path_is_runtime(lp, *p, false);
2✔
1085
                                if (r < 0)
2✔
1086
                                        return r;
1087
                                if (r > 0)
2✔
1088
                                        same_name_link_runtime = true;
×
1089
                        }
1090
                }
1091

1092
                /* Check if next iteration will be "below" the unit file (either a regular file
1093
                 * or a symlink), and hence should be ignored */
1094
                if (!ignore_same_name && path_startswith(info->path, *p))
57,180✔
1095
                        ignore_same_name = true;
4,554✔
1096
        }
1097

1098
        if (enabled_in_runtime) {
4,558✔
1099
                *state = UNIT_FILE_ENABLED_RUNTIME;
13✔
1100
                return 1;
13✔
1101
        }
1102

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

1112
        /* Hmm, we didn't find it, but maybe we found the same name
1113
         * link? */
1114
        if (same_name_link_config) {
4,536✔
1115
                *state = UNIT_FILE_LINKED;
2✔
1116
                return 1;
2✔
1117
        }
1118
        if (same_name_link_runtime) {
4,534✔
1119
                *state = UNIT_FILE_LINKED_RUNTIME;
×
1120
                return 1;
×
1121
        }
1122

1123
        return 0;
1124
}
1125

1126
static void install_info_clear(InstallInfo *i) {
24,632✔
1127
        if (!i)
24,632✔
1128
                return;
1129

1130
        i->name = mfree(i->name);
24,632✔
1131
        i->path = mfree(i->path);
24,632✔
1132
        i->root = mfree(i->root);
24,632✔
1133
        i->aliases = strv_free(i->aliases);
24,632✔
1134
        i->wanted_by = strv_free(i->wanted_by);
24,632✔
1135
        i->required_by = strv_free(i->required_by);
24,632✔
1136
        i->upheld_by = strv_free(i->upheld_by);
24,632✔
1137
        i->also = strv_free(i->also);
24,632✔
1138
        i->default_instance = mfree(i->default_instance);
24,632✔
1139
        i->symlink_target = mfree(i->symlink_target);
24,632✔
1140
}
1141

1142
static InstallInfo* install_info_free(InstallInfo *i) {
23,041✔
1143
        install_info_clear(i);
23,041✔
1144
        return mfree(i);
23,041✔
1145
}
1146

1147
DEFINE_TRIVIAL_CLEANUP_FUNC(InstallInfo*, install_info_free);
23,041✔
1148

1149
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
23,041✔
1150
                install_info_hash_ops,
1151
                char, string_hash_func, string_compare_func,
1152
                InstallInfo, install_info_free);
1153

1154
static void install_context_done(InstallContext *ctx) {
13,569✔
1155
        assert(ctx);
13,569✔
1156

1157
        ctx->will_process = ordered_hashmap_free(ctx->will_process);
13,569✔
1158
        ctx->have_processed = ordered_hashmap_free(ctx->have_processed);
13,569✔
1159
}
13,569✔
1160

1161
static InstallInfo *install_info_find(InstallContext *ctx, const char *name) {
41,810✔
1162
        InstallInfo *i;
41,810✔
1163

1164
        i = ordered_hashmap_get(ctx->have_processed, name);
41,810✔
1165
        if (i)
41,810✔
1166
                return i;
1167

1168
        return ordered_hashmap_get(ctx->will_process, name);
41,793✔
1169
}
1170

1171
static int install_info_may_process(
447✔
1172
                const InstallInfo *i,
1173
                const LookupPaths *lp,
1174
                InstallChange **changes,
1175
                size_t *n_changes) {
1176
        assert(i);
447✔
1177
        assert(lp);
447✔
1178

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

1182
        if (i->install_mode == INSTALL_MODE_MASKED)
447✔
1183
                return install_changes_add(changes, n_changes, -ERFKILL, i->path, NULL);
18✔
1184
        if (path_is_generator(lp, i->path) ||
858✔
1185
            path_is_transient(lp, i->path))
429✔
1186
                return install_changes_add(changes, n_changes, -EADDRNOTAVAIL, i->path, NULL);
×
1187

1188
        return 0;
1189
}
1190

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

1205
        _cleanup_free_ char *name_alloc = NULL;
23,101✔
1206
        int r;
23,101✔
1207

1208
        assert(ctx);
23,101✔
1209

1210
        if (!name) {
23,101✔
1211
                assert(path);
2✔
1212

1213
                r = path_extract_filename(path, &name_alloc);
2✔
1214
                if (r < 0)
2✔
1215
                        return r;
1216

1217
                name = name_alloc;
2✔
1218
        }
1219

1220
        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
23,101✔
1221
                return -EINVAL;
1222

1223
        InstallInfo *i = install_info_find(ctx, name);
23,101✔
1224
        if (i) {
23,101✔
1225
                i->auxiliary = i->auxiliary && auxiliary;
60✔
1226

1227
                if (ret)
60✔
1228
                        *ret = i;
9✔
1229
                return 0;
60✔
1230
        }
1231

1232
        _cleanup_(install_info_freep) InstallInfo *new_info = new(InstallInfo, 1);
46,082✔
1233
        if (!new_info)
23,041✔
1234
                return -ENOMEM;
1235

1236
        *new_info = (InstallInfo) {
23,041✔
1237
                .install_mode = _INSTALL_MODE_INVALID,
1238
                .auxiliary = auxiliary,
1239
        };
1240

1241
        if (name_alloc)
23,041✔
1242
                new_info->name = TAKE_PTR(name_alloc);
2✔
1243
        else {
1244
                new_info->name = strdup(name);
23,039✔
1245
                if (!new_info->name)
23,039✔
1246
                        return -ENOMEM;
1247
        }
1248

1249
        r = strdup_to(&new_info->root, root);
23,041✔
1250
        if (r < 0)
23,041✔
1251
                return r;
1252

1253
        r = strdup_to(&new_info->path, path);
23,041✔
1254
        if (r < 0)
23,041✔
1255
                return r;
1256

1257
        r = ordered_hashmap_ensure_put(&ctx->will_process, &install_info_hash_ops, new_info->name, new_info);
23,041✔
1258
        if (r < 0)
23,041✔
1259
                return r;
1260
        i = TAKE_PTR(new_info);
23,041✔
1261

1262
        if (ret)
23,041✔
1263
                *ret = i;
22,840✔
1264
        return 1;
1265
}
1266

1267
static int config_parse_alias(
161✔
1268
                const char *unit,
1269
                const char *filename,
1270
                unsigned line,
1271
                const char *section,
1272
                unsigned section_line,
1273
                const char *lvalue,
1274
                int ltype,
1275
                const char *rvalue,
1276
                void *data,
1277
                void *userdata) {
1278

1279
        UnitType type;
161✔
1280

1281
        assert(unit);
161✔
1282
        assert(filename);
161✔
1283
        assert(lvalue);
161✔
1284
        assert(rvalue);
161✔
1285

1286
        type = unit_name_to_type(unit);
161✔
1287
        if (!unit_type_may_alias(type))
161✔
1288
                return log_syntax(unit, LOG_WARNING, filename, line, 0,
×
1289
                                  "Alias= is not allowed for %s units, ignoring.",
1290
                                  unit_type_to_string(type));
1291

1292
        return config_parse_strv(unit, filename, line, section, section_line,
161✔
1293
                                 lvalue, ltype, rvalue, data, userdata);
1294
}
1295

1296
static int config_parse_also(
184✔
1297
                const char *unit,
1298
                const char *filename,
1299
                unsigned line,
1300
                const char *section,
1301
                unsigned section_line,
1302
                const char *lvalue,
1303
                int ltype,
1304
                const char *rvalue,
1305
                void *data,
1306
                void *userdata) {
1307

1308
        InstallInfo *info = ASSERT_PTR(userdata);
184✔
1309
        InstallContext *ctx = ASSERT_PTR(data);
184✔
1310
        int r;
184✔
1311

1312
        assert(unit);
184✔
1313
        assert(filename);
184✔
1314
        assert(lvalue);
184✔
1315
        assert(rvalue);
184✔
1316

1317
        for (;;) {
252✔
1318
                _cleanup_free_ char *word = NULL, *printed = NULL;
×
1319

1320
                r = extract_first_word(&rvalue, &word, NULL, 0);
436✔
1321
                if (r < 0)
436✔
1322
                        return r;
1323
                if (r == 0)
436✔
1324
                        break;
1325

1326
                r = install_name_printf(ctx->scope, info, word, &printed);
252✔
1327
                if (r < 0)
252✔
1328
                        return log_syntax(unit, LOG_WARNING, filename, line, r,
×
1329
                                          "Failed to resolve unit name in Also=\"%s\": %m", word);
1330

1331
                r = install_info_add(ctx, printed, NULL, info->root, /* auxiliary= */ true, NULL);
252✔
1332
                if (r < 0)
252✔
1333
                        return r;
1334

1335
                r = strv_push(&info->also, printed);
252✔
1336
                if (r < 0)
252✔
1337
                        return r;
1338

1339
                printed = NULL;
252✔
1340
        }
1341

1342
        return 0;
184✔
1343
}
1344

1345
static int config_parse_default_instance(
98✔
1346
                const char *unit,
1347
                const char *filename,
1348
                unsigned line,
1349
                const char *section,
1350
                unsigned section_line,
1351
                const char *lvalue,
1352
                int ltype,
1353
                const char *rvalue,
1354
                void *data,
1355
                void *userdata) {
1356

1357
        InstallContext *ctx = ASSERT_PTR(data);
98✔
1358
        InstallInfo *info = ASSERT_PTR(userdata);
98✔
1359
        _cleanup_free_ char *printed = NULL;
98✔
1360
        int r;
98✔
1361

1362
        assert(unit);
98✔
1363
        assert(filename);
98✔
1364
        assert(lvalue);
98✔
1365
        assert(rvalue);
98✔
1366

1367
        if (unit_name_is_valid(unit, UNIT_NAME_INSTANCE))
98✔
1368
                /* When enabling an instance, we might be using a template unit file,
1369
                 * but we should ignore DefaultInstance silently. */
1370
                return 0;
1371
        if (!unit_name_is_valid(unit, UNIT_NAME_TEMPLATE))
45✔
1372
                return log_syntax(unit, LOG_WARNING, filename, line, 0,
×
1373
                                  "DefaultInstance= only makes sense for template units, ignoring.");
1374

1375
        r = install_name_printf(ctx->scope, info, rvalue, &printed);
45✔
1376
        if (r < 0)
45✔
1377
                return log_syntax(unit, LOG_WARNING, filename, line, r,
×
1378
                                  "Failed to resolve instance name in DefaultInstance=\"%s\": %m", rvalue);
1379

1380
        if (isempty(printed))
45✔
1381
                printed = mfree(printed);
×
1382

1383
        if (printed && !unit_instance_is_valid(printed))
45✔
1384
                return log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL),
×
1385
                                  "Invalid DefaultInstance= value \"%s\".", printed);
1386

1387
        return free_and_replace(info->default_instance, printed);
45✔
1388
}
1389

1390
static int unit_file_load(
252,410✔
1391
                InstallContext *ctx,
1392
                InstallInfo *info,
1393
                const char *path,
1394
                const char *root_dir,
1395
                SearchFlags flags) {
1396

1397
        const ConfigTableItem items[] = {
252,410✔
1398
                { "Install", "Alias",           config_parse_alias,            0, &info->aliases           },
252,410✔
1399
                { "Install", "WantedBy",        config_parse_strv,             0, &info->wanted_by         },
252,410✔
1400
                { "Install", "RequiredBy",      config_parse_strv,             0, &info->required_by       },
252,410✔
1401
                { "Install", "UpheldBy",        config_parse_strv,             0, &info->upheld_by         },
252,410✔
1402
                { "Install", "DefaultInstance", config_parse_default_instance, 0, info                     },
1403
                { "Install", "Also",            config_parse_also,             0, ctx                      },
1404
                {}
1405
        };
1406

1407
        UnitType type;
252,410✔
1408
        _cleanup_fclose_ FILE *f = NULL;
252,410✔
1409
        _cleanup_close_ int fd = -EBADF;
252,410✔
1410
        struct stat st;
252,410✔
1411
        int r;
252,410✔
1412

1413
        assert(info);
252,410✔
1414
        assert(path);
252,410✔
1415

1416
        if (!(flags & SEARCH_DROPIN)) {
252,410✔
1417
                /* Loading or checking for the main unit file… */
1418

1419
                type = unit_name_to_type(info->name);
249,425✔
1420
                if (type < 0)
249,425✔
1421
                        return -EINVAL;
1422
                if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE|UNIT_NAME_INSTANCE) && !unit_type_may_template(type))
249,425✔
1423
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1424
                                               "%s: unit type %s cannot be templated, ignoring.", path, unit_type_to_string(type));
1425

1426
                if (!(flags & SEARCH_LOAD)) {
249,425✔
1427
                        if (lstat(path, &st) < 0)
214,670✔
1428
                                return -errno;
195,921✔
1429

1430
                        if (null_or_empty(&st))
18,749✔
1431
                                info->install_mode = INSTALL_MODE_MASKED;
×
1432
                        else if (S_ISREG(st.st_mode))
18,749✔
1433
                                info->install_mode = INSTALL_MODE_REGULAR;
18,158✔
1434
                        else if (S_ISLNK(st.st_mode))
591✔
1435
                                return -ELOOP;
1436
                        else if (S_ISDIR(st.st_mode))
×
1437
                                return -EISDIR;
1438
                        else
1439
                                return -ENOTTY;
×
1440

1441
                        return 0;
18,158✔
1442
                }
1443

1444
                fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
34,755✔
1445
                if (fd < 0)
34,755✔
1446
                        return -errno;
31,680✔
1447
        } else {
1448
                /* 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. */
1449

1450
                if (!(flags & SEARCH_LOAD))
2,985✔
1451
                        return 0;
1452

1453
                fd = chase_and_open(path, root_dir, 0, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
2,465✔
1454
                if (fd < 0)
2,465✔
1455
                        return fd;
1456
        }
1457

1458
        if (fstat(fd, &st) < 0)
5,540✔
1459
                return -errno;
×
1460

1461
        if (null_or_empty(&st)) {
5,540✔
1462
                if ((flags & SEARCH_DROPIN) == 0)
×
1463
                        info->install_mode = INSTALL_MODE_MASKED;
×
1464

1465
                return 0;
×
1466
        }
1467

1468
        r = stat_verify_regular(&st);
5,540✔
1469
        if (r < 0)
5,540✔
1470
                return r;
1471

1472
        f = take_fdopen(&fd, "r");
5,540✔
1473
        if (!f)
5,540✔
1474
                return -errno;
×
1475

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

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

1499
        if ((flags & SEARCH_DROPIN) == 0)
5,540✔
1500
                info->install_mode = INSTALL_MODE_REGULAR;
3,075✔
1501

1502
        return
5,540✔
1503
                (int) strv_length(info->aliases) +
5,540✔
1504
                (int) strv_length(info->wanted_by) +
5,540✔
1505
                (int) strv_length(info->required_by) +
5,540✔
1506
                (int) strv_length(info->upheld_by);
5,540✔
1507
}
1508

1509
static int unit_file_load_or_readlink(
252,410✔
1510
                InstallContext *ctx,
1511
                InstallInfo *info,
1512
                const char *path,
1513
                const LookupPaths *lp,
1514
                SearchFlags flags) {
1515
        int r;
252,410✔
1516

1517
        r = unit_file_load(ctx, info, path, lp->root_dir, flags);
252,410✔
1518
        if (r != -ELOOP || (flags & SEARCH_DROPIN))
252,410✔
1519
                return r;
1520

1521
        /* This is a symlink, let's read and verify it. */
1522
        r = unit_file_resolve_symlink(lp->root_dir, lp->search_path,
750✔
1523
                                      NULL, AT_FDCWD, path,
1524
                                      true, &info->symlink_target);
1525
        if (r < 0)
750✔
1526
                return r;
1527
        bool outside_search_path = r > 0;
749✔
1528

1529
        r = null_or_empty_path_with_root(info->symlink_target, lp->root_dir);
749✔
1530
        if (r < 0 && r != -ENOENT)
749✔
1531
                return log_debug_errno(r, "Failed to stat %s: %m", info->symlink_target);
×
1532
        if (r > 0)
749✔
1533
                info->install_mode = INSTALL_MODE_MASKED;
157✔
1534
        else if (outside_search_path)
592✔
1535
                info->install_mode = INSTALL_MODE_LINKED;
9✔
1536
        else
1537
                info->install_mode = INSTALL_MODE_ALIAS;
583✔
1538

1539
        return 0;
1540
}
1541

1542
static int unit_file_search(
23,463✔
1543
                InstallContext *ctx,
1544
                InstallInfo *info,
1545
                const LookupPaths *lp,
1546
                SearchFlags flags) {
1547

1548
        const char *dropin_dir_name = NULL, *dropin_template_dir_name = NULL;
23,463✔
1549
        _cleanup_strv_free_ char **dirs = NULL, **files = NULL;
23,463✔
1550
        _cleanup_free_ char *template = NULL;
23,463✔
1551
        bool found_unit = false;
23,463✔
1552
        int r, result;
23,463✔
1553

1554
        assert(info);
23,463✔
1555
        assert(lp);
23,463✔
1556

1557
        /* Was this unit already loaded? */
1558
        if (info->install_mode != _INSTALL_MODE_INVALID)
23,463✔
1559
                return 0;
1560

1561
        if (info->path)
22,965✔
1562
                return unit_file_load_or_readlink(ctx, info, info->path, lp, flags);
2✔
1563

1564
        assert(info->name);
22,963✔
1565

1566
        if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
22,963✔
1567
                r = unit_name_template(info->name, &template);
159✔
1568
                if (r < 0)
159✔
1569
                        return r;
1570
        }
1571

1572
        STRV_FOREACH(p, lp->search_path) {
248,937✔
1573
                _cleanup_free_ char *path = NULL;
225,975✔
1574

1575
                path = path_join(*p, info->name);
247,810✔
1576
                if (!path)
247,810✔
1577
                        return -ENOMEM;
1578

1579
                r = unit_file_load_or_readlink(ctx, info, path, lp, flags);
247,810✔
1580
                if (r >= 0) {
247,810✔
1581
                        info->path = TAKE_PTR(path);
21,835✔
1582
                        result = r;
21,835✔
1583
                        found_unit = true;
21,835✔
1584
                        break;
21,835✔
1585
                } else if (!IN_SET(r, -ENOENT, -ENOTDIR, -EACCES))
225,975✔
1586
                        return r;
1587
        }
1588

1589
        if (!found_unit && template) {
1,127✔
1590

1591
                /* Unit file doesn't exist, however instance
1592
                 * enablement was requested.  We will check if it is
1593
                 * possible to load template unit file. */
1594

1595
                STRV_FOREACH(p, lp->search_path) {
1,603✔
1596
                        _cleanup_free_ char *path = NULL;
1,468✔
1597

1598
                        path = path_join(*p, template);
1,599✔
1599
                        if (!path)
1,599✔
1600
                                return -ENOMEM;
1601

1602
                        r = unit_file_load_or_readlink(ctx, info, path, lp, flags);
1,599✔
1603
                        if (r >= 0) {
1,599✔
1604
                                info->path = TAKE_PTR(path);
131✔
1605
                                result = r;
131✔
1606
                                found_unit = true;
131✔
1607
                                break;
131✔
1608
                        } else if (!IN_SET(r, -ENOENT, -ENOTDIR, -EACCES))
1,468✔
1609
                                return r;
1610
                }
1611
        }
1612

1613
        if (!found_unit)
21,966✔
1614
                return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
2,980✔
1615
                                       "Cannot find unit %s%s%s.",
1616
                                       info->name, template ? " or " : "", strempty(template));
1617

1618
        if (info->install_mode == INSTALL_MODE_MASKED)
21,966✔
1619
                return result;
1620

1621
        /* Search for drop-in directories */
1622

1623
        dropin_dir_name = strjoina(info->name, ".d");
109,045✔
1624
        STRV_FOREACH(p, lp->search_path) {
322,569✔
1625
                char *path;
300,760✔
1626

1627
                path = path_join(*p, dropin_dir_name);
300,760✔
1628
                if (!path)
300,760✔
1629
                        return -ENOMEM;
1630

1631
                r = strv_consume(&dirs, path);
300,760✔
1632
                if (r < 0)
300,760✔
1633
                        return r;
1634
        }
1635

1636
        if (template) {
21,809✔
1637
                dropin_template_dir_name = strjoina(template, ".d");
775✔
1638
                STRV_FOREACH(p, lp->search_path) {
2,159✔
1639
                        char *path;
2,004✔
1640

1641
                        path = path_join(*p, dropin_template_dir_name);
2,004✔
1642
                        if (!path)
2,004✔
1643
                                return -ENOMEM;
1644

1645
                        r = strv_consume(&dirs, path);
2,004✔
1646
                        if (r < 0)
2,004✔
1647
                                return r;
1648
                }
1649
        }
1650

1651
        /* Load drop-in conf files */
1652

1653
        r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) dirs);
21,809✔
1654
        if (r < 0)
21,809✔
1655
                return log_debug_errno(r, "Failed to get list of conf files: %m");
×
1656

1657
        STRV_FOREACH(p, files) {
24,794✔
1658
                r = unit_file_load_or_readlink(ctx, info, *p, lp, flags | SEARCH_DROPIN);
2,985✔
1659
                if (r < 0)
2,985✔
1660
                        return log_debug_errno(r, "Failed to load conf file \"%s\": %m", *p);
×
1661
        }
1662

1663
        return result;
1664
}
1665

1666
static int install_info_follow(
606✔
1667
                InstallContext *ctx,
1668
                InstallInfo *info,
1669
                const LookupPaths *lp,
1670
                SearchFlags flags,
1671
                bool ignore_different_name) {
1672

1673
        assert(ctx);
606✔
1674
        assert(info);
606✔
1675

1676
        if (!IN_SET(info->install_mode, INSTALL_MODE_ALIAS, INSTALL_MODE_LINKED))
606✔
1677
                return -EINVAL;
1678
        if (!info->symlink_target)
606✔
1679
                return -EINVAL;
1680

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

1683
        if (!ignore_different_name && !path_equal_filename(info->symlink_target, info->name))
606✔
1684
                return -EXDEV;
1685

1686
        free_and_replace(info->path, info->symlink_target);
14✔
1687
        info->install_mode = _INSTALL_MODE_INVALID;
14✔
1688

1689
        return unit_file_load_or_readlink(ctx, info, info->path, lp, flags);
14✔
1690
}
1691

1692
/**
1693
 * Search for the unit file. If the unit name is a symlink, follow the symlink to the
1694
 * target, maybe more than once. Propagate the instance name if present.
1695
 */
1696
static int install_info_traverse(
22,856✔
1697
                InstallContext *ctx,
1698
                const LookupPaths *lp,
1699
                InstallInfo *start,
1700
                SearchFlags flags,
1701
                InstallInfo **ret) {
1702

1703
        InstallInfo *i;
22,856✔
1704
        unsigned k = 0;
22,856✔
1705
        int r;
22,856✔
1706

1707
        assert(ctx);
22,856✔
1708
        assert(lp);
22,856✔
1709
        assert(start);
22,856✔
1710

1711
        r = unit_file_search(ctx, start, lp, flags);
22,856✔
1712
        if (r < 0)
22,856✔
1713
                return r;
22,856✔
1714

1715
        i = start;
21,859✔
1716
        while (IN_SET(i->install_mode, INSTALL_MODE_ALIAS, INSTALL_MODE_LINKED)) {
22,460✔
1717
                /* Follow the symlink */
1718

1719
                if (++k > UNIT_FILE_FOLLOW_SYMLINK_MAX)
601✔
1720
                        return -ELOOP;
1721

1722
                if (!FLAGS_SET(flags, SEARCH_FOLLOW_CONFIG_SYMLINKS)) {
601✔
1723
                        r = path_is_config(lp, i->path, true);
6✔
1724
                        if (r < 0)
6✔
1725
                                return r;
1726
                        if (r > 0)
6✔
1727
                                return -ELOOP;
1728
                }
1729

1730
                r = install_info_follow(ctx, i, lp, flags,
1,202✔
1731
                                        /* If linked, don't look at the target name */
1732
                                        /* ignore_different_name= */ i->install_mode == INSTALL_MODE_LINKED);
601✔
1733
                if (r == -EXDEV && i->symlink_target) {
601✔
1734
                        _cleanup_free_ char *target_name = NULL, *unit_instance = NULL;
587✔
1735
                        const char *bn;
592✔
1736

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

1739
                        r = path_extract_filename(i->symlink_target, &target_name);
592✔
1740
                        if (r < 0)
592✔
1741
                                return r;
1742

1743
                        if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE) &&
633✔
1744
                            unit_name_is_valid(target_name, UNIT_NAME_TEMPLATE)) {
41✔
1745

1746
                                _cleanup_free_ char *instance = NULL;
41✔
1747

1748
                                r = unit_name_to_instance(i->name, &instance);
41✔
1749
                                if (r < 0)
41✔
1750
                                        return r;
1751

1752
                                r = unit_name_replace_instance(target_name, instance, &unit_instance);
41✔
1753
                                if (r < 0)
41✔
1754
                                        return r;
1755

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

1760
                                        r = install_info_follow(ctx, i, lp, flags, true);
5✔
1761
                                        if (r < 0)
5✔
1762
                                                return r;
1763

1764
                                        continue;
5✔
1765
                                }
1766

1767
                                bn = unit_instance;
36✔
1768
                        } else
1769
                                bn = target_name;
551✔
1770

1771
                        r = install_info_add(ctx, bn, NULL, lp->root_dir, /* auxiliary= */ false, &i);
587✔
1772
                        if (r < 0)
587✔
1773
                                return r;
1774

1775
                        /* Try again, with the new target we found. */
1776
                        r = unit_file_search(ctx, i, lp, flags);
587✔
1777
                        if (r == -ENOENT)
587✔
1778
                                /* Translate error code to highlight this specific case */
1779
                                return -ENOLINK;
1780
                }
1781
                if (r < 0)
596✔
1782
                        return r;
1783
        }
1784

1785
        if (ret)
21,859✔
1786
                *ret = i;
20,719✔
1787

1788
        return 0;
1789
}
1790

1791
/**
1792
 * Call install_info_add() with name_or_path as the path (if name_or_path starts with "/")
1793
 * or the name (otherwise). root_dir is prepended to the path.
1794
 */
1795
static int install_info_add_auto(
22,247✔
1796
                InstallContext *ctx,
1797
                const LookupPaths *lp,
1798
                const char *name_or_path,
1799
                InstallInfo **ret) {
1800

1801
        assert(ctx);
22,247✔
1802
        assert(name_or_path);
22,247✔
1803

1804
        if (path_is_absolute(name_or_path)) {
22,247✔
1805
                _cleanup_free_ char *pp = path_join(lp->root_dir, name_or_path);
4✔
1806
                if (!pp)
2✔
1807
                        return -ENOMEM;
1808

1809
                return install_info_add(ctx, NULL, pp, lp->root_dir, /* auxiliary= */ false, ret);
2✔
1810
        } else
1811
                return install_info_add(ctx, name_or_path, NULL, lp->root_dir, /* auxiliary= */ false, ret);
22,245✔
1812
}
1813

1814
static int install_info_discover(
22,247✔
1815
                InstallContext *ctx,
1816
                const LookupPaths *lp,
1817
                const char *name_or_path,
1818
                SearchFlags flags,
1819
                InstallInfo **ret,
1820
                InstallChange **changes,
1821
                size_t *n_changes) {
1822

1823
        InstallInfo *info;
22,247✔
1824
        int r;
22,247✔
1825

1826
        assert(ctx);
22,247✔
1827
        assert(lp);
22,247✔
1828
        assert(name_or_path);
22,247✔
1829

1830
        r = install_info_add_auto(ctx, lp, name_or_path, &info);
22,247✔
1831
        if (r >= 0)
22,247✔
1832
                r = install_info_traverse(ctx, lp, info, flags, ret);
22,247✔
1833

1834
        if (r < 0)
22,247✔
1835
                return install_changes_add(changes, n_changes, r, name_or_path, NULL);
997✔
1836

1837
        return r;
1838
}
1839

1840
static int install_info_discover_and_check(
448✔
1841
                InstallContext *ctx,
1842
                const LookupPaths *lp,
1843
                const char *name_or_path,
1844
                SearchFlags flags,
1845
                InstallInfo **ret,
1846
                InstallChange **changes,
1847
                size_t *n_changes) {
1848

1849
        int r;
448✔
1850

1851
        r = install_info_discover(ctx, lp, name_or_path, flags, ret, changes, n_changes);
448✔
1852
        if (r < 0)
448✔
1853
                return r;
1854

1855
        return install_info_may_process(ret ? *ret : NULL, lp, changes, n_changes);
447✔
1856
}
1857

1858
int unit_file_verify_alias(
224✔
1859
                const InstallInfo *info,
1860
                const char *dst,
1861
                char **ret_dst,
1862
                InstallChange **changes,
1863
                size_t *n_changes) {
1864

1865
        _cleanup_free_ char *dst_updated = NULL;
224✔
1866
        int r;
224✔
1867

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

1881
        const char *path_alias = strrchr(dst, '/');
224✔
1882
        if (path_alias) {
224✔
1883
                /* This branch covers legacy Alias= function of creating .wants and .requires symlinks. */
1884
                _cleanup_free_ char *dir = NULL;
89✔
1885
                char *p;
89✔
1886

1887
                path_alias++; /* skip over slash */
89✔
1888

1889
                r = path_extract_directory(dst, &dir);
89✔
1890
                if (r < 0)
89✔
1891
                        return log_error_errno(r, "Failed to extract parent directory from '%s': %m", dst);
×
1892

1893
                p = endswith(dir, ".wants");
89✔
1894
                if (!p)
89✔
1895
                        p = endswith(dir, ".requires");
57✔
1896
                if (!p) {
57✔
1897
                        r = install_changes_add(changes, n_changes, -EXDEV, dst, NULL);
10✔
1898
                        if (r != -EXDEV)
10✔
1899
                                return r;
1900

1901
                        return log_debug_errno(SYNTHETIC_ERRNO(EXDEV), "Invalid path \"%s\" in alias.", dir);
10✔
1902
                }
1903

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

1906
                UnitNameFlags type = unit_name_classify(dir);
79✔
1907
                if (type < 0) {
79✔
1908
                        r = install_changes_add(changes, n_changes, -EXDEV, dst, NULL);
1✔
1909
                        if (r != -EXDEV)
1✔
1910
                                return r;
1911
                        return log_debug_errno(SYNTHETIC_ERRNO(EXDEV),
1✔
1912
                                               "Invalid unit name component \"%s\" in alias.", dir);
1913
                }
1914

1915
                const bool instance_propagation = type == UNIT_NAME_TEMPLATE;
78✔
1916

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

1926
                        return log_debug_errno(SYNTHETIC_ERRNO(EXDEV),
65✔
1927
                                               "Invalid unit \"%s\" symlink \"%s\".",
1928
                                               info->name, dst);
1929
                }
1930

1931
        } else {
1932
                /* If the symlink target has an instance set and the symlink source doesn't, we "propagate
1933
                 * the instance", i.e. instantiate the symlink source with the target instance. */
1934
                if (unit_name_is_valid(dst, UNIT_NAME_TEMPLATE)) {
135✔
1935
                        _cleanup_free_ char *inst = NULL;
26✔
1936

1937
                        UnitNameFlags type = unit_name_to_instance(info->name, &inst);
26✔
1938
                        if (type < 0) {
26✔
1939
                                r = install_changes_add(changes, n_changes, -EUCLEAN, info->name, NULL);
×
1940
                                if (r != -EUCLEAN)
×
1941
                                        return r;
1942
                                return log_debug_errno(type, "Failed to extract instance name from \"%s\": %m", info->name);
×
1943
                        }
1944

1945
                        if (type == UNIT_NAME_INSTANCE) {
26✔
1946
                                r = unit_name_replace_instance(dst, inst, &dst_updated);
4✔
1947
                                if (r < 0)
4✔
1948
                                        return log_error_errno(r, "Failed to build unit name from %s+%s: %m",
×
1949
                                                               dst, inst);
1950
                        }
1951
                }
1952

1953
                r = unit_validate_alias_symlink_or_warn(LOG_DEBUG, dst_updated ?: dst, info->name);
266✔
1954
                if (r == -ELOOP)  /* -ELOOP means self-alias, which we (quietly) ignore */
135✔
1955
                        return r;
1956
                if (r < 0)
135✔
1957
                        return install_changes_add(changes, n_changes,
21✔
1958
                                                   r == -EINVAL ? -EXDEV : r,
1959
                                                   dst_updated ?: dst,
21✔
1960
                                                   info->name);
21✔
1961
        }
1962

1963
        *ret_dst = TAKE_PTR(dst_updated);
127✔
1964
        return 0;
127✔
1965
}
1966

1967
static int install_info_symlink_alias(
530✔
1968
                RuntimeScope scope,
1969
                UnitFileFlags file_flags,
1970
                InstallInfo *info,
1971
                const LookupPaths *lp,
1972
                const char *config_path,
1973
                bool force,
1974
                InstallChange **changes,
1975
                size_t *n_changes) {
1976

1977
        int r, ret = 0;
530✔
1978

1979
        assert(info);
530✔
1980
        assert(lp);
530✔
1981
        assert(config_path);
530✔
1982

1983
        STRV_FOREACH(s, info->aliases) {
634✔
1984
                _cleanup_free_ char *alias_path = NULL, *alias_target = NULL, *dst = NULL, *dst_updated = NULL;
104✔
1985

1986
                r = install_name_printf(scope, info, *s, &dst);
104✔
1987
                if (r < 0) {
104✔
1988
                        RET_GATHER(ret, install_changes_add(changes, n_changes, r, *s, NULL));
×
1989
                        continue;
×
1990
                }
1991

1992
                r = unit_file_verify_alias(info, dst, &dst_updated, changes, n_changes);
104✔
1993
                if (r < 0) {
104✔
1994
                        if (r != -ELOOP)
×
1995
                                RET_GATHER(ret, r);
×
1996
                        continue;
×
1997
                }
1998

1999
                alias_path = path_make_absolute(dst_updated ?: dst, config_path);
104✔
2000
                if (!alias_path)
104✔
2001
                        return -ENOMEM;
2002

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

2015
                bool broken;
104✔
2016
                r = chase(alias_path, lp->root_dir, CHASE_NONEXISTENT, /* ret_path= */ NULL, /* ret_fd= */ NULL);
104✔
2017
                if (r < 0 && r != -ENOENT) {
104✔
2018
                        RET_GATHER(ret, r);
×
2019
                        continue;
×
2020
                }
2021
                broken = r == 0; /* symlink target does not exist? */
104✔
2022

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

2032
        return ret;
2033
}
2034

2035
static int install_info_symlink_wants(
1,590✔
2036
                RuntimeScope scope,
2037
                UnitFileFlags file_flags,
2038
                InstallInfo *info,
2039
                const LookupPaths *lp,
2040
                const char *config_path,
2041
                char **list,
2042
                const char *suffix,
2043
                InstallChange **changes,
2044
                size_t *n_changes) {
2045

2046
        _cleanup_(install_info_clear) InstallInfo instance = {
×
2047
                .install_mode = _INSTALL_MODE_INVALID,
2048
        };
2049

2050
        UnitNameFlags valid_dst_type = UNIT_NAME_ANY;
1,590✔
2051
        const char *n;
1,590✔
2052
        int r, q;
1,590✔
2053

2054
        assert(info);
1,590✔
2055
        assert(lp);
1,590✔
2056
        assert(config_path);
1,590✔
2057

2058
        if (strv_isempty(list))
2,083✔
2059
                return 0;
2060

2061
        if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN | UNIT_NAME_INSTANCE))
493✔
2062
                /* Not a template unit. Use the name directly. */
2063
                n = info->name;
2064

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

2068
                r = unit_name_replace_instance(info->name, info->default_instance, &instance.name);
19✔
2069
                if (r < 0)
19✔
2070
                        return r;
2071

2072
                r = unit_file_search(NULL, &instance, lp, SEARCH_FOLLOW_CONFIG_SYMLINKS);
19✔
2073
                if (r < 0)
19✔
2074
                        return r;
2075

2076
                if (instance.install_mode == INSTALL_MODE_MASKED)
19✔
2077
                        return install_changes_add(changes, n_changes, -ERFKILL, instance.path, NULL);
×
2078

2079
                n = instance.name;
19✔
2080

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

2085
                valid_dst_type = UNIT_NAME_INSTANCE | UNIT_NAME_TEMPLATE;
2086
                n = info->name;
2087
        }
2088

2089
        r = 0;
493✔
2090
        STRV_FOREACH(s, list) {
1,025✔
2091
                _cleanup_free_ char *path = NULL, *dst = NULL;
532✔
2092

2093
                q = install_name_printf(scope, info, *s, &dst);
532✔
2094
                if (q < 0) {
532✔
2095
                        RET_GATHER(r, install_changes_add(changes, n_changes, q, *s, NULL));
×
2096
                        continue;
×
2097
                }
2098

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

2110
                        if (unit_name_is_valid(dst, UNIT_NAME_ANY))
×
2111
                                RET_GATHER(r, install_changes_add(changes, n_changes, -EIDRM, dst, n));
×
2112
                        else
2113
                                RET_GATHER(r, install_changes_add(changes, n_changes, -EUCLEAN, dst, NULL));
×
2114

2115
                        continue;
×
2116
                }
2117

2118
                path = strjoin(config_path, "/", dst, suffix, n);
532✔
2119
                if (!path)
532✔
2120
                        return -ENOMEM;
2121

2122
                q = create_symlink(lp, info->path, path, /* force= */ true, changes, n_changes);
532✔
2123
                if (q != 0 && r >= 0)
532✔
2124
                        r = q;
532✔
2125

2126
                if (unit_file_exists(scope, lp, dst) == 0) {
532✔
2127
                        q = install_changes_add(changes, n_changes, INSTALL_CHANGE_DESTINATION_NOT_PRESENT, dst, info->path);
1✔
2128
                        if (q < 0)
1✔
2129
                                return q;
2130
                }
2131
        }
2132

2133
        return r;
2134
}
2135

2136
static int install_info_symlink_link(
530✔
2137
                InstallInfo *info,
2138
                const LookupPaths *lp,
2139
                const char *config_path,
2140
                bool force,
2141
                InstallChange **changes,
2142
                size_t *n_changes) {
2143

2144
        _cleanup_free_ char *path = NULL;
530✔
2145
        int r;
530✔
2146

2147
        assert(info);
530✔
2148
        assert(lp);
530✔
2149
        assert(config_path);
530✔
2150
        assert(info->path);
530✔
2151

2152
        r = in_search_path(lp, info->path);
530✔
2153
        if (r < 0)
530✔
2154
                return r;
2155
        if (r > 0)
530✔
2156
                return 0;
2157

2158
        path = path_join(config_path, info->name);
3✔
2159
        if (!path)
3✔
2160
                return -ENOMEM;
2161

2162
        return create_symlink(lp, info->path, path, force, changes, n_changes);
3✔
2163
}
2164

2165
static int install_info_apply(
530✔
2166
                RuntimeScope scope,
2167
                UnitFileFlags file_flags,
2168
                InstallInfo *info,
2169
                const LookupPaths *lp,
2170
                const char *config_path,
2171
                InstallChange **changes,
2172
                size_t *n_changes) {
2173

2174
        int r, q;
530✔
2175

2176
        assert(info);
530✔
2177
        assert(lp);
530✔
2178
        assert(config_path);
530✔
2179

2180
        if (info->install_mode != INSTALL_MODE_REGULAR)
530✔
2181
                return 0;
2182

2183
        bool force = file_flags & UNIT_FILE_FORCE;
530✔
2184

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

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

2194
        q = install_info_symlink_wants(scope, file_flags, info, lp, config_path, info->wanted_by, ".wants/", changes, n_changes);
530✔
2195
        if (q != 0 && r >= 0)
530✔
2196
                r = q;
493✔
2197

2198
        q = install_info_symlink_wants(scope, file_flags, info, lp, config_path, info->required_by, ".requires/", changes, n_changes);
530✔
2199
        if (q != 0 && r >= 0)
530✔
2200
                r = q;
×
2201

2202
        q = install_info_symlink_wants(scope, file_flags, info, lp, config_path, info->upheld_by, ".upholds/", changes, n_changes);
530✔
2203
        if (q != 0 && r >= 0)
530✔
2204
                r = q;
×
2205

2206
        return r;
2207
}
2208

2209
static int install_context_apply(
54✔
2210
                InstallContext *ctx,
2211
                const LookupPaths *lp,
2212
                UnitFileFlags file_flags,
2213
                const char *config_path,
2214
                SearchFlags flags,
2215
                InstallChange **changes,
2216
                size_t *n_changes) {
2217

2218
        InstallInfo *i;
54✔
2219
        int r;
54✔
2220

2221
        assert(ctx);
54✔
2222
        assert(lp);
54✔
2223
        assert(config_path);
54✔
2224

2225
        if (ordered_hashmap_isempty(ctx->will_process))
54✔
2226
                return 0;
2227

2228
        r = ordered_hashmap_ensure_allocated(&ctx->have_processed, &install_info_hash_ops);
49✔
2229
        if (r < 0)
49✔
2230
                return r;
2231

2232
        r = 0;
2233
        while ((i = ordered_hashmap_first(ctx->will_process))) {
602✔
2234
                int q;
553✔
2235

2236
                q = ordered_hashmap_move_one(ctx->have_processed, ctx->will_process, i->name);
553✔
2237
                if (q < 0)
553✔
2238
                        return q;
2239

2240
                q = install_info_traverse(ctx, lp, i, flags, NULL);
553✔
2241
                if (q < 0) {
553✔
2242
                        if (i->auxiliary) {
×
2243
                                q = install_changes_add(changes, n_changes, INSTALL_CHANGE_AUXILIARY_FAILED, i->name, NULL);
×
2244
                                if (q < 0)
×
2245
                                        return q;
2246
                                continue;
×
2247
                        }
2248

2249
                        return install_changes_add(changes, n_changes, q, i->name, NULL);
×
2250
                }
2251

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

2265
                if (i->install_mode != INSTALL_MODE_REGULAR)
536✔
2266
                        continue;
6✔
2267

2268
                q = install_info_apply(ctx->scope, file_flags, i, lp, config_path, changes, n_changes);
530✔
2269
                if (r >= 0) {
530✔
2270
                        if (q < 0)
530✔
2271
                                r = q;
2272
                        else
2273
                                r += q;
530✔
2274
                }
2275
        }
2276

2277
        return r;
2278
}
2279

2280
static int install_context_mark_for_removal(
25✔
2281
                InstallContext *ctx,
2282
                const LookupPaths *lp,
2283
                Set **remove_symlinks_to,
2284
                const char *config_path,
2285
                InstallChange **changes,
2286
                size_t *n_changes) {
2287

2288
        InstallInfo *i;
25✔
2289
        int r;
25✔
2290

2291
        assert(ctx);
25✔
2292
        assert(lp);
25✔
2293
        assert(config_path);
25✔
2294

2295
        /* Marks all items for removal */
2296

2297
        if (ordered_hashmap_isempty(ctx->will_process))
25✔
2298
                return 0;
2299

2300
        r = ordered_hashmap_ensure_allocated(&ctx->have_processed, &install_info_hash_ops);
21✔
2301
        if (r < 0)
21✔
2302
                return r;
2303

2304
        while ((i = ordered_hashmap_first(ctx->will_process))) {
62✔
2305

2306
                r = ordered_hashmap_move_one(ctx->have_processed, ctx->will_process, i->name);
41✔
2307
                if (r < 0)
41✔
2308
                        return r;
2309

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

2344
                r = mark_symlink_for_removal(remove_symlinks_to, i->name);
39✔
2345
                if (r < 0)
39✔
2346
                        return r;
2347
        }
2348

2349
        return 0;
2350
}
2351

2352
int unit_file_mask(
5✔
2353
                RuntimeScope scope,
2354
                UnitFileFlags flags,
2355
                const char *root_dir,
2356
                char * const *names,
2357
                InstallChange **changes,
2358
                size_t *n_changes) {
2359

2360
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
5✔
2361
        const char *config_path;
5✔
2362
        int r;
5✔
2363

2364
        assert(scope >= 0);
5✔
2365
        assert(scope < _RUNTIME_SCOPE_MAX);
5✔
2366

2367
        r = lookup_paths_init(&lp, scope, 0, root_dir);
5✔
2368
        if (r < 0)
5✔
2369
                return r;
2370

2371
        config_path = (flags & UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
5✔
2372
        if (!config_path)
5✔
2373
                return -ENXIO;
2374

2375
        r = 0;
2376

2377
        STRV_FOREACH(name, names) {
10✔
2378
                _cleanup_free_ char *path = NULL;
5✔
2379

2380
                if (!unit_name_is_valid(*name, UNIT_NAME_ANY)) {
5✔
2381
                        RET_GATHER(r, -EINVAL);
×
2382
                        continue;
×
2383
                }
2384

2385
                path = path_make_absolute(*name, config_path);
5✔
2386
                if (!path)
5✔
2387
                        return -ENOMEM;
×
2388

2389
                RET_GATHER(r, create_symlink(&lp, "/dev/null", path, flags & UNIT_FILE_FORCE, changes, n_changes));
5✔
2390
        }
2391

2392
        return r;
2393
}
2394

2395
int unit_file_unmask(
5✔
2396
                RuntimeScope scope,
2397
                UnitFileFlags flags,
2398
                const char *root_dir,
2399
                char * const *names,
2400
                InstallChange **changes,
2401
                size_t *n_changes) {
2402

2403
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
5✔
2404
        _cleanup_set_free_ Set *remove_symlinks_to = NULL;
5✔
2405
        _cleanup_strv_free_ char **todo = NULL;
×
2406
        const char *config_path;
5✔
2407
        size_t n_todo = 0;
5✔
2408
        int r, q;
5✔
2409

2410
        assert(scope >= 0);
5✔
2411
        assert(scope < _RUNTIME_SCOPE_MAX);
5✔
2412

2413
        r = lookup_paths_init(&lp, scope, 0, root_dir);
5✔
2414
        if (r < 0)
5✔
2415
                return r;
2416

2417
        config_path = (flags & UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
5✔
2418
        if (!config_path)
5✔
2419
                return -ENXIO;
2420

2421
        bool dry_run = flags & UNIT_FILE_DRY_RUN;
5✔
2422

2423
        STRV_FOREACH(name, names) {
10✔
2424
                if (!unit_name_is_valid(*name, UNIT_NAME_ANY))
5✔
2425
                        return -EINVAL;
5✔
2426

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

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

2447
                        TAKE_PTR(info.name);  /* … and give it back here */
1✔
2448
                }
2449

2450
                _cleanup_free_ char *path = path_make_absolute(*name, config_path);
10✔
2451
                if (!path)
5✔
2452
                        return -ENOMEM;
2453

2454
                r = null_or_empty_path(path);
5✔
2455
                if (r == -ENOENT)
5✔
2456
                        continue;
2✔
2457
                if (r < 0)
3✔
2458
                        return r;
2459
                if (r == 0)
3✔
2460
                        continue;
×
2461

2462
                if (!GREEDY_REALLOC0(todo, n_todo + 2))
3✔
2463
                        return -ENOMEM;
2464

2465
                todo[n_todo] = strdup(*name);
3✔
2466
                if (!todo[n_todo])
3✔
2467
                        return -ENOMEM;
2468

2469
                n_todo++;
3✔
2470
        }
2471

2472
        strv_uniq(todo);
5✔
2473

2474
        r = 0;
5✔
2475
        STRV_FOREACH(i, todo) {
8✔
2476
                _cleanup_free_ char *path = NULL;
3✔
2477
                const char *rp;
3✔
2478

2479
                path = path_make_absolute(*i, config_path);
3✔
2480
                if (!path)
3✔
2481
                        return -ENOMEM;
2482

2483
                if (!dry_run && unlink(path) < 0) {
3✔
2484
                        if (errno != ENOENT)
×
2485
                                RET_GATHER(r, install_changes_add(changes, n_changes, -errno, path, NULL));
×
2486

2487
                        continue;
×
2488
                }
2489

2490
                q = install_changes_add(changes, n_changes, INSTALL_CHANGE_UNLINK, path, NULL);
3✔
2491
                if (q < 0)
3✔
2492
                        return q;
2493

2494
                rp = skip_root(lp.root_dir, path);
3✔
2495
                q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: path);
3✔
2496
                if (q < 0)
3✔
2497
                        return q;
2498
        }
2499

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

2502
        return r;
2503
}
2504

2505
int unit_file_link(
1✔
2506
                RuntimeScope scope,
2507
                UnitFileFlags flags,
2508
                const char *root_dir,
2509
                char * const *files,
2510
                InstallChange **changes,
2511
                size_t *n_changes) {
2512

2513
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
1✔
2514
        _cleanup_ordered_hashmap_free_ OrderedHashmap *todo = NULL;
1✔
2515
        const char *config_path;
1✔
2516
        int r;
1✔
2517

2518
        assert(scope >= 0);
1✔
2519
        assert(scope < _RUNTIME_SCOPE_MAX);
1✔
2520
        assert(changes);
1✔
2521
        assert(n_changes);
1✔
2522

2523
        r = lookup_paths_init(&lp, scope, 0, root_dir);
1✔
2524
        if (r < 0)
1✔
2525
                return r;
2526

2527
        config_path = FLAGS_SET(flags, UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
1✔
2528
        if (!config_path)
1✔
2529
                return -ENXIO;
2530

2531
        STRV_FOREACH(file, files) {
2✔
2532
                _cleanup_free_ char *fn = NULL, *path = NULL, *full = NULL;
1✔
2533

2534
                if (ordered_hashmap_contains(todo, *file))
1✔
2535
                        continue;
×
2536

2537
                if (!path_is_absolute(*file))
1✔
2538
                        return install_changes_add(changes, n_changes, -EINVAL, *file, NULL);
×
2539

2540
                r = path_extract_filename(*file, &fn);
1✔
2541
                if (r < 0)
1✔
2542
                        return install_changes_add(changes, n_changes, r, *file, NULL);
×
2543

2544
                if (!unit_name_is_valid(fn, UNIT_NAME_ANY))
1✔
2545
                        return install_changes_add(changes, n_changes, -EUCLEAN, *file, NULL);
×
2546

2547
                full = path_join(lp.root_dir, *file);
1✔
2548
                if (!full)
1✔
2549
                        return -ENOMEM;
2550

2551
                r = verify_regular_at(AT_FDCWD, full, /* follow= */ false);
1✔
2552
                if (r < 0)
1✔
2553
                        return install_changes_add(changes, n_changes, r, *file, NULL);
×
2554

2555
                r = in_search_path(&lp, *file);
1✔
2556
                if (r < 0)
1✔
2557
                        return install_changes_add(changes, n_changes, r, *file, NULL);
×
2558
                if (r > 0)
1✔
2559
                        /* A silent noop if the file is already in the search path. */
2560
                        continue;
×
2561

2562
                if (underneath_search_path(&lp, *file))
1✔
2563
                        return install_changes_add(changes, n_changes, -ETXTBSY, *file, NULL);
×
2564

2565
                path = strdup(*file);
1✔
2566
                if (!path)
1✔
2567
                        return -ENOMEM;
2568

2569
                r = ordered_hashmap_ensure_put(&todo, &path_hash_ops_free_free, path, fn);
1✔
2570
                if (r < 0)
1✔
2571
                        return r;
2572
                assert(r > 0);
1✔
2573

2574
                TAKE_PTR(path);
1✔
2575
                TAKE_PTR(fn);
1✔
2576
        }
2577

2578
        r = 0;
1✔
2579

2580
        const char *fn, *path;
1✔
2581
        ORDERED_HASHMAP_FOREACH_KEY(fn, path, todo) {
2✔
2582
                _cleanup_free_ char *new_path = NULL;
1✔
2583

2584
                new_path = path_make_absolute(fn, config_path);
1✔
2585
                if (!new_path)
1✔
2586
                        return -ENOMEM;
×
2587

2588
                RET_GATHER(r, create_symlink(&lp, path, new_path, FLAGS_SET(flags, UNIT_FILE_FORCE), changes, n_changes));
1✔
2589
        }
2590

2591
        return r;
1✔
2592
}
2593

2594
static int path_shall_revert(const LookupPaths *lp, const char *path) {
1✔
2595
        int r;
1✔
2596

2597
        assert(lp);
1✔
2598
        assert(path);
1✔
2599

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

2602
        r = path_is_config(lp, path, true);
1✔
2603
        if (r != 0)
1✔
2604
                return r;
2605

2606
        r = path_is_control(lp, path);
×
2607
        if (r != 0)
×
2608
                return r;
2609

2610
        return path_is_transient(lp, path);
×
2611
}
2612

2613
int unit_file_revert(
4✔
2614
                RuntimeScope scope,
2615
                const char *root_dir,
2616
                char * const *names,
2617
                InstallChange **changes,
2618
                size_t *n_changes) {
2619

2620
        _cleanup_set_free_ Set *remove_symlinks_to = NULL;
4✔
2621
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
4✔
2622
        _cleanup_strv_free_ char **todo = NULL;
4✔
2623
        size_t n_todo = 0;
4✔
2624
        int r, q;
4✔
2625

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

2638
        r = lookup_paths_init(&lp, scope, 0, root_dir);
4✔
2639
        if (r < 0)
4✔
2640
                return r;
2641

2642
        STRV_FOREACH(name, names) {
8✔
2643
                bool has_vendor = false;
4✔
2644

2645
                if (!unit_name_is_valid(*name, UNIT_NAME_ANY))
4✔
2646
                        return -EINVAL;
2647

2648
                STRV_FOREACH(p, lp.search_path) {
52✔
2649
                        _cleanup_free_ char *path = NULL, *dropin = NULL;
48✔
2650
                        struct stat st;
48✔
2651

2652
                        path = path_make_absolute(*name, *p);
48✔
2653
                        if (!path)
48✔
2654
                                return -ENOMEM;
2655

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

2669
                        dropin = strjoin(path, ".d");
48✔
2670
                        if (!dropin)
48✔
2671
                                return -ENOMEM;
2672

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

2686
                                        todo[n_todo++] = TAKE_PTR(dropin);
1✔
2687
                                }
2688
                        }
2689
                }
2690

2691
                if (!has_vendor)
4✔
2692
                        continue;
×
2693

2694
                /* OK, there's a vendor version, hence drop all configuration versions */
2695
                STRV_FOREACH(p, lp.search_path) {
52✔
2696
                        _cleanup_free_ char *path = NULL;
48✔
2697
                        struct stat st;
48✔
2698

2699
                        path = path_make_absolute(*name, *p);
48✔
2700
                        if (!path)
48✔
2701
                                return -ENOMEM;
2702

2703
                        r = RET_NERRNO(lstat(path, &st));
48✔
2704
                        if (r < 0) {
42✔
2705
                                if (r != -ENOENT)
42✔
2706
                                        return install_changes_add(changes, n_changes, r, path, NULL);
×
2707
                        } else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
6✔
2708
                                r = path_is_config(&lp, path, true);
6✔
2709
                                if (r < 0)
6✔
2710
                                        return install_changes_add(changes, n_changes, r, path, NULL);
×
2711
                                if (r > 0) {
6✔
2712
                                        if (!GREEDY_REALLOC0(todo, n_todo + 2))
2✔
2713
                                                return -ENOMEM;
2714

2715
                                        todo[n_todo++] = TAKE_PTR(path);
2✔
2716
                                }
2717
                        }
2718
                }
2719
        }
2720

2721
        strv_uniq(todo);
4✔
2722

2723
        r = 0;
4✔
2724
        STRV_FOREACH(i, todo) {
7✔
2725
                _cleanup_strv_free_ char **fs = NULL;
3✔
2726
                const char *rp;
3✔
2727

2728
                (void) get_files_in_directory(*i, &fs);
3✔
2729

2730
                q = rm_rf(*i, REMOVE_ROOT|REMOVE_PHYSICAL);
3✔
2731
                if (q < 0 && q != -ENOENT && r >= 0) {
3✔
2732
                        r = q;
×
2733
                        continue;
×
2734
                }
2735

2736
                STRV_FOREACH(j, fs) {
4✔
2737
                        _cleanup_free_ char *t = NULL;
1✔
2738

2739
                        t = path_join(*i, *j);
1✔
2740
                        if (!t)
1✔
2741
                                return -ENOMEM;
2742

2743
                        q = install_changes_add(changes, n_changes, INSTALL_CHANGE_UNLINK, t, NULL);
1✔
2744
                        if (q < 0)
1✔
2745
                                return q;
2746
                }
2747

2748
                q = install_changes_add(changes, n_changes, INSTALL_CHANGE_UNLINK, *i, NULL);
3✔
2749
                if (q < 0)
3✔
2750
                        return q;
2751

2752
                rp = skip_root(lp.root_dir, *i);
3✔
2753
                q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: *i);
3✔
2754
                if (q < 0)
3✔
2755
                        return q;
2756
        }
2757

2758
        q = remove_marked_symlinks(remove_symlinks_to, lp.runtime_config, &lp, false, changes, n_changes);
4✔
2759
        if (r >= 0)
4✔
2760
                r = q;
4✔
2761

2762
        q = remove_marked_symlinks(remove_symlinks_to, lp.persistent_config, &lp, false, changes, n_changes);
4✔
2763
        if (r >= 0)
4✔
2764
                r = q;
4✔
2765

2766
        return r;
2767
}
2768

2769
int unit_file_add_dependency(
1✔
2770
                RuntimeScope scope,
2771
                UnitFileFlags file_flags,
2772
                const char *root_dir,
2773
                char * const *names,
2774
                const char *target,
2775
                UnitDependency dep,
2776
                InstallChange **changes,
2777
                size_t *n_changes) {
2778

2779
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
1✔
2780
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
1✔
2781
        InstallInfo *info, *target_info;
1✔
2782
        const char *config_path;
1✔
2783
        int r;
1✔
2784

2785
        assert(scope >= 0);
1✔
2786
        assert(scope < _RUNTIME_SCOPE_MAX);
1✔
2787
        assert(target);
1✔
2788
        assert(IN_SET(dep, UNIT_WANTS, UNIT_REQUIRES));
1✔
2789

2790
        if (!unit_name_is_valid(target, UNIT_NAME_ANY))
1✔
2791
                return install_changes_add(changes, n_changes, -EUCLEAN, target, NULL);
×
2792

2793
        r = lookup_paths_init(&lp, scope, 0, root_dir);
1✔
2794
        if (r < 0)
1✔
2795
                return r;
2796

2797
        config_path = (file_flags & UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
1✔
2798
        if (!config_path)
1✔
2799
                return -ENXIO;
2800

2801
        r = install_info_discover_and_check(&ctx, &lp, target, SEARCH_FOLLOW_CONFIG_SYMLINKS,
1✔
2802
                                            &target_info, changes, n_changes);
2803
        if (r < 0)
1✔
2804
                return r;
2805

2806
        assert(target_info->install_mode == INSTALL_MODE_REGULAR);
1✔
2807

2808
        STRV_FOREACH(name, names) {
2✔
2809
                char ***l;
1✔
2810

2811
                r = install_info_discover_and_check(&ctx, &lp, *name,
1✔
2812
                                                    SEARCH_FOLLOW_CONFIG_SYMLINKS,
2813
                                                    &info, changes, n_changes);
2814
                if (r < 0)
1✔
2815
                        return r;
2816

2817
                assert(info->install_mode == INSTALL_MODE_REGULAR);
1✔
2818

2819
                /* We didn't actually load anything from the unit
2820
                 * file, but instead just add in our new symlink to
2821
                 * create. */
2822

2823
                if (dep == UNIT_WANTS)
1✔
2824
                        l = &info->wanted_by;
1✔
2825
                else if (dep == UNIT_REQUIRES)
×
2826
                        l = &info->required_by;
×
2827
                else
2828
                        l = &info->upheld_by;
×
2829

2830
                strv_free(*l);
1✔
2831
                *l = strv_new(target_info->name);
1✔
2832
                if (!*l)
1✔
2833
                        return -ENOMEM;
2834
        }
2835

2836
        return install_context_apply(&ctx, &lp, file_flags, config_path,
1✔
2837
                                     SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes);
2838
}
2839

2840
static int do_unit_file_enable(
27✔
2841
                const LookupPaths *lp,
2842
                RuntimeScope scope,
2843
                UnitFileFlags flags,
2844
                const char *config_path,
2845
                char * const *names_or_paths,
2846
                InstallChange **changes,
2847
                size_t *n_changes) {
2848

2849
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
27✔
2850
        InstallInfo *info;
27✔
2851
        int r;
27✔
2852

2853
        STRV_FOREACH(name, names_or_paths) {
53✔
2854
                r = install_info_discover_and_check(&ctx, lp, *name,
27✔
2855
                                                    SEARCH_LOAD | SEARCH_FOLLOW_CONFIG_SYMLINKS,
2856
                                                    &info, changes, n_changes);
2857
                if (r < 0)
27✔
2858
                        return r;
2859

2860
                assert(info->install_mode == INSTALL_MODE_REGULAR);
26✔
2861
        }
2862

2863
        /* This will return the number of symlink rules that were
2864
           supposed to be created, not the ones actually created. This
2865
           is useful to determine whether the passed units had any
2866
           installation data at all. */
2867

2868
        return install_context_apply(&ctx, lp, flags, config_path,
26✔
2869
                                     SEARCH_LOAD, changes, n_changes);
2870
}
2871

2872
int unit_file_enable(
25✔
2873
                RuntimeScope scope,
2874
                UnitFileFlags flags,
2875
                const char *root_dir,
2876
                char * const *names_or_paths,
2877
                InstallChange **changes,
2878
                size_t *n_changes) {
2879

2880
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
25✔
2881
        int r;
25✔
2882

2883
        assert(scope >= 0);
25✔
2884
        assert(scope < _RUNTIME_SCOPE_MAX);
25✔
2885

2886
        r = lookup_paths_init(&lp, scope, 0, root_dir);
25✔
2887
        if (r < 0)
25✔
2888
                return r;
2889

2890
        const char *config_path = config_path_from_flags(&lp, flags);
25✔
2891
        if (!config_path)
25✔
2892
                return -ENXIO;
2893

2894
        return do_unit_file_enable(&lp, scope, flags, config_path, names_or_paths, changes, n_changes);
25✔
2895
}
2896

2897
static int do_unit_file_disable(
15✔
2898
                const LookupPaths *lp,
2899
                RuntimeScope scope,
2900
                UnitFileFlags flags,
2901
                const char *config_path,
2902
                char * const *names,
2903
                InstallChange **changes,
2904
                size_t *n_changes) {
2905

2906
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
15✔
2907
        bool has_install_info = false;
15✔
2908
        int r;
15✔
2909

2910
        STRV_FOREACH(name, names) {
30✔
2911
                InstallInfo *info;
15✔
2912

2913
                if (!unit_name_is_valid(*name, UNIT_NAME_ANY))
15✔
2914
                        return install_changes_add(changes, n_changes, -EUCLEAN, *name, NULL);
×
2915

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

2927
                /* If we enable multiple units, some with install info and others without,
2928
                 * the "empty [Install] section" warning is not shown. Let's make the behavior
2929
                 * of disable align with that. */
2930
                has_install_info = has_install_info || install_info_has_rules(info) || install_info_has_also(info);
15✔
2931
        }
2932

2933
        _cleanup_set_free_ Set *remove_symlinks_to = NULL;
15✔
2934
        r = install_context_mark_for_removal(&ctx, lp, &remove_symlinks_to, config_path, changes, n_changes);
15✔
2935
        if (r >= 0)
15✔
2936
                r = remove_marked_symlinks(remove_symlinks_to, config_path, lp, flags & UNIT_FILE_DRY_RUN, changes, n_changes);
15✔
2937
        if (r < 0)
15✔
2938
                return r;
×
2939

2940
        /* The warning is shown only if it's a no-op */
2941
        return install_changes_have_modification(*changes, *n_changes) || has_install_info;
15✔
2942
}
2943

2944
int unit_file_disable(
13✔
2945
                RuntimeScope scope,
2946
                UnitFileFlags flags,
2947
                const char *root_dir,
2948
                char * const *files,
2949
                InstallChange **changes,
2950
                size_t *n_changes) {
2951

2952
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
13✔
2953
        int r;
13✔
2954

2955
        assert(scope >= 0);
13✔
2956
        assert(scope < _RUNTIME_SCOPE_MAX);
13✔
2957

2958
        r = lookup_paths_init(&lp, scope, 0, root_dir);
13✔
2959
        if (r < 0)
13✔
2960
                return r;
2961

2962
        const char *config_path = config_path_from_flags(&lp, flags);
13✔
2963
        if (!config_path)
13✔
2964
                return -ENXIO;
2965

2966
        return do_unit_file_disable(&lp, scope, flags, config_path, files, changes, n_changes);
13✔
2967
}
2968

2969
static int normalize_linked_files(
2✔
2970
                RuntimeScope scope,
2971
                const LookupPaths *lp,
2972
                char * const *names_or_paths,
2973
                char ***ret_names,
2974
                char ***ret_files) {
2975

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

2980
        _cleanup_strv_free_ char **files = NULL, **names = NULL;
2✔
2981
        int r;
2✔
2982

2983
        STRV_FOREACH(a, names_or_paths) {
4✔
2984
                _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
×
2985
                InstallInfo *i = NULL;
2✔
2986
                _cleanup_free_ char *n = NULL;
2✔
2987

2988
                r = path_extract_filename(*a, &n);
2✔
2989
                if (r < 0)
2✔
2990
                        return r;
2991
                if (r == O_DIRECTORY)
2✔
2992
                        return log_debug_errno(SYNTHETIC_ERRNO(EISDIR),
×
2993
                                               "Unexpected path to a directory \"%s\", refusing.", *a);
2994

2995
                if (!is_path(*a) && !unit_name_is_valid(*a, UNIT_NAME_INSTANCE)) {
2✔
2996
                        r = install_info_discover(&ctx, lp, n, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i, NULL, NULL);
2✔
2997
                        if (r < 0)
2✔
2998
                                log_debug_errno(r, "Failed to discover unit \"%s\", operating on name: %m", n);
×
2999
                }
3000

3001
                r = strv_consume(&names, TAKE_PTR(n));
2✔
3002
                if (r < 0)
2✔
3003
                        return r;
3004

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

3017
                r = strv_extend(&files, p ?: *a);
2✔
3018
                if (r < 0)
2✔
3019
                        return r;
3020
        }
3021

3022
        *ret_names = TAKE_PTR(names);
2✔
3023
        *ret_files = TAKE_PTR(files);
2✔
3024
        return 0;
2✔
3025
}
3026

3027
int unit_file_reenable(
2✔
3028
                RuntimeScope scope,
3029
                UnitFileFlags flags,
3030
                const char *root_dir,
3031
                char * const *names_or_paths,
3032
                InstallChange **changes,
3033
                size_t *n_changes) {
3034

3035
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
2✔
3036
        _cleanup_strv_free_ char **names = NULL, **files = NULL;
2✔
3037
        int r;
2✔
3038

3039
        assert(scope >= 0);
2✔
3040
        assert(scope < _RUNTIME_SCOPE_MAX);
2✔
3041

3042
        r = lookup_paths_init(&lp, scope, 0, root_dir);
2✔
3043
        if (r < 0)
2✔
3044
                return r;
3045

3046
        const char *config_path = config_path_from_flags(&lp, flags);
2✔
3047
        if (!config_path)
2✔
3048
                return -ENXIO;
3049

3050
        r = normalize_linked_files(scope, &lp, names_or_paths, &names, &files);
2✔
3051
        if (r < 0)
2✔
3052
                return r;
3053

3054
        /* First, we invoke the disable command with only the basename... */
3055
        r = do_unit_file_disable(&lp, scope, flags, config_path, names, changes, n_changes);
2✔
3056
        if (r < 0)
2✔
3057
                return r;
3058

3059
        /* But the enable command with the full name */
3060
        return do_unit_file_enable(&lp, scope, flags, config_path, files, changes, n_changes);
2✔
3061
}
3062

3063
int unit_file_set_default(
4✔
3064
                RuntimeScope scope,
3065
                UnitFileFlags flags,
3066
                const char *root_dir,
3067
                const char *name,
3068
                InstallChange **changes,
3069
                size_t *n_changes) {
3070

3071
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
4✔
3072
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
4✔
3073
        InstallInfo *info;
4✔
3074
        const char *new_path;
4✔
3075
        int r;
4✔
3076

3077
        assert(scope >= 0);
4✔
3078
        assert(scope < _RUNTIME_SCOPE_MAX);
4✔
3079
        assert(name);
4✔
3080

3081
        if (unit_name_to_type(name) != UNIT_TARGET) /* this also validates the name */
4✔
3082
                return -EINVAL;
3083
        if (streq(name, SPECIAL_DEFAULT_TARGET))
4✔
3084
                return -EINVAL;
3085

3086
        r = lookup_paths_init(&lp, scope, 0, root_dir);
4✔
3087
        if (r < 0)
4✔
3088
                return r;
3089

3090
        r = install_info_discover_and_check(&ctx, &lp, name, 0, &info, changes, n_changes);
4✔
3091
        if (r < 0)
4✔
3092
                return r;
3093

3094
        new_path = strjoina(lp.persistent_config, "/" SPECIAL_DEFAULT_TARGET);
15✔
3095
        return create_symlink(&lp, info->path, new_path, flags & UNIT_FILE_FORCE, changes, n_changes);
3✔
3096
}
3097

3098
int unit_file_get_default(
9✔
3099
                RuntimeScope scope,
3100
                const char *root_dir,
3101
                char **ret) {
3102

3103
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
9✔
3104
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
9✔
3105
        InstallInfo *info;
9✔
3106
        int r;
9✔
3107

3108
        assert(scope >= 0);
9✔
3109
        assert(scope < _RUNTIME_SCOPE_MAX);
9✔
3110
        assert(ret);
9✔
3111

3112
        r = lookup_paths_init(&lp, scope, 0, root_dir);
9✔
3113
        if (r < 0)
9✔
3114
                return r;
3115

3116
        r = install_info_discover(&ctx, &lp, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS,
9✔
3117
                                  &info, NULL, NULL);
3118
        if (r < 0)
9✔
3119
                return r;
3120

3121
        return strdup_to(ret, info->name);
7✔
3122
}
3123

3124
int unit_file_lookup_state(
2,587✔
3125
                RuntimeScope scope,
3126
                const LookupPaths *lp,
3127
                const char *name,
3128
                UnitFileState *ret) {
3129

3130
        _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
2,587✔
3131
        InstallInfo *info;
2,587✔
3132
        UnitFileState state;
2,587✔
3133
        int r;
2,587✔
3134

3135
        assert(lp);
2,587✔
3136
        assert(name);
2,587✔
3137

3138
        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
2,587✔
3139
                return -EINVAL;
3140

3141
        r = install_info_discover(&ctx, lp, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
2,587✔
3142
                                  &info, NULL, NULL);
3143
        if (r < 0)
2,587✔
3144
                return log_debug_errno(r, "Failed to discover unit %s: %m", name);
38✔
3145

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

3150
        /* Shortcut things, if the caller just wants to know if this unit exists. */
3151
        if (!ret)
2,549✔
3152
                return 0;
3153

3154
        switch (info->install_mode) {
2,541✔
3155

3156
        case INSTALL_MODE_MASKED:
18✔
3157
                r = path_is_runtime(lp, info->path, true);
18✔
3158
                if (r < 0)
18✔
3159
                        return r;
3160

3161
                state = r > 0 ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
18✔
3162
                break;
18✔
3163

3164
        case INSTALL_MODE_REGULAR:
2,523✔
3165
                /* Check if the name we were querying is actually an alias */
3166
                if (!path_equal_filename(name, info->path) && !unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
2,523✔
3167
                        state = UNIT_FILE_ALIAS;
72✔
3168
                        break;
72✔
3169
                }
3170

3171
                r = path_is_generator(lp, info->path);
2,451✔
3172
                if (r < 0)
2,451✔
3173
                        return r;
3174
                if (r > 0) {
2,451✔
3175
                        state = UNIT_FILE_GENERATED;
9✔
3176
                        break;
9✔
3177
                }
3178

3179
                r = path_is_transient(lp, info->path);
2,442✔
3180
                if (r < 0)
2,442✔
3181
                        return r;
3182
                if (r > 0) {
2,442✔
3183
                        state = UNIT_FILE_TRANSIENT;
14✔
3184
                        break;
14✔
3185
                }
3186

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

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

3212
                break;
3213

3214
        default:
×
3215
                assert_not_reached();
×
3216
        }
3217

3218
        *ret = state;
2,541✔
3219
        return 0;
2,541✔
3220
}
3221

3222
int unit_file_get_state(
478✔
3223
                RuntimeScope scope,
3224
                const char *root_dir,
3225
                const char *name,
3226
                UnitFileState *ret) {
3227

3228
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
478✔
3229
        int r;
478✔
3230

3231
        assert(scope >= 0);
478✔
3232
        assert(scope < _RUNTIME_SCOPE_MAX);
478✔
3233
        assert(name);
478✔
3234

3235
        r = lookup_paths_init(&lp, scope, 0, root_dir);
478✔
3236
        if (r < 0)
478✔
3237
                return r;
3238

3239
        return unit_file_lookup_state(scope, &lp, name, ret);
478✔
3240
}
3241

3242
int unit_file_exists_full(
1,486✔
3243
                RuntimeScope scope,
3244
                const LookupPaths *lp,
3245
                bool follow,
3246
                const char *name,
3247
                char **ret_path) {
3248

3249
        _cleanup_(install_context_done) InstallContext c = {
1,486✔
3250
                .scope = scope,
3251
        };
3252
        int r;
1,486✔
3253

3254
        assert(lp);
1,486✔
3255
        assert(name);
1,486✔
3256

3257
        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
1,486✔
3258
                return -EINVAL;
3259

3260
        InstallInfo *info = NULL;
1,486✔
3261
        r = install_info_discover(
4,458✔
3262
                        &c,
3263
                        lp,
3264
                        name,
3265
                        follow ? SEARCH_FOLLOW_CONFIG_SYMLINKS : 0,
3266
                        ret_path ? &info : NULL,
3267
                        /* changes= */ NULL,
3268
                        /* n_changes= */ NULL);
3269
        if (r == -ENOENT) {
1,486✔
3270
                if (ret_path)
955✔
UNCOV
3271
                        *ret_path = NULL;
×
3272
                return 0;
955✔
3273
        }
3274
        if (r < 0)
531✔
3275
                return r;
3276

3277
        if (ret_path) {
531✔
UNCOV
3278
                assert(info);
×
3279

UNCOV
3280
                r = strdup_to(ret_path, info->path);
×
UNCOV
3281
                if (r < 0)
×
UNCOV
3282
                        return r;
×
3283
        }
3284

3285
        return 1;
3286
}
3287

3288
static int split_pattern_into_name_and_instances(const char *pattern, char **out_unit_name, char ***out_instances) {
7,440✔
UNCOV
3289
        _cleanup_strv_free_ char **instances = NULL;
×
3290
        _cleanup_free_ char *unit_name = NULL;
7,440✔
3291
        int r;
7,440✔
3292

3293
        assert(pattern);
7,440✔
3294
        assert(out_instances);
7,440✔
3295
        assert(out_unit_name);
7,440✔
3296

3297
        r = extract_first_word(&pattern, &unit_name, NULL, EXTRACT_RETAIN_ESCAPE);
7,440✔
3298
        if (r < 0)
7,440✔
3299
                return r;
3300

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

3309
                instances = strv_split(pattern, WHITESPACE);
2✔
3310
                if (!instances)
2✔
3311
                        return -ENOMEM;
3312

3313
                *out_instances = TAKE_PTR(instances);
2✔
3314
        }
3315

3316
        *out_unit_name = TAKE_PTR(unit_name);
7,440✔
3317

3318
        return 0;
7,440✔
3319
}
3320

3321
static int presets_find_config(RuntimeScope scope, const char *root_dir, char ***ret) {
290✔
3322
        static const char* const initrd_dirs[] = { CONF_PATHS("systemd/initrd-preset"), NULL };
290✔
3323
        static const char* const system_dirs[] = { CONF_PATHS("systemd/system-preset"), NULL };
290✔
3324
        static const char* const user_dirs[] = { CONF_PATHS("systemd/user-preset"), NULL };
290✔
3325
        const char* const* dirs;
290✔
3326
        int r;
290✔
3327

3328
        assert(scope >= 0);
290✔
3329
        assert(scope < _RUNTIME_SCOPE_MAX);
290✔
3330

3331
        if (scope == RUNTIME_SCOPE_SYSTEM) {
290✔
3332
                r = chase_and_access("/etc/initrd-release", root_dir, CHASE_PREFIX_ROOT, F_OK, /* ret_path= */ NULL);
253✔
3333
                if (r < 0 && r != -ENOENT)
253✔
3334
                        return r;
3335

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

3349
                dirs = system_dirs;
3350
        } else if (IN_SET(scope, RUNTIME_SCOPE_GLOBAL, RUNTIME_SCOPE_USER))
37✔
3351
                dirs = user_dirs;
3352
        else
UNCOV
3353
                assert_not_reached();
×
3354

3355
        return conf_files_list_strv(ret, ".preset", root_dir, 0, dirs);
290✔
3356
}
3357

3358
static int read_presets(RuntimeScope scope, const char *root_dir, UnitFilePresets *presets) {
290✔
3359
        _cleanup_(unit_file_presets_done) UnitFilePresets ps = {};
290✔
UNCOV
3360
        _cleanup_strv_free_ char **files = NULL;
×
3361
        int r;
290✔
3362

3363
        assert(scope >= 0);
290✔
3364
        assert(scope < _RUNTIME_SCOPE_MAX);
290✔
3365
        assert(presets);
290✔
3366

3367
        r = presets_find_config(scope, root_dir, &files);
290✔
3368
        if (r < 0)
290✔
3369
                return r;
3370

3371
        STRV_FOREACH(p, files) {
1,322✔
3372
                _cleanup_fclose_ FILE *f = NULL;
1,322✔
3373
                int n = 0;
1,032✔
3374

3375
                f = fopen(*p, "re");
1,032✔
3376
                if (!f) {
1,032✔
UNCOV
3377
                        if (errno == ENOENT)
×
UNCOV
3378
                                continue;
×
3379

UNCOV
3380
                        return -errno;
×
3381
                }
3382

3383
                for (;;) {
33,237✔
3384
                        _cleanup_free_ char *line = NULL;
33,237✔
3385
                        _cleanup_(unit_file_preset_rule_done) UnitFilePresetRule rule = {};
33,237✔
3386
                        const char *parameter;
33,237✔
3387

3388
                        r = read_stripped_line(f, LONG_LINE_MAX, &line);
33,237✔
3389
                        if (r < 0)
33,237✔
3390
                                return r;
3391
                        if (r == 0)
33,237✔
3392
                                break;
3393

3394
                        n++;
32,205✔
3395

3396
                        if (isempty(line))
32,205✔
3397
                                continue;
6,450✔
3398
                        if (strchr(COMMENTS, line[0]))
25,755✔
3399
                                continue;
8,982✔
3400

3401
                        if ((parameter = first_word(line, "enable"))) {
16,773✔
3402
                                char *unit_name;
7,440✔
3403
                                char **instances = NULL;
7,440✔
3404

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

3412
                                rule = (UnitFilePresetRule) {
7,440✔
3413
                                        .pattern = unit_name,
3414
                                        .action = PRESET_ENABLE,
3415
                                        .instances = instances,
3416
                                };
3417

3418
                        } else if ((parameter = first_word(line, "disable"))) {
9,333✔
3419
                                char *pattern;
9,325✔
3420

3421
                                pattern = strdup(parameter);
9,325✔
3422
                                if (!pattern)
9,325✔
3423
                                        return -ENOMEM;
3424

3425
                                rule = (UnitFilePresetRule) {
9,325✔
3426
                                        .pattern = pattern,
3427
                                        .action = PRESET_DISABLE,
3428
                                };
3429

3430
                        } else if ((parameter = first_word(line, "ignore"))) {
8✔
3431
                                char *pattern;
4✔
3432

3433
                                pattern = strdup(parameter);
4✔
3434
                                if (!pattern)
4✔
3435
                                        return -ENOMEM;
3436

3437
                                rule = (UnitFilePresetRule) {
4✔
3438
                                        .pattern = pattern,
3439
                                        .action = PRESET_IGNORE,
3440
                                };
3441
                        }
3442

3443
                        if (rule.action != 0) {
16,773✔
3444
                                if (!GREEDY_REALLOC(ps.rules, ps.n_rules + 1))
16,769✔
3445
                                        return -ENOMEM;
3446

3447
                                ps.rules[ps.n_rules++] = TAKE_STRUCT(rule);
16,769✔
3448
                                continue;
16,769✔
3449
                        }
3450

3451
                        log_syntax(NULL, LOG_WARNING, *p, n, 0, "Couldn't parse line '%s'. Ignoring.", line);
4✔
3452
                }
3453
        }
3454

3455
        ps.initialized = true;
290✔
3456
        *presets = TAKE_STRUCT(ps);
290✔
3457

3458
        return 0;
290✔
3459
}
3460

3461
static int pattern_match_multiple_instances(
592,638✔
3462
                        const UnitFilePresetRule rule,
3463
                        const char *unit_name,
3464
                        char ***ret) {
3465

3466
        _cleanup_free_ char *templated_name = NULL;
592,638✔
3467
        int r;
592,638✔
3468

3469
        assert(unit_name);
592,638✔
3470

3471
        /* If no ret is needed or the rule itself does not have instances
3472
         * initialized, we return not matching */
3473
        if (!ret || !rule.instances)
592,638✔
3474
                return 0;
3475

3476
        r = unit_name_template(unit_name, &templated_name);
4✔
3477
        if (r < 0)
4✔
3478
                return r;
3479
        if (!streq(rule.pattern, templated_name))
2✔
3480
                return 0;
3481

3482
        /* Compose a list of specified instances when unit name is a template  */
3483
        if (unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
2✔
3484
                _cleanup_strv_free_ char **out_strv = NULL;
1✔
3485

3486
                STRV_FOREACH(iter, rule.instances) {
4✔
3487
                        _cleanup_free_ char *name = NULL;
3✔
3488

3489
                        r = unit_name_replace_instance(unit_name, *iter, &name);
3✔
3490
                        if (r < 0)
3✔
3491
                                return r;
3492

3493
                        r = strv_consume(&out_strv, TAKE_PTR(name));
3✔
3494
                        if (r < 0)
3✔
3495
                                return r;
3496
                }
3497

3498
                *ret = TAKE_PTR(out_strv);
1✔
3499
                return 1;
1✔
3500
        } else {
3501
                /* We now know the input unit name is an instance name */
3502
                _cleanup_free_ char *instance_name = NULL;
1✔
3503

3504
                r = unit_name_to_instance(unit_name, &instance_name);
1✔
3505
                if (r < 0)
1✔
3506
                        return r;
3507

3508
                if (strv_find(rule.instances, instance_name))
1✔
3509
                        return 1;
3510
        }
UNCOV
3511
        return 0;
×
3512
}
3513

3514
static int query_presets(const char *name, const UnitFilePresets *presets, char ***instance_name_list) {
9,405✔
3515
        PresetAction action = PRESET_UNKNOWN;
9,405✔
3516

3517
        assert(name);
9,405✔
3518
        assert(presets);
9,405✔
3519

3520
        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
9,405✔
3521
                return -EINVAL;
3522

3523
        FOREACH_ARRAY(i, presets->rules, presets->n_rules)
592,673✔
3524
                if (pattern_match_multiple_instances(*i, name, instance_name_list) > 0 ||
1,185,274✔
3525
                    fnmatch(i->pattern, name, FNM_NOESCAPE) == 0) {
592,636✔
3526
                        action = i->action;
9,370✔
3527
                        break;
9,370✔
3528
                }
3529

3530
        switch (action) {
9,405✔
3531

3532
        case PRESET_UNKNOWN:
3533
                log_debug("Preset files don't specify rule for %s. Enabling.", name);
35✔
3534
                return PRESET_ENABLE;
3535

3536
        case PRESET_ENABLE:
490✔
3537
                if (instance_name_list && *instance_name_list)
490✔
3538
                        STRV_FOREACH(s, *instance_name_list)
4✔
3539
                                log_debug("Preset files say enable %s.", *s);
3✔
3540
                else
3541
                        log_debug("Preset files say enable %s.", name);
489✔
3542
                return PRESET_ENABLE;
3543

3544
        case PRESET_DISABLE:
3545
                log_debug("Preset files say disable %s.", name);
8,878✔
3546
                return PRESET_DISABLE;
3547

3548
        case PRESET_IGNORE:
3549
                log_debug("Preset files say ignore %s.", name);
2✔
3550
                return PRESET_IGNORE;
3551

UNCOV
3552
        default:
×
UNCOV
3553
                assert_not_reached();
×
3554
        }
3555
}
3556

3557
PresetAction unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached) {
570✔
3558
        _cleanup_(unit_file_presets_done) UnitFilePresets tmp = {};
570✔
3559
        int r;
570✔
3560

3561
        if (!cached)
570✔
3562
                cached = &tmp;
256✔
3563
        if (!cached->initialized) {
570✔
3564
                r = read_presets(scope, root_dir, cached);
261✔
3565
                if (r < 0)
261✔
3566
                        return r;
3567
        }
3568

3569
        return query_presets(name, cached, NULL);
570✔
3570
}
3571

3572
static int execute_preset(
28✔
3573
                UnitFileFlags file_flags,
3574
                InstallContext *plus,
3575
                InstallContext *minus,
3576
                const LookupPaths *lp,
3577
                const char *config_path,
3578
                char * const *files,
3579
                UnitFilePresetMode mode,
3580
                InstallChange **changes,
3581
                size_t *n_changes) {
3582

3583
        int r;
28✔
3584

3585
        assert(plus);
28✔
3586
        assert(minus);
28✔
3587
        assert(lp);
28✔
3588
        assert(config_path);
28✔
3589

3590
        if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) {
28✔
UNCOV
3591
                _cleanup_set_free_ Set *remove_symlinks_to = NULL;
×
3592

3593
                r = install_context_mark_for_removal(minus, lp, &remove_symlinks_to, config_path, changes, n_changes);
10✔
3594
                if (r < 0)
10✔
UNCOV
3595
                        return r;
×
3596

3597
                r = remove_marked_symlinks(remove_symlinks_to, config_path, lp, false, changes, n_changes);
10✔
3598
        } else
3599
                r = 0;
3600

3601
        if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) {
10✔
3602
                int q;
27✔
3603

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

3617
        return r;
3618
}
3619

3620
static int preset_prepare_one(
9,380✔
3621
                RuntimeScope scope,
3622
                InstallContext *plus,
3623
                InstallContext *minus,
3624
                LookupPaths *lp,
3625
                const char *name,
3626
                const UnitFilePresets *presets,
3627
                InstallChange **changes,
3628
                size_t *n_changes) {
3629

3630
        _cleanup_(install_context_done) InstallContext tmp = { .scope = scope };
9,380✔
UNCOV
3631
        _cleanup_strv_free_ char **instance_name_list = NULL;
×
3632
        InstallInfo *info;
9,380✔
3633
        int r;
9,380✔
3634

3635
        if (install_info_find(plus, name) || install_info_find(minus, name))
9,380✔
3636
                return 0;
85✔
3637

3638
        r = install_info_discover(&tmp, lp, name, SEARCH_FOLLOW_CONFIG_SYMLINKS,
9,295✔
3639
                                  &info, changes, n_changes);
3640
        if (r < 0)
9,295✔
3641
                return r;
3642

3643
        if (!streq(name, info->name)) {
9,294✔
3644
                log_debug("Skipping %s because it is an alias for %s.", name, info->name);
459✔
3645
                return 0;
459✔
3646
        }
3647

3648
        r = query_presets(name, presets, &instance_name_list);
8,835✔
3649
        if (r < 0)
8,835✔
3650
                return r;
3651

3652
        if (r == PRESET_ENABLE) {
8,835✔
3653
                if (instance_name_list)
413✔
3654
                        STRV_FOREACH(s, instance_name_list) {
4✔
3655
                                r = install_info_discover_and_check(plus, lp, *s, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
3✔
3656
                                                                    &info, changes, n_changes);
3657
                                if (r < 0)
3✔
3658
                                        return r;
3659
                        }
3660
                else {
3661
                        r = install_info_discover_and_check(plus, lp, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
9,380✔
3662
                                                            &info, changes, n_changes);
3663
                        if (r < 0)
3664
                                return r;
3665
                }
3666

3667
        } else if (r == PRESET_DISABLE)
8,422✔
3668
                r = install_info_discover(minus, lp, name, SEARCH_FOLLOW_CONFIG_SYMLINKS,
8,420✔
3669
                                          &info, changes, n_changes);
3670

3671
        return r;
3672
}
3673

3674
int unit_file_preset(
9✔
3675
                RuntimeScope scope,
3676
                UnitFileFlags file_flags,
3677
                const char *root_dir,
3678
                char * const *names,
3679
                UnitFilePresetMode mode,
3680
                InstallChange **changes,
3681
                size_t *n_changes) {
3682

3683
        _cleanup_(install_context_done) InstallContext plus = {}, minus = {};
9✔
3684
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
9✔
3685
        _cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
9✔
3686
        const char *config_path;
9✔
3687
        int r;
9✔
3688

3689
        assert(scope >= 0);
9✔
3690
        assert(scope < _RUNTIME_SCOPE_MAX);
9✔
3691
        assert(mode < _UNIT_FILE_PRESET_MODE_MAX);
9✔
3692

3693
        r = lookup_paths_init(&lp, scope, 0, root_dir);
9✔
3694
        if (r < 0)
9✔
3695
                return r;
3696

3697
        config_path = (file_flags & UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
9✔
3698
        if (!config_path)
9✔
3699
                return -ENXIO;
3700

3701
        r = read_presets(scope, root_dir, &presets);
9✔
3702
        if (r < 0)
9✔
3703
                return r;
3704

3705
        STRV_FOREACH(name, names) {
18✔
3706
                r = preset_prepare_one(scope, &plus, &minus, &lp, *name, &presets, changes, n_changes);
9✔
3707
                if (r < 0 && !ERRNO_IS_NEG_UNIT_ISSUE(r))
9✔
3708
                        return r;
3709
        }
3710

3711
        return execute_preset(file_flags, &plus, &minus, &lp, config_path, names, mode, changes, n_changes);
9✔
3712
}
3713

3714
int unit_file_preset_all(
20✔
3715
                RuntimeScope scope,
3716
                UnitFileFlags file_flags,
3717
                const char *root_dir,
3718
                UnitFilePresetMode mode,
3719
                InstallChange **changes,
3720
                size_t *n_changes) {
3721

3722
        _cleanup_(install_context_done) InstallContext plus = {}, minus = {};
20✔
3723
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
20✔
UNCOV
3724
        _cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
×
3725
        const char *config_path = NULL;
20✔
3726
        int r;
20✔
3727

3728
        assert(scope >= 0);
20✔
3729
        assert(scope < _RUNTIME_SCOPE_MAX);
20✔
3730
        assert(mode < _UNIT_FILE_PRESET_MODE_MAX);
20✔
3731

3732
        r = lookup_paths_init(&lp, scope, 0, root_dir);
20✔
3733
        if (r < 0)
20✔
3734
                return r;
3735

3736
        config_path = (file_flags & UNIT_FILE_RUNTIME) ? lp.runtime_config : lp.persistent_config;
20✔
3737
        if (!config_path)
20✔
3738
                return -ENXIO;
3739

3740
        r = read_presets(scope, root_dir, &presets);
20✔
3741
        if (r < 0)
20✔
3742
                return r;
3743

3744
        STRV_FOREACH(i, lp.search_path) {
282✔
3745
                _cleanup_closedir_ DIR *d = NULL;
282✔
3746

3747
                d = opendir(*i);
263✔
3748
                if (!d) {
263✔
3749
                        if (errno == ENOENT)
145✔
3750
                                continue;
145✔
3751

UNCOV
3752
                        return log_debug_errno(errno, "Failed to opendir %s: %m", *i);
×
3753
                }
3754

3755
                FOREACH_DIRENT(de, d,
15,148✔
3756
                               return log_debug_errno(errno, "Failed to read directory %s: %m", *i)) {
3757

3758
                        if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
14,795✔
3759
                                continue;
5,424✔
3760

3761
                        if (!IN_SET(de->d_type, DT_LNK, DT_REG))
9,371✔
UNCOV
3762
                                continue;
×
3763

3764
                        r = preset_prepare_one(scope, &plus, &minus, &lp, de->d_name, &presets, changes, n_changes);
9,371✔
3765
                        if (r < 0 && !ERRNO_IS_NEG_UNIT_ISSUE(r))
9,371✔
3766
                                return r;
3767
                }
3768
        }
3769

3770
        return execute_preset(file_flags, &plus, &minus, &lp, config_path, NULL, mode, changes, n_changes);
19✔
3771
}
3772

3773
static UnitFileList* unit_file_list_free(UnitFileList *f) {
932✔
3774
        if (!f)
932✔
3775
                return NULL;
3776

3777
        free(f->path);
932✔
3778
        return mfree(f);
932✔
3779
}
3780

3781
DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free);
932✔
3782

3783
DEFINE_PRIVATE_HASH_OPS_FULL(unit_file_list_hash_ops_free_free,
1,864✔
3784
                             char, string_hash_func, string_compare_func, free,
3785
                             UnitFileList, unit_file_list_free);
3786

3787
int unit_file_get_list(
4✔
3788
                RuntimeScope scope,
3789
                const char *root_dir,
3790
                char * const *states,
3791
                char * const *patterns,
3792
                Hashmap **ret) {
3793

3794
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
4✔
UNCOV
3795
        _cleanup_hashmap_free_ Hashmap *h = NULL;
×
3796
        int r;
4✔
3797

3798
        assert(scope >= 0);
4✔
3799
        assert(scope < _RUNTIME_SCOPE_MAX);
4✔
3800
        assert(ret);
4✔
3801

3802
        r = lookup_paths_init(&lp, scope, 0, root_dir);
4✔
3803
        if (r < 0)
4✔
3804
                return r;
3805

3806
        STRV_FOREACH(dirname, lp.search_path) {
52✔
3807
                _cleanup_closedir_ DIR *d = NULL;
52✔
3808

3809
                d = opendir(*dirname);
48✔
3810
                if (!d) {
48✔
3811
                        if (errno == ENOENT)
27✔
3812
                                continue;
27✔
UNCOV
3813
                        if (IN_SET(errno, ENOTDIR, EACCES)) {
×
UNCOV
3814
                                log_debug_errno(errno, "Failed to open \"%s\": %m", *dirname);
×
UNCOV
3815
                                continue;
×
3816
                        }
3817

UNCOV
3818
                        return -errno;
×
3819
                }
3820

3821
                FOREACH_DIRENT(de, d, return -errno) {
1,586✔
3822
                        if (!IN_SET(de->d_type, DT_LNK, DT_REG))
1,523✔
3823
                                continue;
591✔
3824

3825
                        if (hashmap_contains(h, de->d_name))
1,376✔
3826
                                continue;
10✔
3827

3828
                        if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
1,366✔
UNCOV
3829
                                continue;
×
3830

3831
                        if (!strv_fnmatch_or_empty(patterns, de->d_name, FNM_NOESCAPE))
1,366✔
3832
                                continue;
434✔
3833

3834
                        UnitFileState state;
932✔
3835

3836
                        r = unit_file_lookup_state(scope, &lp, de->d_name, &state);
932✔
3837
                        if (r < 0)
932✔
UNCOV
3838
                                state = UNIT_FILE_BAD;
×
3839

3840
                        if (!strv_isempty(states) &&
932✔
UNCOV
3841
                            !strv_contains(states, unit_file_state_to_string(state)))
×
UNCOV
3842
                                continue;
×
3843

3844
                        _cleanup_(unit_file_list_freep) UnitFileList *f = new(UnitFileList, 1);
932✔
3845
                        if (!f)
932✔
3846
                                return -ENOMEM;
3847

3848
                        *f = (UnitFileList) {
1,864✔
3849
                                .path = path_make_absolute(de->d_name, *dirname),
932✔
3850
                                .state = state,
3851
                        };
3852
                        if (!f->path)
932✔
3853
                                return -ENOMEM;
3854

3855
                        _cleanup_free_ char *unit_name = strdup(de->d_name);
932✔
3856
                        if (!unit_name)
932✔
3857
                                return -ENOMEM;
3858

3859
                        r = hashmap_ensure_put(&h, &unit_file_list_hash_ops_free_free, unit_name, f);
932✔
3860
                        if (r < 0)
932✔
3861
                                return r;
3862
                        assert(r > 0);
932✔
3863

3864
                        TAKE_PTR(unit_name);
932✔
3865
                        TAKE_PTR(f);
932✔
3866
                }
3867
        }
3868

3869
        *ret = TAKE_PTR(h);
4✔
3870
        return 0;
4✔
3871
}
3872

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

3889
DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState);
5,710✔
3890

3891
static const char* const install_change_type_table[_INSTALL_CHANGE_TYPE_MAX] = {
3892
        [INSTALL_CHANGE_SYMLINK]                 = "symlink",
3893
        [INSTALL_CHANGE_UNLINK]                  = "unlink",
3894
        [INSTALL_CHANGE_IS_MASKED]               = "masked",
3895
        [INSTALL_CHANGE_IS_MASKED_GENERATOR]     = "masked by generator",
3896
        [INSTALL_CHANGE_IS_DANGLING]             = "dangling",
3897
        [INSTALL_CHANGE_DESTINATION_NOT_PRESENT] = "destination not present",
3898
        [INSTALL_CHANGE_AUXILIARY_FAILED]        = "auxiliary unit failed",
3899
};
3900

3901
DEFINE_STRING_TABLE_LOOKUP(install_change_type, InstallChangeType);
120✔
3902

3903
static const char* const unit_file_preset_mode_table[_UNIT_FILE_PRESET_MODE_MAX] = {
3904
        [UNIT_FILE_PRESET_FULL]         = "full",
3905
        [UNIT_FILE_PRESET_ENABLE_ONLY]  = "enable-only",
3906
        [UNIT_FILE_PRESET_DISABLE_ONLY] = "disable-only",
3907
};
3908

3909
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