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

systemd / systemd / 15263807472

26 May 2025 08:53PM UTC coverage: 72.046% (-0.002%) from 72.048%
15263807472

push

github

yuwata
src/core/manager.c: log preset activity on first boot

This gives us a little more information about what units were enabled
or disabled on that first boot and will be useful for OS developers
tracking down the source of unit state.

An example with this enabled looks like:

```
NET: Registered PF_VSOCK protocol family
systemd[1]: Applying preset policy.
systemd[1]: Unit /etc/systemd/system/dnsmasq.service is masked, ignoring.
systemd[1]: Unit /etc/systemd/system/systemd-repart.service is masked, ignoring.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket'.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir.mount' → '/etc/systemd/system/var-mnt-workdir.mount'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir\x2dtmp.mount' → '/etc/systemd/system/var-mnt-workdir\x2dtmp.mount'.
systemd[1]: Created symlink '/etc/systemd/system/afterburn-sshkeys.target.requires/afterburn-sshkeys@core.service' → '/usr/lib/systemd/system/afterburn-sshkeys@.service'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket' → '/usr/lib/systemd/system/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket' → '/usr/lib/systemd/system/systemd-resolved-monitor.socket'.
systemd[1]: Populated /etc with preset unit settings.
```

Considering it only happens on first boot and not on every boot I think
the extra information is worth the extra verbosity in the logs just for
that boot.

5 of 6 new or added lines in 1 file covered. (83.33%)

5463 existing lines in 165 files now uncovered.

299151 of 415222 relevant lines covered (72.05%)

702386.45 hits per line

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

98.84
/src/basic/unit-name.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include "sd-id128.h"
4

5
#include "alloc-util.h"
6
#include "glob-util.h"
7
#include "hexdecoct.h"
8
#include "log.h"
9
#include "memory-util.h"
10
#include "path-util.h"
11
#include "sparse-endian.h"
12
#include "special.h"
13
#include "siphash24.h"
14
#include "string-util.h"
15
#include "unit-def.h"
16
#include "unit-name.h"
17

18
/* Characters valid in a unit name. */
19
#define VALID_CHARS                             \
20
        DIGITS                                  \
21
        LETTERS                                 \
22
        ":-_.\\"
23

24
/* The same, but also permits the single @ character that may appear */
25
#define VALID_CHARS_WITH_AT                     \
26
        "@"                                     \
27
        VALID_CHARS
28

29
/* All chars valid in a unit name glob */
30
#define VALID_CHARS_GLOB                        \
31
        VALID_CHARS_WITH_AT                     \
32
        "[]!-*?"
33

34
#define LONG_UNIT_NAME_HASH_KEY SD_ID128_MAKE(ec,f2,37,fb,58,32,4a,32,84,9f,06,9b,0d,21,eb,9a)
35
#define UNIT_NAME_HASH_LENGTH_CHARS 16
36

37
bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
45,165,141✔
38
        const char *e, *i, *at;
45,165,141✔
39

40
        assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
45,165,141✔
41

42
        if (_unlikely_(flags == 0))
45,165,141✔
43
                return false;
44

45
        if (isempty(n))
45,165,141✔
46
                return false;
47

48
        if (strlen(n) >= UNIT_NAME_MAX)
45,165,027✔
49
                return false;
50

51
        e = strrchr(n, '.');
45,165,023✔
52
        if (!e || e == n)
45,165,023✔
53
                return false;
54

55
        if (unit_type_from_string(e + 1) < 0)
41,610,781✔
56
                return false;
57

58
        for (i = n, at = NULL; i < e; i++) {
780,993,806✔
59

60
                if (*i == '@' && !at)
739,465,308✔
61
                        at = i;
2,156,850✔
62

63
                if (!strchr(VALID_CHARS_WITH_AT, *i))
739,465,308✔
64
                        return false;
65
        }
66

67
        if (at == n)
41,528,498✔
68
                return false;
69

70
        if (flags & UNIT_NAME_PLAIN)
41,528,492✔
71
                if (!at)
28,478,423✔
72
                        return true;
73

74
        if (flags & UNIT_NAME_INSTANCE)
14,154,865✔
75
                if (at && e > at + 1)
13,467,835✔
76
                        return true;
77

78
        if (flags & UNIT_NAME_TEMPLATE)
12,875,993✔
79
                if (at && e == at + 1)
1,939,970✔
80
                        return true;
598,715✔
81

82
        return false;
83
}
84

85
bool unit_prefix_is_valid(const char *p) {
14,397,217✔
86

87
        /* We don't allow additional @ in the prefix string */
88

89
        if (isempty(p))
14,397,217✔
90
                return false;
91

92
        return in_charset(p, VALID_CHARS);
14,397,216✔
93
}
94

95
bool unit_instance_is_valid(const char *i) {
120,873✔
96

97
        /* The max length depends on the length of the string, so we
98
         * don't really check this here. */
99

100
        if (isempty(i))
120,873✔
101
                return false;
102

103
        /* We allow additional @ in the instance string, we do not
104
         * allow them in the prefix! */
105

106
        return in_charset(i, "@" VALID_CHARS);
120,871✔
107
}
108

109
bool unit_suffix_is_valid(const char *s) {
278,365✔
110
        if (isempty(s))
278,365✔
111
                return false;
112

113
        if (s[0] != '.')
278,365✔
114
                return false;
115

116
        if (unit_type_from_string(s + 1) < 0)
278,365✔
117
                return false;
1✔
118

119
        return true;
120
}
121

122
int unit_name_to_prefix(const char *n, char **ret) {
11,104,849✔
123
        const char *p;
11,104,849✔
124
        char *s;
11,104,849✔
125

126
        assert(n);
11,104,849✔
127
        assert(ret);
11,104,849✔
128

129
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
11,104,849✔
130
                return -EINVAL;
131

132
        p = strchr(n, '@');
11,104,842✔
133
        if (!p)
11,104,842✔
134
                p = strrchr(n, '.');
10,576,544✔
135

136
        assert_se(p);
10,576,544✔
137

138
        s = strndup(n, p - n);
11,104,842✔
139
        if (!s)
11,104,842✔
140
                return -ENOMEM;
141

142
        *ret = s;
11,104,842✔
143
        return 0;
11,104,842✔
144
}
145

146
UnitNameFlags unit_name_to_instance(const char *n, char **ret) {
357,950✔
147
        const char *p, *d;
357,950✔
148

149
        assert(n);
357,950✔
150

151
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
357,950✔
152
                return -EINVAL;
153

154
        /* Everything past the first @ and before the last . is the instance */
155
        p = strchr(n, '@');
357,934✔
156
        if (!p) {
357,934✔
157
                if (ret)
202,286✔
158
                        *ret = NULL;
188,040✔
159
                return UNIT_NAME_PLAIN;
202,286✔
160
        }
161

162
        p++;
155,648✔
163

164
        d = strrchr(p, '.');
155,648✔
165
        if (!d)
155,648✔
166
                return -EINVAL;
167

168
        if (ret) {
155,648✔
169
                char *i = strndup(p, d-p);
154,056✔
170
                if (!i)
154,056✔
171
                        return -ENOMEM;
172

173
                *ret = i;
154,056✔
174
        }
175
        return d > p ? UNIT_NAME_INSTANCE : UNIT_NAME_TEMPLATE;
155,648✔
176
}
177

178
int unit_name_to_prefix_and_instance(const char *n, char **ret) {
1,004✔
179
        const char *d;
1,004✔
180
        char *s;
1,004✔
181

182
        assert(n);
1,004✔
183
        assert(ret);
1,004✔
184

185
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
1,004✔
186
                return -EINVAL;
187

188
        d = strrchr(n, '.');
1,004✔
189
        if (!d)
1,004✔
190
                return -EINVAL;
191

192
        s = strndup(n, d - n);
1,004✔
193
        if (!s)
1,004✔
194
                return -ENOMEM;
195

196
        *ret = s;
1,004✔
197
        return 0;
1,004✔
198
}
199

200
UnitType unit_name_to_type(const char *n) {
8,199,025✔
201
        const char *e;
8,199,025✔
202

203
        assert(n);
8,199,025✔
204

205
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
8,199,025✔
206
                return _UNIT_TYPE_INVALID;
207

208
        assert_se(e = strrchr(n, '.'));
8,197,724✔
209

210
        return unit_type_from_string(e + 1);
8,197,724✔
211
}
212

213
int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
3,317✔
214
        _cleanup_free_ char *s = NULL;
3,317✔
215
        size_t a, b;
3,317✔
216
        char *e;
3,317✔
217

218
        assert(n);
3,317✔
219
        assert(suffix);
3,317✔
220
        assert(ret);
3,317✔
221

222
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
3,317✔
223
                return -EINVAL;
224

225
        if (!unit_suffix_is_valid(suffix))
3,317✔
226
                return -EINVAL;
227

228
        assert_se(e = strrchr(n, '.'));
3,317✔
229

230
        a = e - n;
3,317✔
231
        b = strlen(suffix);
3,317✔
232

233
        s = new(char, a + b + 1);
3,317✔
234
        if (!s)
3,317✔
235
                return -ENOMEM;
236

237
        strcpy(mempcpy(s, n, a), suffix);
3,317✔
238

239
        /* Make sure the name is still valid (i.e. didn't grow too large due to longer suffix) */
240
        if (!unit_name_is_valid(s, UNIT_NAME_ANY))
3,317✔
241
                return -EINVAL;
242

243
        *ret = TAKE_PTR(s);
3,317✔
244
        return 0;
3,317✔
245
}
246

247
int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
1,120✔
248
        UnitType type;
1,120✔
249

250
        assert(prefix);
1,120✔
251
        assert(suffix);
1,120✔
252
        assert(ret);
1,120✔
253

254
        if (suffix[0] != '.')
1,120✔
255
                return -EINVAL;
256

257
        type = unit_type_from_string(suffix + 1);
1,120✔
258
        if (type < 0)
1,120✔
259
                return type;
260

261
        return unit_name_build_from_type(prefix, instance, type, ret);
1,120✔
262
}
263

264
int unit_name_build_from_type(const char *prefix, const char *instance, UnitType type, char **ret) {
7,199,056✔
265
        _cleanup_free_ char *s = NULL;
7,199,056✔
266
        const char *ut;
7,199,056✔
267

268
        assert(prefix);
7,199,056✔
269
        assert(type >= 0);
7,199,056✔
270
        assert(type < _UNIT_TYPE_MAX);
7,199,056✔
271
        assert(ret);
7,199,056✔
272

273
        if (!unit_prefix_is_valid(prefix))
7,199,056✔
274
                return -EINVAL;
275

276
        ut = unit_type_to_string(type);
7,199,056✔
277

278
        if (instance) {
7,199,056✔
279
                if (!unit_instance_is_valid(instance))
112,163✔
280
                        return -EINVAL;
281

282
                s = strjoin(prefix, "@", instance, ".", ut);
112,163✔
283
        } else
284
                s = strjoin(prefix, ".", ut);
7,086,893✔
285
        if (!s)
7,199,056✔
286
                return -ENOMEM;
287

288
        /* Verify that this didn't grow too large (or otherwise is invalid) */
289
        if (!unit_name_is_valid(s, instance ? UNIT_NAME_INSTANCE : UNIT_NAME_PLAIN))
14,285,949✔
290
                return -EINVAL;
291

292
        *ret = TAKE_PTR(s);
7,199,056✔
293
        return 0;
7,199,056✔
294
}
295

296
static char *do_escape_char(char c, char *t) {
198,807✔
297
        assert(t);
198,807✔
298

299
        *(t++) = '\\';
198,807✔
300
        *(t++) = 'x';
198,807✔
301
        *(t++) = hexchar(c >> 4);
198,807✔
302
        *(t++) = hexchar(c);
198,807✔
303

304
        return t;
198,807✔
305
}
306

307
static char *do_escape(const char *f, char *t) {
239,177✔
308
        assert(f);
239,177✔
309
        assert(t);
239,177✔
310

311
        /* do not create units with a leading '.', like for "/.dotdir" mount points */
312
        if (*f == '.') {
239,177✔
UNCOV
313
                t = do_escape_char(*f, t);
×
UNCOV
314
                f++;
×
315
        }
316

317
        for (; *f; f++) {
5,497,535✔
318
                if (*f == '/')
5,258,358✔
319
                        *(t++) = '-';
459,202✔
320
                else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
4,799,156✔
321
                        t = do_escape_char(*f, t);
198,779✔
322
                else
323
                        *(t++) = *f;
4,600,377✔
324
        }
325

326
        return t;
239,177✔
327
}
328

329
char* unit_name_escape(const char *f) {
239,177✔
330
        char *r, *t;
239,177✔
331

332
        assert(f);
239,177✔
333

334
        r = new(char, strlen(f)*4+1);
239,177✔
335
        if (!r)
239,177✔
336
                return NULL;
337

338
        t = do_escape(f, r);
239,177✔
339
        *t = 0;
239,177✔
340

341
        return r;
239,177✔
342
}
343

344
int unit_name_unescape(const char *f, char **ret) {
10,048✔
345
        _cleanup_free_ char *r = NULL;
10,048✔
346
        char *t;
10,048✔
347

348
        assert(f);
10,048✔
349

350
        r = strdup(f);
10,048✔
351
        if (!r)
10,048✔
352
                return -ENOMEM;
353

354
        for (t = r; *f; f++) {
264,615✔
355
                if (*f == '-')
254,567✔
356
                        *(t++) = '/';
20,926✔
357
                else if (*f == '\\') {
233,641✔
358
                        int a, b;
13,433✔
359

360
                        if (f[1] != 'x')
13,433✔
361
                                return -EINVAL;
362

363
                        a = unhexchar(f[2]);
13,433✔
364
                        if (a < 0)
13,433✔
365
                                return -EINVAL;
366

367
                        b = unhexchar(f[3]);
13,433✔
368
                        if (b < 0)
13,433✔
369
                                return -EINVAL;
370

371
                        *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
13,433✔
372
                        f += 3;
13,433✔
373
                } else
374
                        *(t++) = *f;
220,208✔
375
        }
376

377
        *t = 0;
10,048✔
378

379
        *ret = TAKE_PTR(r);
10,048✔
380

381
        return 0;
10,048✔
382
}
383

384
int unit_name_path_escape(const char *f, char **ret) {
266,520✔
385
        _cleanup_free_ char *p = NULL;
266,520✔
386
        char *s;
266,520✔
387
        int r;
266,520✔
388

389
        assert(f);
266,520✔
390
        assert(ret);
266,520✔
391

392
        r = path_simplify_alloc(f, &p);
266,520✔
393
        if (r < 0)
266,520✔
394
                return r;
395

396
        if (empty_or_root(p))
266,520✔
397
                s = strdup("-");
29,567✔
398
        else {
399
                if (!path_is_normalized(p))
236,953✔
400
                        return -EINVAL;
401

402
                /* Truncate trailing slashes and skip leading slashes */
403
                delete_trailing_chars(p, "/");
236,942✔
404
                s = unit_name_escape(skip_leading_chars(p, "/"));
473,884✔
405
        }
406
        if (!s)
266,509✔
407
                return -ENOMEM;
408

409
        *ret = s;
266,509✔
410
        return 0;
266,509✔
411
}
412

413
int unit_name_path_unescape(const char *f, char **ret) {
9,618✔
414
        _cleanup_free_ char *s = NULL;
9,618✔
415
        int r;
9,618✔
416

417
        assert(f);
9,618✔
418

419
        if (isempty(f))
19,234✔
420
                return -EINVAL;
421

422
        if (streq(f, "-")) {
9,616✔
423
                s = strdup("/");
148✔
424
                if (!s)
148✔
425
                        return -ENOMEM;
426
        } else {
427
                _cleanup_free_ char *w = NULL;
9,468✔
428

429
                r = unit_name_unescape(f, &w);
9,468✔
430
                if (r < 0)
9,468✔
431
                        return r;
432

433
                /* Don't accept trailing or leading slashes */
434
                if (startswith(w, "/") || endswith(w, "/"))
9,468✔
435
                        return -EINVAL;
436

437
                /* Prefix a slash again */
438
                s = strjoin("/", w);
9,461✔
439
                if (!s)
9,461✔
440
                        return -ENOMEM;
441

442
                if (!path_is_normalized(s))
9,461✔
443
                        return -EINVAL;
444
        }
445

446
        if (ret)
9,605✔
447
                *ret = TAKE_PTR(s);
9,605✔
448

449
        return 0;
450
}
451

452
int unit_name_replace_instance_full(
7,950✔
453
                const char *original,
454
                const char *instance,
455
                bool accept_glob,
456
                char **ret) {
457

458
        _cleanup_free_ char *s = NULL;
7,950✔
459
        const char *prefix, *suffix;
7,950✔
460
        size_t pl;
7,950✔
461

462
        assert(original);
7,950✔
463
        assert(instance);
7,950✔
464
        assert(ret);
7,950✔
465

466
        if (!unit_name_is_valid(original, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
7,950✔
467
                return -EINVAL;
468
        if (!unit_instance_is_valid(instance) && !(accept_glob && in_charset(instance, VALID_CHARS_GLOB)))
7,944✔
469
                return -EINVAL;
470

471
        prefix = ASSERT_PTR(strchr(original, '@'));
7,943✔
472
        suffix = ASSERT_PTR(strrchr(original, '.'));
7,943✔
473
        assert(prefix < suffix);
7,943✔
474

475
        pl = prefix - original + 1; /* include '@' */
7,943✔
476

477
        s = new(char, pl + strlen(instance) + strlen(suffix) + 1);
7,943✔
478
        if (!s)
7,943✔
479
                return -ENOMEM;
480

481
#if HAS_FEATURE_MEMORY_SANITIZER
482
        /* MSan doesn't like stpncpy... See also https://github.com/google/sanitizers/issues/926 */
483
        memzero(s, pl + strlen(instance) + strlen(suffix) + 1);
484
#endif
485

486
        strcpy(stpcpy(stpncpy(s, original, pl), instance), suffix);
7,943✔
487

488
        /* Make sure the resulting name still is valid, i.e. didn't grow too large. Globs will be expanded
489
         * by clients when used, so the check is pointless. */
490
        if (!accept_glob && !unit_name_is_valid(s, UNIT_NAME_INSTANCE))
7,943✔
491
                return -EINVAL;
492

493
        *ret = TAKE_PTR(s);
7,943✔
494
        return 0;
7,943✔
495
}
496

497
int unit_name_template(const char *f, char **ret) {
793,028✔
498
        const char *p, *e;
793,028✔
499
        char *s;
793,028✔
500
        size_t a;
793,028✔
501

502
        assert(f);
793,028✔
503
        assert(ret);
793,028✔
504

505
        if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
793,028✔
506
                return -EINVAL;
507

508
        assert_se(p = strchr(f, '@'));
299,736✔
509
        assert_se(e = strrchr(f, '.'));
299,736✔
510

511
        a = p - f;
299,736✔
512

513
        s = new(char, a + 1 + strlen(e) + 1);
299,736✔
514
        if (!s)
299,736✔
515
                return -ENOMEM;
516

517
        strcpy(mempcpy(s, f, a + 1), e);
299,736✔
518

519
        *ret = s;
299,736✔
520
        return 0;
299,736✔
521
}
522

523
bool unit_name_is_hashed(const char *name) {
2,011✔
524
        char *s;
2,011✔
525

526
        if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
2,011✔
527
                return false;
528

529
        assert_se(s = strrchr(name, '.'));
2,005✔
530

531
        if (s - name < UNIT_NAME_HASH_LENGTH_CHARS + 1)
2,005✔
532
                return false;
533

534
        s -= UNIT_NAME_HASH_LENGTH_CHARS;
1,130✔
535
        if (s[-1] != '_')
1,130✔
536
                return false;
537

538
        for (size_t i = 0; i < UNIT_NAME_HASH_LENGTH_CHARS; i++)
46✔
539
                if (!strchr(LOWERCASE_HEXDIGITS, s[i]))
44✔
540
                        return false;
541

542
        return true;
543
}
544

545
int unit_name_hash_long(const char *name, char **ret) {
21✔
546
        _cleanup_free_ char *n = NULL, *hash = NULL;
21✔
547
        char *suffix;
21✔
548
        le64_t h;
21✔
549
        size_t len;
21✔
550

551
        if (strlen(name) < UNIT_NAME_MAX)
21✔
552
                return -EMSGSIZE;
553

554
        suffix = strrchr(name, '.');
21✔
555
        if (!suffix)
21✔
556
                return -EINVAL;
557

558
        if (unit_type_from_string(suffix+1) < 0)
21✔
559
                return -EINVAL;
560

561
        h = htole64(siphash24_string(name, LONG_UNIT_NAME_HASH_KEY.bytes));
21✔
562

563
        hash = hexmem(&h, sizeof(h));
21✔
564
        if (!hash)
21✔
565
                return -ENOMEM;
566

567
        assert_se(strlen(hash) == UNIT_NAME_HASH_LENGTH_CHARS);
21✔
568

569
        len = UNIT_NAME_MAX - 1 - strlen(suffix+1) - UNIT_NAME_HASH_LENGTH_CHARS - 2;
21✔
570
        assert(len > 0 && len < UNIT_NAME_MAX);
21✔
571

572
        n = strndup(name, len);
21✔
573
        if (!n)
21✔
574
                return -ENOMEM;
575

576
        if (!strextend(&n, "_", hash, suffix))
21✔
577
                return -ENOMEM;
578
        assert_se(unit_name_is_valid(n, UNIT_NAME_PLAIN));
21✔
579

580
        *ret = TAKE_PTR(n);
21✔
581

582
        return 0;
21✔
583
}
584

585
int unit_name_from_path(const char *path, const char *suffix, char **ret) {
262,037✔
586
        _cleanup_free_ char *p = NULL, *s = NULL;
262,037✔
587
        int r;
262,037✔
588

589
        assert(path);
262,037✔
590
        assert(suffix);
262,037✔
591
        assert(ret);
262,037✔
592

593
        if (!unit_suffix_is_valid(suffix))
262,037✔
594
                return -EINVAL;
595

596
        r = unit_name_path_escape(path, &p);
262,037✔
597
        if (r < 0)
262,037✔
598
                return r;
599

600
        s = strjoin(p, suffix);
262,031✔
601
        if (!s)
262,031✔
602
                return -ENOMEM;
603

604
        if (strlen(s) >= UNIT_NAME_MAX) {
262,031✔
UNCOV
605
                _cleanup_free_ char *n = NULL;
×
606

607
                log_debug("Unit name \"%s\" too long, falling back to hashed unit name.", s);
21✔
608

609
                r = unit_name_hash_long(s, &n);
21✔
610
                if (r < 0)
21✔
UNCOV
611
                        return r;
×
612

613
                free_and_replace(s, n);
21✔
614
        }
615

616
        /* Refuse if this for some other reason didn't result in a valid name */
617
        if (!unit_name_is_valid(s, UNIT_NAME_PLAIN))
262,031✔
618
                return -EINVAL;
619

620
        *ret = TAKE_PTR(s);
262,031✔
621
        return 0;
262,031✔
622
}
623

624
int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
25✔
625
        _cleanup_free_ char *p = NULL, *s = NULL;
25✔
626
        int r;
25✔
627

628
        assert(prefix);
25✔
629
        assert(path);
25✔
630
        assert(suffix);
25✔
631
        assert(ret);
25✔
632

633
        if (!unit_prefix_is_valid(prefix))
25✔
634
                return -EINVAL;
635

636
        if (!unit_suffix_is_valid(suffix))
25✔
637
                return -EINVAL;
638

639
        r = unit_name_path_escape(path, &p);
24✔
640
        if (r < 0)
24✔
641
                return r;
642

643
        s = strjoin(prefix, "@", p, suffix);
23✔
644
        if (!s)
23✔
645
                return -ENOMEM;
646

647
        if (strlen(s) >= UNIT_NAME_MAX) /* Return a slightly more descriptive error for this specific condition */
23✔
648
                return -ENAMETOOLONG;
649

650
        /* Refuse if this for some other reason didn't result in a valid name */
651
        if (!unit_name_is_valid(s, UNIT_NAME_INSTANCE))
23✔
652
                return -EINVAL;
653

654
        *ret = TAKE_PTR(s);
23✔
655
        return 0;
23✔
656
}
657

658
int unit_name_to_path(const char *name, char **ret) {
2,003✔
659
        _cleanup_free_ char *prefix = NULL;
2,003✔
660
        int r;
2,003✔
661

662
        assert(name);
2,003✔
663

664
        r = unit_name_to_prefix(name, &prefix);
2,003✔
665
        if (r < 0)
2,003✔
666
                return r;
667

668
        if (unit_name_is_hashed(name))
2,001✔
669
                return -ENAMETOOLONG;
670

671
        return unit_name_path_unescape(prefix, ret);
2,000✔
672
}
673

674
static bool do_escape_mangle(const char *f, bool allow_globs, char *t) {
1,302✔
675
        const char *valid_chars;
1,302✔
676
        bool mangled = false;
1,302✔
677

678
        assert(f);
1,302✔
679
        assert(t);
1,302✔
680

681
        /* We'll only escape the obvious characters here, to play safe.
682
         *
683
         * Returns true if any characters were mangled, false otherwise.
684
         */
685

686
        valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
1,302✔
687

688
        for (; *f; f++)
23,741✔
689
                if (*f == '/') {
22,439✔
690
                        *(t++) = '-';
17✔
691
                        mangled = true;
17✔
692
                } else if (!strchr(valid_chars, *f)) {
22,422✔
693
                        t = do_escape_char(*f, t);
28✔
694
                        mangled = true;
28✔
695
                } else
696
                        *(t++) = *f;
22,394✔
697
        *t = 0;
1,302✔
698

699
        return mangled;
1,302✔
700
}
701

702
/**
703
 *  Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
704
 *  /blah/blah is converted to blah-blah.mount, anything else is left alone,
705
 *  except that @suffix is appended if a valid unit suffix is not present.
706
 *
707
 *  If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
708
 */
709
int unit_name_mangle_with_suffix(
12,990✔
710
                const char *name,
711
                const char *operation,
712
                UnitNameMangle flags,
713
                const char *suffix,
714
                char **ret) {
715

716
        _cleanup_free_ char *s = NULL;
12,990✔
717
        bool mangled, suggest_escape = true, warn = flags & UNIT_NAME_MANGLE_WARN;
12,990✔
718
        int r;
12,990✔
719

720
        assert(name);
12,990✔
721
        assert(suffix);
12,990✔
722
        assert(ret);
12,990✔
723

724
        if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
25,980✔
725
                return -EINVAL;
726

727
        if (!unit_suffix_is_valid(suffix))
12,986✔
728
                return -EINVAL;
729

730
        /* Already a fully valid unit name? If so, no mangling is necessary... */
731
        if (unit_name_is_valid(name, UNIT_NAME_ANY))
12,986✔
732
                goto good;
11,515✔
733

734
        /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
735
        if (string_is_glob(name) && in_charset(name, VALID_CHARS_GLOB)) {
1,471✔
736
                if (flags & UNIT_NAME_MANGLE_GLOB)
79✔
737
                        goto good;
79✔
UNCOV
738
                log_full(warn ? LOG_NOTICE : LOG_DEBUG,
×
739
                         "Glob pattern passed%s%s, but globs are not supported for this.",
740
                         operation ? " " : "", strempty(operation));
741
                suggest_escape = false;
742
        }
743

744
        if (path_is_absolute(name)) {
1,392✔
745
                _cleanup_free_ char *n = NULL;
93✔
746

747
                r = path_simplify_alloc(name, &n);
93✔
748
                if (r < 0)
93✔
749
                        return r;
750

751
                if (is_device_path(n)) {
93✔
752
                        r = unit_name_from_path(n, ".device", ret);
58✔
753
                        if (r >= 0)
58✔
754
                                return 1;
755
                        if (r != -EINVAL)
2✔
756
                                return r;
757
                }
758

759
                r = unit_name_from_path(n, ".mount", ret);
37✔
760
                if (r >= 0)
37✔
761
                        return 1;
762
                if (r != -EINVAL)
3✔
763
                        return r;
764
        }
765

766
        s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
1,302✔
767
        if (!s)
1,302✔
768
                return -ENOMEM;
769

770
        mangled = do_escape_mangle(name, flags & UNIT_NAME_MANGLE_GLOB, s);
1,302✔
771
        if (mangled)
1,302✔
772
                log_full(warn ? LOG_NOTICE : LOG_DEBUG,
11✔
773
                         "Invalid unit name \"%s\" escaped as \"%s\"%s.",
774
                         name, s,
775
                         suggest_escape ? " (maybe you should use systemd-escape?)" : "");
776

777
        /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow
778
         * "foo.*" as a valid glob. */
779
        if ((!(flags & UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
1,302✔
780
                strcat(s, suffix);
1,296✔
781

782
        /* Make sure mangling didn't grow this too large (but don't do this check if globbing is allowed,
783
         * since globs generally do not qualify as valid unit names) */
784
        if (!FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB) && !unit_name_is_valid(s, UNIT_NAME_ANY))
1,302✔
785
                return -EINVAL;
786

787
        *ret = TAKE_PTR(s);
1,302✔
788
        return 1;
1,302✔
789

790
good:
11,594✔
791
        return strdup_to(ret, name);
11,594✔
792
}
793

794
int slice_build_parent_slice(const char *slice, char **ret) {
5,300✔
795
        assert(slice);
5,300✔
796
        assert(ret);
5,300✔
797

798
        if (!slice_name_is_valid(slice))
5,300✔
799
                return -EINVAL;
5,300✔
800

801
        if (streq(slice, SPECIAL_ROOT_SLICE)) {
5,288✔
802
                *ret = NULL;
601✔
803
                return 0;
601✔
804
        }
805

806
        _cleanup_free_ char *s = strdup(slice);
4,687✔
807
        if (!s)
4,687✔
808
                return -ENOMEM;
809

810
        char *dash = strrchr(s, '-');
4,687✔
811
        if (!dash)
4,687✔
812
                return strdup_to_full(ret, SPECIAL_ROOT_SLICE);
1,771✔
813

814
        /* We know that s ended with .slice before truncation, so we have enough space. */
815
        strcpy(dash, ".slice");
2,916✔
816

817
        *ret = TAKE_PTR(s);
2,916✔
818
        return 1;
2,916✔
819
}
820

821
int slice_build_subslice(const char *slice, const char *name, char **ret) {
192✔
822
        char *subslice;
192✔
823

824
        assert(slice);
192✔
825
        assert(name);
192✔
826
        assert(ret);
192✔
827

828
        if (!slice_name_is_valid(slice))
192✔
829
                return -EINVAL;
830

831
        if (!unit_prefix_is_valid(name))
190✔
832
                return -EINVAL;
833

834
        if (streq(slice, SPECIAL_ROOT_SLICE))
190✔
835
                subslice = strjoin(name, ".slice");
1✔
836
        else {
837
                char *e;
189✔
838

839
                assert_se(e = endswith(slice, ".slice"));
189✔
840

841
                subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
189✔
842
                if (!subslice)
189✔
843
                        return -ENOMEM;
844

845
                stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
189✔
846
        }
847

848
        *ret = subslice;
190✔
849
        return 0;
190✔
850
}
851

852
bool slice_name_is_valid(const char *name) {
8,159✔
853
        const char *p, *e;
8,159✔
854
        bool dash = false;
8,159✔
855

856
        if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
8,159✔
857
                return false;
858

859
        if (streq(name, SPECIAL_ROOT_SLICE))
8,135✔
860
                return true;
861

862
        e = endswith(name, ".slice");
7,232✔
863
        if (!e)
7,232✔
864
                return false;
865

866
        for (p = name; p < e; p++) {
117,306✔
867

868
                if (*p == '-') {
110,083✔
869

870
                        /* Don't allow initial dash */
871
                        if (p == name)
4,405✔
872
                                return false;
873

874
                        /* Don't allow multiple dashes */
875
                        if (dash)
4,402✔
876
                                return false;
877

878
                        dash = true;
879
                } else
880
                        dash = false;
881
        }
882

883
        /* Don't allow trailing hash */
884
        if (dash)
7,223✔
885
                return false;
2✔
886

887
        return true;
888
}
889

890
bool unit_name_prefix_equal(const char *a, const char *b) {
3,655✔
891
        const char *p, *q;
3,655✔
892

893
        assert(a);
3,655✔
894
        assert(b);
3,655✔
895

896
        if (!unit_name_is_valid(a, UNIT_NAME_ANY) || !unit_name_is_valid(b, UNIT_NAME_ANY))
3,655✔
897
                return false;
898

899
        p = strchr(a, '@');
3,653✔
900
        if (!p)
3,653✔
901
                p = strrchr(a, '.');
3,031✔
902

903
        q = strchr(b, '@');
3,653✔
904
        if (!q)
3,653✔
905
                q = strrchr(b, '.');
6✔
906

907
        assert(p);
3,653✔
908
        assert(q);
3,653✔
909

910
        return memcmp_nn(a, p - a, b, q - b) == 0;
3,653✔
911
}
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