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

systemd / systemd / 28272947092

26 Jun 2026 08:38PM UTC coverage: 72.893% (+0.2%) from 72.703%
28272947092

push

github

poettering
sysupdate: Address review feedback on CheckNew varlink scaffolding

Follow-up to #42422:

 - Rename process_image() to context_process_image(), since it now
   operates on a Context object.
 - Use IN_SET() in image_type_can_sysupdate() instead of a switch.
 - Name the return parameters of context_list_components() ret_xyz, per
   our coding style.
 - Drop a redundant "else" after a return in vl_method_check_new().

9 of 11 new or added lines in 1 file covered. (81.82%)

12567 existing lines in 144 files now uncovered.

341026 of 467845 relevant lines covered (72.89%)

1339355.33 hits per line

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

98.87
/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
        ALPHANUMERICAL                          \
21
        ":-_.\\"
22

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

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

33
#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)
34
#define UNIT_NAME_HASH_LENGTH_CHARS 16
35

36
bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
119,501,088✔
37
        const char *e, *i, *at;
119,501,088✔
38

39
        assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
119,501,088✔
40

41
        if (_unlikely_(flags == 0))
119,501,088✔
42
                return false;
43

44
        if (isempty(n))
119,501,088✔
45
                return false;
46

47
        if (strlen(n) >= UNIT_NAME_MAX)
119,500,949✔
48
                return false;
49

50
        e = strrchr(n, '.');
119,500,945✔
51
        if (!e || e == n)
119,500,945✔
52
                return false;
53

54
        if (unit_type_from_string(e + 1) < 0)
111,532,238✔
55
                return false;
56

57
        for (i = n, at = NULL; i < e; i++) {
1,875,640,719✔
58

59
                if (*i == '@' && !at)
1,764,350,170✔
60
                        at = i;
6,488,936✔
61

62
                if (!strchr(VALID_CHARS_WITH_AT, *i))
1,764,350,170✔
63
                        return false;
64
        }
65

66
        if (at == n)
111,290,549✔
67
                return false;
68

69
        if (flags & UNIT_NAME_PLAIN)
111,290,543✔
70
                if (!at)
76,094,395✔
71
                        return true;
72

73
        if (flags & UNIT_NAME_INSTANCE)
38,549,599✔
74
                if (at && e > at + 1)
36,926,133✔
75
                        return true;
76

77
        if (flags & UNIT_NAME_TEMPLATE)
34,573,668✔
78
                if (at && e == at + 1)
4,444,241✔
79
                        return true;
1,662,612✔
80

81
        return false;
82
}
83

84
bool unit_prefix_is_valid(const char *p) {
37,449,495✔
85

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

88
        if (isempty(p))
37,449,495✔
89
                return false;
90

91
        return in_charset(p, VALID_CHARS);
37,449,494✔
92
}
93

94
bool unit_instance_is_valid(const char *i) {
401,439✔
95

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

99
        if (isempty(i))
401,439✔
100
                return false;
101

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

105
        return in_charset(i, "@" VALID_CHARS);
401,437✔
106
}
107

108
bool unit_suffix_is_valid(const char *s) {
522,952✔
109
        if (isempty(s))
522,952✔
110
                return false;
111

112
        if (s[0] != '.')
522,952✔
113
                return false;
114

115
        if (unit_type_from_string(s + 1) < 0)
522,952✔
116
                return false;
1✔
117

118
        return true;
119
}
120

121
int unit_name_to_prefix(const char *n, char **ret) {
30,756,698✔
122
        const char *p;
30,756,698✔
123
        char *s;
30,756,698✔
124

125
        assert(n);
30,756,698✔
126
        assert(ret);
30,756,698✔
127

128
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
30,756,698✔
129
                return -EINVAL;
130

131
        p = strchr(n, '@');
30,756,691✔
132
        if (!p)
30,756,691✔
133
                p = strrchr(n, '.');
29,129,174✔
134

135
        assert_se(p);
29,129,174✔
136

137
        s = strndup(n, p - n);
30,756,691✔
138
        if (!s)
30,756,691✔
139
                return -ENOMEM;
140

141
        *ret = s;
30,756,691✔
142
        return 0;
30,756,691✔
143
}
144

145
UnitNameFlags unit_name_to_instance(const char *n, char **ret) {
986,456✔
146
        const char *p, *d;
986,456✔
147

148
        assert(n);
986,456✔
149

150
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
986,456✔
151
                return -EINVAL;
152

153
        /* Everything past the first @ and before the last . is the instance */
154
        p = strchr(n, '@');
986,440✔
155
        if (!p) {
986,440✔
156
                if (ret)
496,090✔
157
                        *ret = NULL;
459,892✔
158
                return UNIT_NAME_PLAIN;
159
        }
160

161
        p++;
490,350✔
162

163
        d = strrchr(p, '.');
490,350✔
164
        if (!d)
490,350✔
165
                return -EINVAL;
166

167
        if (ret) {
490,350✔
168
                char *i = strndup(p, d-p);
487,028✔
169
                if (!i)
487,028✔
170
                        return -ENOMEM;
171

172
                *ret = i;
487,028✔
173
        }
174
        return d > p ? UNIT_NAME_INSTANCE : UNIT_NAME_TEMPLATE;
490,350✔
175
}
176

177
int unit_name_to_prefix_and_instance(const char *n, char **ret) {
2,129✔
178
        const char *d;
2,129✔
179
        char *s;
2,129✔
180

181
        assert(n);
2,129✔
182
        assert(ret);
2,129✔
183

184
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
2,129✔
185
                return -EINVAL;
186

187
        d = strrchr(n, '.');
2,129✔
188
        if (!d)
2,129✔
189
                return -EINVAL;
190

191
        s = strndup(n, d - n);
2,129✔
192
        if (!s)
2,129✔
193
                return -ENOMEM;
194

195
        *ret = s;
2,129✔
196
        return 0;
2,129✔
197
}
198

199
UnitType unit_name_to_type(const char *n) {
21,396,208✔
200
        const char *e;
21,396,208✔
201

202
        assert(n);
21,396,208✔
203

204
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
21,396,208✔
205
                return _UNIT_TYPE_INVALID;
206

207
        assert_se(e = strrchr(n, '.'));
21,394,508✔
208

209
        return unit_type_from_string(e + 1);
21,394,508✔
210
}
211

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

217
        assert(n);
7,233✔
218
        assert(suffix);
7,233✔
219
        assert(ret);
7,233✔
220

221
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
7,233✔
222
                return -EINVAL;
223

224
        if (!unit_suffix_is_valid(suffix))
7,233✔
225
                return -EINVAL;
226

227
        assert_se(e = strrchr(n, '.'));
7,233✔
228

229
        a = e - n;
7,233✔
230
        b = strlen(suffix);
7,233✔
231

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

236
        strcpy(mempcpy(s, n, a), suffix);
7,233✔
237

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

242
        *ret = TAKE_PTR(s);
7,233✔
243
        return 0;
7,233✔
244
}
245

246
int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
2,944✔
247
        UnitType type;
2,944✔
248

249
        assert(prefix);
2,944✔
250
        assert(suffix);
2,944✔
251
        assert(ret);
2,944✔
252

253
        if (suffix[0] != '.')
2,944✔
254
                return -EINVAL;
255

256
        type = unit_type_from_string(suffix + 1);
2,944✔
257
        if (type < 0)
2,944✔
258
                return type;
259

260
        return unit_name_build_from_type(prefix, instance, type, ret);
2,944✔
261
}
262

263
int unit_name_build_from_type(const char *prefix, const char *instance, UnitType type, char **ret) {
18,726,090✔
264
        _cleanup_free_ char *s = NULL;
18,726,090✔
265
        const char *ut;
18,726,090✔
266

267
        assert(prefix);
18,726,090✔
268
        assert(type >= 0);
18,726,090✔
269
        assert(type < _UNIT_TYPE_MAX);
18,726,090✔
270
        assert(ret);
18,726,090✔
271

272
        if (!unit_prefix_is_valid(prefix))
18,726,090✔
273
                return -EINVAL;
274

275
        ut = unit_type_to_string(type);
18,726,090✔
276

277
        if (instance) {
18,726,090✔
278
                if (!unit_instance_is_valid(instance))
377,327✔
279
                        return -EINVAL;
280

281
                s = strjoin(prefix, "@", instance, ".", ut);
377,327✔
282
        } else
283
                s = strjoin(prefix, ".", ut);
18,348,763✔
284
        if (!s)
18,726,090✔
285
                return -ENOMEM;
286

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

291
        *ret = TAKE_PTR(s);
18,726,090✔
292
        return 0;
18,726,090✔
293
}
294

295
static char *do_escape_char(char c, char *t) {
413,182✔
296
        assert(t);
413,182✔
297

298
        *(t++) = '\\';
413,182✔
299
        *(t++) = 'x';
413,182✔
300
        *(t++) = hexchar(c >> 4);
413,182✔
301
        *(t++) = hexchar(c);
413,182✔
302

303
        return t;
413,182✔
304
}
305

306
static char *do_escape(const char *f, char *t) {
443,622✔
307
        assert(f);
443,622✔
308
        assert(t);
443,622✔
309

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

316
        for (; *f; f++) {
10,322,612✔
317
                if (*f == '/')
9,878,990✔
318
                        *(t++) = '-';
859,365✔
319
                else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
9,019,625✔
320
                        t = do_escape_char(*f, t);
413,154✔
321
                else
322
                        *(t++) = *f;
8,606,471✔
323
        }
324

325
        return t;
443,622✔
326
}
327

328
char* unit_name_escape(const char *f) {
443,622✔
329
        char *r, *t;
443,622✔
330

331
        assert(f);
443,622✔
332

333
        r = new(char, strlen(f)*4+1);
443,622✔
334
        if (!r)
443,622✔
335
                return NULL;
336

337
        t = do_escape(f, r);
443,622✔
338
        *t = 0;
443,622✔
339

340
        return r;
443,622✔
341
}
342

343
int unit_name_unescape(const char *f, char **ret) {
14,363✔
344
        _cleanup_free_ char *r = NULL;
14,363✔
345
        char *t;
14,363✔
346

347
        assert(f);
14,363✔
348
        assert(ret);
14,363✔
349

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

354
        for (t = r; *f; f++) {
281,083✔
355
                if (*f == '-')
266,720✔
356
                        *(t++) = '/';
23,070✔
357
                else if (*f == '\\') {
243,650✔
358
                        int a, b;
7,320✔
359

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

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

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

371
                        *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
7,320✔
372
                        f += 3;
7,320✔
373
                } else
374
                        *(t++) = *f;
236,330✔
375
        }
376

377
        *t = 0;
14,363✔
378

379
        *ret = TAKE_PTR(r);
14,363✔
380

381
        return 0;
14,363✔
382
}
383

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

389
        assert(f);
496,628✔
390
        assert(ret);
496,628✔
391

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

396
        if (empty_or_root(p))
496,628✔
397
                s = strdup("-");
59,321✔
398
        else {
399
                if (!path_is_normalized(p))
437,307✔
400
                        return -EINVAL;
401

402
                /* Truncate trailing slashes and skip leading slashes */
403
                delete_trailing_chars(p, "/");
437,296✔
404
                s = unit_name_escape(skip_leading_chars(p, "/"));
874,592✔
405
        }
406
        if (!s)
496,617✔
407
                return -ENOMEM;
408

409
        *ret = s;
496,617✔
410
        return 0;
496,617✔
411
}
412

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

417
        assert(f);
13,427✔
418

419
        if (isempty(f))
26,852✔
420
                return -EINVAL;
421

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

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

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

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

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

446
        if (ret)
13,414✔
447
                *ret = TAKE_PTR(s);
13,414✔
448

449
        return 0;
450
}
451

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

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

462
        assert(original);
23,302✔
463
        assert(instance);
23,302✔
464
        assert(ret);
23,302✔
465

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

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

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

477
        s = new(char, pl + strlen(instance) + strlen(suffix) + 1);
23,295✔
478
        if (!s)
23,295✔
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);
23,295✔
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))
23,295✔
491
                return -EINVAL;
492

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

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

502
        assert(f);
1,855,581✔
503
        assert(ret);
1,855,581✔
504

505
        if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
1,855,581✔
506
                return -EINVAL;
507

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

511
        a = p - f;
872,363✔
512

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

517
        strcpy(mempcpy(s, f, a + 1), e);
872,363✔
518

519
        *ret = s;
872,363✔
520
        return 0;
872,363✔
521
}
522

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

526
        if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
5,728✔
527
                return false;
528

529
        assert_se(s = strrchr(name, '.'));
5,722✔
530

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

534
        s -= UNIT_NAME_HASH_LENGTH_CHARS;
3,841✔
535
        if (s[-1] != '_')
3,841✔
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
        const char *suffix;
21✔
548
        le64_t h;
21✔
549
        size_t len;
21✔
550

551
        assert(ret);
21✔
552

553
        if (strlen(name) < UNIT_NAME_MAX)
21✔
554
                return -EMSGSIZE;
555

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

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

563
        h = htole64(siphash24_string(name, LONG_UNIT_NAME_HASH_KEY.bytes));
21✔
564

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

569
        assert_se(strlen(hash) == UNIT_NAME_HASH_LENGTH_CHARS);
21✔
570

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

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

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

582
        *ret = TAKE_PTR(n);
21✔
583

584
        return 0;
21✔
585
}
586

587
int unit_name_from_path(const char *path, const char *suffix, char **ret) {
496,166✔
588
        _cleanup_free_ char *p = NULL, *s = NULL;
496,166✔
589
        int r;
496,166✔
590

591
        assert(path);
496,166✔
592
        assert(suffix);
496,166✔
593
        assert(ret);
496,166✔
594

595
        if (!unit_suffix_is_valid(suffix))
496,166✔
596
                return -EINVAL;
597

598
        r = unit_name_path_escape(path, &p);
496,166✔
599
        if (r < 0)
496,166✔
600
                return r;
601

602
        s = strjoin(p, suffix);
496,160✔
603
        if (!s)
496,160✔
604
                return -ENOMEM;
605

606
        if (strlen(s) >= UNIT_NAME_MAX) {
496,160✔
607
                _cleanup_free_ char *n = NULL;
×
608

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

611
                r = unit_name_hash_long(s, &n);
21✔
612
                if (r < 0)
21✔
613
                        return r;
×
614

615
                free_and_replace(s, n);
21✔
616
        }
617

618
        /* Refuse if this for some other reason didn't result in a valid name */
619
        if (!unit_name_is_valid(s, UNIT_NAME_PLAIN))
496,160✔
620
                return -EINVAL;
621

622
        *ret = TAKE_PTR(s);
496,160✔
623
        return 0;
496,160✔
624
}
625

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

630
        assert(prefix);
25✔
631
        assert(path);
25✔
632
        assert(suffix);
25✔
633
        assert(ret);
25✔
634

635
        if (!unit_prefix_is_valid(prefix))
25✔
636
                return -EINVAL;
637

638
        if (!unit_suffix_is_valid(suffix))
25✔
639
                return -EINVAL;
640

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

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

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

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

656
        *ret = TAKE_PTR(s);
23✔
657
        return 0;
23✔
658
}
659

660
int unit_name_to_path(const char *name, char **ret) {
5,720✔
661
        _cleanup_free_ char *prefix = NULL;
5,720✔
662
        int r;
5,720✔
663

664
        assert(name);
5,720✔
665

666
        r = unit_name_to_prefix(name, &prefix);
5,720✔
667
        if (r < 0)
5,720✔
668
                return r;
669

670
        if (unit_name_is_hashed(name))
5,718✔
671
                return -ENAMETOOLONG;
672

673
        return unit_name_path_unescape(prefix, ret);
5,717✔
674
}
675

676
static bool do_escape_mangle(const char *f, bool allow_globs, char *t) {
1,701✔
677
        const char *valid_chars;
1,701✔
678
        bool mangled = false;
1,701✔
679

680
        assert(f);
1,701✔
681
        assert(t);
1,701✔
682

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

688
        valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
1,701✔
689

690
        for (; *f; f++)
33,605✔
691
                if (*f == '/') {
31,904✔
692
                        *(t++) = '-';
17✔
693
                        mangled = true;
17✔
694
                } else if (!strchr(valid_chars, *f)) {
31,887✔
695
                        t = do_escape_char(*f, t);
28✔
696
                        mangled = true;
28✔
697
                } else
698
                        *(t++) = *f;
31,859✔
699
        *t = 0;
1,701✔
700

701
        return mangled;
1,701✔
702
}
703

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

718
        _cleanup_free_ char *s = NULL;
19,532✔
719
        bool mangled, suggest_escape = true, warn = FLAGS_SET(flags, UNIT_NAME_MANGLE_WARN);
19,532✔
720
        int r;
19,532✔
721

722
        assert(name);
19,532✔
723
        assert(suffix);
19,532✔
724
        assert(ret);
19,532✔
725

726
        if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
39,064✔
727
                return -EINVAL;
728

729
        if (!unit_suffix_is_valid(suffix))
19,528✔
730
                return -EINVAL;
731

732
        /* Already a fully valid unit name? If so, no mangling is necessary... */
733
        if (unit_name_is_valid(name, UNIT_NAME_ANY)) {
19,528✔
734
                if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !endswith(name, suffix)) {
17,651✔
735
                        const char *e = ASSERT_PTR(strrchr(name, '.'));
5✔
736

737
                        return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG,
10✔
738
                                              SYNTHETIC_ERRNO(EINVAL),
739
                                              "Unit name \"%s\" has unit type \"%s\", but \"%s\" is expected%s%s.",
740
                                              name, e + 1, suffix + 1,
741
                                              operation ? " " : "", strempty(operation));
742
                }
743

744
                goto good;
17,646✔
745
        }
746

747
        /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
748
        if (string_is_glob(name) && in_charset(name, VALID_CHARS_GLOB)) {
1,877✔
749
                if (FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB))
82✔
750
                        goto good;
82✔
UNCOV
751
                log_full(warn ? LOG_NOTICE : LOG_DEBUG,
×
752
                         "Glob pattern passed%s%s, but globs are not supported for this.",
753
                         operation ? " " : "", strempty(operation));
754
                suggest_escape = false;
755
        }
756

757
        if (path_is_absolute(name)) {
1,795✔
758
                _cleanup_free_ char *n = NULL, *u = NULL;
97✔
759

760
                r = path_simplify_alloc(name, &n);
97✔
761
                if (r < 0)
97✔
762
                        return r;
763

764
                if (is_device_path(n)) {
97✔
765
                        r = unit_name_from_path(n, ".device", &u);
60✔
766
                        if (r >= 0) {
60✔
767
                                if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !streq(suffix, ".device"))
58✔
768
                                        return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG,
2✔
769
                                                              SYNTHETIC_ERRNO(EINVAL),
770
                                                              "Path \"%s\" resolves to unit type \"device\", but \"%s\" is expected%s%s.",
771
                                                              name, suffix + 1,
772
                                                              operation ? " " : "", strempty(operation));
773

774
                                *ret = TAKE_PTR(u);
57✔
775
                                return 1;
57✔
776
                        }
777
                        if (r != -EINVAL)
2✔
778
                                return r;
779
                }
780

781
                r = unit_name_from_path(n, ".mount", &u);
39✔
782
                if (r >= 0) {
39✔
783
                        if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !streq(suffix, ".mount"))
36✔
784
                                return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG,
2✔
785
                                                      SYNTHETIC_ERRNO(EINVAL),
786
                                                      "Path \"%s\" resolves to unit type \"mount\", but \"%s\" is expected%s%s.",
787
                                                      name, suffix + 1,
788
                                                      operation ? " " : "", strempty(operation));
789

790
                        *ret = TAKE_PTR(u);
35✔
791
                        return 1;
35✔
792
                }
793
                if (r != -EINVAL)
3✔
794
                        return r;
795
        }
796

797
        s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
1,701✔
798
        if (!s)
1,701✔
799
                return -ENOMEM;
800

801
        mangled = do_escape_mangle(name, FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB), s);
1,701✔
802
        if (mangled)
1,701✔
803
                log_full(warn ? LOG_NOTICE : LOG_DEBUG,
11✔
804
                         "Invalid unit name \"%s\" escaped as \"%s\"%s.",
805
                         name, s,
806
                         suggest_escape ? " (maybe you should use systemd-escape?)" : "");
807

808
        /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow
809
         * "foo.*" as a valid glob. */
810
        if ((!FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
1,701✔
811
                strcat(s, suffix);
1,695✔
812

813
        /* Make sure mangling didn't grow this too large (but don't do this check if globbing is allowed,
814
         * since globs generally do not qualify as valid unit names) */
815
        if (!FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB) && !unit_name_is_valid(s, UNIT_NAME_ANY))
1,701✔
816
                return -EINVAL;
817

818
        *ret = TAKE_PTR(s);
1,701✔
819
        return 1;
1,701✔
820

821
good:
17,728✔
822
        return strdup_to(ret, name);
17,728✔
823
}
824

825
int slice_build_parent_slice(const char *slice, char **ret) {
13,138✔
826
        assert(slice);
13,138✔
827
        assert(ret);
13,138✔
828

829
        if (!slice_name_is_valid(slice))
13,138✔
830
                return -EINVAL;
13,138✔
831

832
        if (streq(slice, SPECIAL_ROOT_SLICE)) {
13,126✔
833
                *ret = NULL;
997✔
834
                return 0;
997✔
835
        }
836

837
        _cleanup_free_ char *s = strdup(slice);
12,129✔
838
        if (!s)
12,129✔
839
                return -ENOMEM;
840

841
        char *dash = strrchr(s, '-');
12,129✔
842
        if (!dash)
12,129✔
843
                return strdup_to_full(ret, SPECIAL_ROOT_SLICE);
3,485✔
844

845
        /* We know that s ended with .slice before truncation, so we have enough space. */
846
        strcpy(dash, ".slice");
8,644✔
847

848
        *ret = TAKE_PTR(s);
8,644✔
849
        return 1;
8,644✔
850
}
851

852
int slice_build_subslice(const char *slice, const char *name, char **ret) {
226✔
853
        char *subslice;
226✔
854

855
        assert(slice);
226✔
856
        assert(name);
226✔
857
        assert(ret);
226✔
858

859
        if (!slice_name_is_valid(slice))
226✔
860
                return -EINVAL;
861

862
        if (!unit_prefix_is_valid(name))
224✔
863
                return -EINVAL;
864

865
        if (streq(slice, SPECIAL_ROOT_SLICE))
224✔
866
                subslice = strjoin(name, ".slice");
1✔
867
        else {
868
                const char *e;
223✔
869

870
                assert_se(e = endswith(slice, ".slice"));
223✔
871

872
                subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
223✔
873
                if (!subslice)
223✔
874
                        return -ENOMEM;
875

876
                stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
223✔
877
        }
878

879
        *ret = subslice;
224✔
880
        return 0;
224✔
881
}
882

883
bool slice_name_is_valid(const char *name) {
19,950✔
884
        const char *p, *e;
19,950✔
885
        bool dash = false;
19,950✔
886

887
        if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
19,950✔
888
                return false;
889

890
        if (streq(name, SPECIAL_ROOT_SLICE))
19,926✔
891
                return true;
892

893
        e = endswith(name, ".slice");
18,429✔
894
        if (!e)
18,429✔
895
                return false;
896

897
        for (p = name; p < e; p++) {
350,549✔
898

899
                if (*p == '-') {
332,129✔
900

901
                        /* Don't allow initial dash */
902
                        if (p == name)
12,991✔
903
                                return false;
904

905
                        /* Don't allow multiple dashes */
906
                        if (dash)
12,988✔
907
                                return false;
908

909
                        dash = true;
910
                } else
911
                        dash = false;
912
        }
913

914
        /* Don't allow trailing hash */
915
        if (dash)
18,420✔
916
                return false;
2✔
917

918
        return true;
919
}
920

921
bool unit_name_prefix_equal(const char *a, const char *b) {
9,160✔
922
        const char *p, *q;
9,160✔
923

924
        assert(a);
9,160✔
925
        assert(b);
9,160✔
926

927
        if (!unit_name_is_valid(a, UNIT_NAME_ANY) || !unit_name_is_valid(b, UNIT_NAME_ANY))
9,160✔
928
                return false;
929

930
        p = strchr(a, '@');
9,158✔
931
        if (!p)
9,158✔
932
                p = strrchr(a, '.');
6,650✔
933

934
        q = strchr(b, '@');
9,158✔
935
        if (!q)
9,158✔
936
                q = strrchr(b, '.');
6✔
937

938
        assert(p);
9,158✔
939
        assert(q);
9,158✔
940

941
        return memcmp_nn(a, p - a, b, q - b) == 0;
9,158✔
942
}
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