Coverage Report

Created: 2026-08-08 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/systemd/src/basic/unit-name.c
Line
Count
Source
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
34.0M
        ALPHANUMERICAL                          \
21
5.28G
        ":-_.\\"
22
23
/* The same, but also permits the single @ character that may appear */
24
#define VALID_CHARS_WITH_AT                     \
25
5.24G
        "@"                                     \
26
5.24G
        VALID_CHARS
27
28
/* All chars valid in a unit name glob */
29
#define VALID_CHARS_GLOB                        \
30
0
        VALID_CHARS_WITH_AT                     \
31
0
        "[]!-*?"
32
33
0
#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
212k
#define UNIT_NAME_HASH_LENGTH_CHARS 16
35
36
73.6M
bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
37
73.6M
        const char *e, *i, *at;
38
39
73.6M
        assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
40
41
73.6M
        if (_unlikely_(flags == 0))
42
0
                return false;
43
44
73.6M
        if (isempty(n))
45
60.2k
                return false;
46
47
73.5M
        if (strlen(n) >= UNIT_NAME_MAX)
48
474
                return false;
49
50
73.5M
        e = strrchr(n, '.');
51
73.5M
        if (!e || e == n)
52
589k
                return false;
53
54
72.9M
        if (unit_type_from_string(e + 1) < 0)
55
18.7k
                return false;
56
57
5.32G
        for (i = n, at = NULL; i < e; i++) {
58
59
5.24G
                if (*i == '@' && !at)
60
53.8k
                        at = i;
61
62
5.24G
                if (!strchr(VALID_CHARS_WITH_AT, *i))
63
24.3k
                        return false;
64
5.24G
        }
65
66
72.9M
        if (at == n)
67
796
                return false;
68
69
72.9M
        if (flags & UNIT_NAME_PLAIN)
70
54.5M
                if (!at)
71
54.5M
                        return true;
72
73
18.3M
        if (flags & UNIT_NAME_INSTANCE)
74
17.6M
                if (at && e > at + 1)
75
38.0k
                        return true;
76
77
18.3M
        if (flags & UNIT_NAME_TEMPLATE)
78
754k
                if (at && e == at + 1)
79
4.80k
                        return true;
80
81
18.3M
        return false;
82
18.3M
}
83
84
34.0M
bool unit_prefix_is_valid(const char *p) {
85
86
        /* We don't allow additional @ in the prefix string */
87
88
34.0M
        if (isempty(p))
89
0
                return false;
90
91
34.0M
        return in_charset(p, VALID_CHARS);
92
34.0M
}
93
94
2.64k
bool unit_instance_is_valid(const char *i) {
95
96
        /* The max length depends on the length of the string, so we
97
         * don't really check this here. */
98
99
2.64k
        if (isempty(i))
100
0
                return false;
101
102
        /* We allow additional @ in the instance string, we do not
103
         * allow them in the prefix! */
104
105
2.64k
        return in_charset(i, "@" VALID_CHARS);
106
2.64k
}
107
108
893
bool unit_suffix_is_valid(const char *s) {
109
893
        if (isempty(s))
110
0
                return false;
111
112
893
        if (s[0] != '.')
113
0
                return false;
114
115
893
        if (unit_type_from_string(s + 1) < 0)
116
0
                return false;
117
118
893
        return true;
119
893
}
120
121
17.7M
int unit_name_to_prefix(const char *n, char **ret) {
122
17.7M
        const char *p;
123
17.7M
        char *s;
124
125
17.7M
        assert(n);
126
17.7M
        assert(ret);
127
128
17.7M
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
129
0
                return -EINVAL;
130
131
17.7M
        p = strchr(n, '@');
132
17.7M
        if (!p)
133
17.7M
                p = strrchr(n, '.');
134
135
17.7M
        assert_se(p);
136
137
17.7M
        s = strndup(n, p - n);
138
17.7M
        if (!s)
139
0
                return -ENOMEM;
140
141
17.7M
        *ret = s;
142
17.7M
        return 0;
143
17.7M
}
144
145
338k
UnitNameFlags unit_name_to_instance(const char *n, char **ret) {
146
338k
        const char *p, *d;
147
148
338k
        assert(n);
149
150
338k
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
151
0
                return -EINVAL;
152
153
        /* Everything past the first @ and before the last . is the instance */
154
338k
        p = strchr(n, '@');
155
338k
        if (!p) {
156
331k
                if (ret)
157
331k
                        *ret = NULL;
158
331k
                return UNIT_NAME_PLAIN;
159
331k
        }
160
161
7.03k
        p++;
162
163
7.03k
        d = strrchr(p, '.');
164
7.03k
        if (!d)
165
0
                return -EINVAL;
166
167
7.03k
        if (ret) {
168
7.03k
                char *i = strndup(p, d-p);
169
7.03k
                if (!i)
170
0
                        return -ENOMEM;
171
172
7.03k
                *ret = i;
173
7.03k
        }
174
7.03k
        return d > p ? UNIT_NAME_INSTANCE : UNIT_NAME_TEMPLATE;
175
7.03k
}
176
177
3.94k
int unit_name_to_prefix_and_instance(const char *n, char **ret) {
178
3.94k
        const char *d;
179
3.94k
        char *s;
180
181
3.94k
        assert(n);
182
3.94k
        assert(ret);
183
184
3.94k
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
185
0
                return -EINVAL;
186
187
3.94k
        d = strrchr(n, '.');
188
3.94k
        if (!d)
189
0
                return -EINVAL;
190
191
3.94k
        s = strndup(n, d - n);
192
3.94k
        if (!s)
193
0
                return -ENOMEM;
194
195
3.94k
        *ret = s;
196
3.94k
        return 0;
197
3.94k
}
198
199
18.1M
UnitType unit_name_to_type(const char *n) {
200
18.1M
        const char *e;
201
202
18.1M
        assert(n);
203
204
18.1M
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
205
62.1k
                return _UNIT_TYPE_INVALID;
206
207
18.0M
        assert_se(e = strrchr(n, '.'));
208
209
18.0M
        return unit_type_from_string(e + 1);
210
18.1M
}
211
212
0
int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
213
0
        _cleanup_free_ char *s = NULL;
214
0
        size_t a, b;
215
0
        const char *e;
216
217
0
        assert(n);
218
0
        assert(suffix);
219
0
        assert(ret);
220
221
0
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
222
0
                return -EINVAL;
223
224
0
        if (!unit_suffix_is_valid(suffix))
225
0
                return -EINVAL;
226
227
0
        assert_se(e = strrchr(n, '.'));
228
229
0
        a = e - n;
230
0
        b = strlen(suffix);
231
232
0
        s = new(char, a + b + 1);
233
0
        if (!s)
234
0
                return -ENOMEM;
235
236
0
        strcpy(mempcpy(s, n, a), suffix);
237
238
        /* Make sure the name is still valid (i.e. didn't grow too large due to longer suffix) */
239
0
        if (!unit_name_is_valid(s, UNIT_NAME_ANY))
240
0
                return -EINVAL;
241
242
0
        *ret = TAKE_PTR(s);
243
0
        return 0;
244
0
}
245
246
0
int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
247
0
        UnitType type;
248
249
0
        assert(prefix);
250
0
        assert(suffix);
251
0
        assert(ret);
252
253
0
        if (suffix[0] != '.')
254
0
                return -EINVAL;
255
256
0
        type = unit_type_from_string(suffix + 1);
257
0
        if (type < 0)
258
0
                return type;
259
260
0
        return unit_name_build_from_type(prefix, instance, type, ret);
261
0
}
262
263
17.0M
int unit_name_build_from_type(const char *prefix, const char *instance, UnitType type, char **ret) {
264
17.0M
        _cleanup_free_ char *s = NULL;
265
17.0M
        const char *ut;
266
267
17.0M
        assert(prefix);
268
17.0M
        assert(type >= 0);
269
17.0M
        assert(type < _UNIT_TYPE_MAX);
270
17.0M
        assert(ret);
271
272
17.0M
        if (!unit_prefix_is_valid(prefix))
273
0
                return -EINVAL;
274
275
17.0M
        ut = unit_type_to_string(type);
276
277
17.0M
        if (instance) {
278
0
                if (!unit_instance_is_valid(instance))
279
0
                        return -EINVAL;
280
281
0
                s = strjoin(prefix, "@", instance, ".", ut);
282
0
        } else
283
17.0M
                s = strjoin(prefix, ".", ut);
284
17.0M
        if (!s)
285
0
                return -ENOMEM;
286
287
        /* Verify that this didn't grow too large (or otherwise is invalid) */
288
17.0M
        if (!unit_name_is_valid(s, instance ? UNIT_NAME_INSTANCE : UNIT_NAME_PLAIN))
289
0
                return -EINVAL;
290
291
17.0M
        *ret = TAKE_PTR(s);
292
17.0M
        return 0;
293
17.0M
}
294
295
0
static char *do_escape_char(char c, char *t) {
296
0
        assert(t);
297
298
0
        *(t++) = '\\';
299
0
        *(t++) = 'x';
300
0
        *(t++) = hexchar(c >> 4);
301
0
        *(t++) = hexchar(c);
302
303
0
        return t;
304
0
}
305
306
399
static char *do_escape(const char *f, char *t) {
307
399
        assert(f);
308
399
        assert(t);
309
310
        /* do not create units with a leading '.', like for "/.dotdir" mount points */
311
399
        if (*f == '.') {
312
0
                t = do_escape_char(*f, t);
313
0
                f++;
314
0
        }
315
316
2.12k
        for (; *f; f++) {
317
1.72k
                if (*f == '/')
318
133
                        *(t++) = '-';
319
1.59k
                else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
320
0
                        t = do_escape_char(*f, t);
321
1.59k
                else
322
1.59k
                        *(t++) = *f;
323
1.72k
        }
324
325
399
        return t;
326
399
}
327
328
399
char* unit_name_escape(const char *f) {
329
399
        char *r, *t;
330
331
399
        assert(f);
332
333
399
        r = new(char, strlen(f)*4+1);
334
399
        if (!r)
335
0
                return NULL;
336
337
399
        t = do_escape(f, r);
338
399
        *t = 0;
339
340
399
        return r;
341
399
}
342
343
117k
int unit_name_unescape(const char *f, char **ret) {
344
117k
        _cleanup_free_ char *r = NULL;
345
117k
        char *t;
346
347
117k
        assert(f);
348
117k
        assert(ret);
349
350
117k
        r = strdup(f);
351
117k
        if (!r)
352
0
                return -ENOMEM;
353
354
9.83M
        for (t = r; *f; f++) {
355
9.72M
                if (*f == '-')
356
4.13M
                        *(t++) = '/';
357
5.59M
                else if (*f == '\\') {
358
6.31k
                        int a, b;
359
360
6.31k
                        if (f[1] != 'x')
361
3.89k
                                return -EINVAL;
362
363
2.41k
                        a = unhexchar(f[2]);
364
2.41k
                        if (a < 0)
365
460
                                return -EINVAL;
366
367
1.95k
                        b = unhexchar(f[3]);
368
1.95k
                        if (b < 0)
369
332
                                return -EINVAL;
370
371
1.62k
                        *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
372
1.62k
                        f += 3;
373
1.62k
                } else
374
5.58M
                        *(t++) = *f;
375
9.72M
        }
376
377
113k
        *t = 0;
378
379
113k
        *ret = TAKE_PTR(r);
380
381
113k
        return 0;
382
117k
}
383
384
893
int unit_name_path_escape(const char *f, char **ret) {
385
893
        _cleanup_free_ char *p = NULL;
386
893
        char *s;
387
893
        int r;
388
389
893
        assert(f);
390
893
        assert(ret);
391
392
893
        r = path_simplify_alloc(f, &p);
393
893
        if (r < 0)
394
0
                return r;
395
396
893
        if (empty_or_root(p))
397
494
                s = strdup("-");
398
399
        else {
399
399
                if (!path_is_normalized(p))
400
0
                        return -EINVAL;
401
402
                /* Truncate trailing slashes and skip leading slashes */
403
399
                delete_trailing_chars(p, "/");
404
399
                s = unit_name_escape(skip_leading_chars(p, "/"));
405
399
        }
406
893
        if (!s)
407
0
                return -ENOMEM;
408
409
893
        *ret = s;
410
893
        return 0;
411
893
}
412
413
111k
int unit_name_path_unescape(const char *f, char **ret) {
414
111k
        _cleanup_free_ char *s = NULL;
415
111k
        int r;
416
417
111k
        assert(f);
418
419
111k
        if (isempty(f))
420
0
                return -EINVAL;
421
422
111k
        if (streq(f, "-")) {
423
256
                s = strdup("/");
424
256
                if (!s)
425
0
                        return -ENOMEM;
426
111k
        } else {
427
111k
                _cleanup_free_ char *w = NULL;
428
429
111k
                r = unit_name_unescape(f, &w);
430
111k
                if (r < 0)
431
4.69k
                        return r;
432
433
                /* Don't accept trailing or leading slashes */
434
106k
                if (startswith(w, "/") || endswith(w, "/"))
435
594
                        return -EINVAL;
436
437
                /* Prefix a slash again */
438
106k
                s = strjoin("/", w);
439
106k
                if (!s)
440
0
                        return -ENOMEM;
441
442
106k
                if (!path_is_normalized(s))
443
61.5k
                        return -EINVAL;
444
106k
        }
445
446
44.8k
        if (ret)
447
44.8k
                *ret = TAKE_PTR(s);
448
449
44.8k
        return 0;
450
111k
}
451
452
int unit_name_replace_instance_full(
453
                const char *original,
454
                const char *instance,
455
                bool accept_glob,
456
1.09k
                char **ret) {
457
458
1.09k
        _cleanup_free_ char *s = NULL;
459
1.09k
        const char *prefix, *suffix;
460
1.09k
        size_t pl;
461
462
1.09k
        assert(original);
463
1.09k
        assert(instance);
464
1.09k
        assert(ret);
465
466
1.09k
        if (!unit_name_is_valid(original, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
467
0
                return -EINVAL;
468
1.09k
        if (!unit_instance_is_valid(instance) && !(accept_glob && in_charset(instance, VALID_CHARS_GLOB)))
469
0
                return -EINVAL;
470
471
1.09k
        prefix = ASSERT_PTR(strchr(original, '@'));
472
1.09k
        suffix = ASSERT_PTR(strrchr(original, '.'));
473
1.09k
        assert(prefix < suffix);
474
475
1.09k
        pl = prefix - original + 1; /* include '@' */
476
477
1.09k
        s = new(char, pl + strlen(instance) + strlen(suffix) + 1);
478
1.09k
        if (!s)
479
0
                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
1.09k
        strcpy(stpcpy(stpncpy(s, original, pl), instance), suffix);
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
1.09k
        if (!accept_glob && !unit_name_is_valid(s, UNIT_NAME_INSTANCE))
491
136
                return -EINVAL;
492
493
958
        *ret = TAKE_PTR(s);
494
958
        return 0;
495
1.09k
}
496
497
2.90k
int unit_name_template(const char *f, char **ret) {
498
2.90k
        const char *p, *e;
499
2.90k
        char *s;
500
2.90k
        size_t a;
501
502
2.90k
        assert(f);
503
2.90k
        assert(ret);
504
505
2.90k
        if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
506
0
                return -EINVAL;
507
508
2.90k
        assert_se(p = strchr(f, '@'));
509
2.90k
        assert_se(e = strrchr(f, '.'));
510
511
2.90k
        a = p - f;
512
513
2.90k
        s = new(char, a + 1 + strlen(e) + 1);
514
2.90k
        if (!s)
515
0
                return -ENOMEM;
516
517
2.90k
        strcpy(mempcpy(s, f, a + 1), e);
518
519
2.90k
        *ret = s;
520
2.90k
        return 0;
521
2.90k
}
522
523
111k
bool unit_name_is_hashed(const char *name) {
524
111k
        const char *s;
525
526
111k
        if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
527
0
                return false;
528
529
111k
        assert_se(s = strrchr(name, '.'));
530
531
111k
        if (s - name < UNIT_NAME_HASH_LENGTH_CHARS + 1)
532
17.3k
                return false;
533
534
94.6k
        s -= UNIT_NAME_HASH_LENGTH_CHARS;
535
94.6k
        if (s[-1] != '_')
536
92.9k
                return false;
537
538
6.20k
        for (size_t i = 0; i < UNIT_NAME_HASH_LENGTH_CHARS; i++)
539
5.92k
                if (!strchr(LOWERCASE_HEXDIGITS, s[i]))
540
1.38k
                        return false;
541
542
275
        return true;
543
1.65k
}
544
545
0
int unit_name_hash_long(const char *name, char **ret) {
546
0
        _cleanup_free_ char *n = NULL, *hash = NULL;
547
0
        const char *suffix;
548
0
        le64_t h;
549
0
        size_t len;
550
551
0
        assert(ret);
552
553
0
        if (strlen(name) < UNIT_NAME_MAX)
554
0
                return -EMSGSIZE;
555
556
0
        suffix = strrchr(name, '.');
557
0
        if (!suffix)
558
0
                return -EINVAL;
559
560
0
        if (unit_type_from_string(suffix+1) < 0)
561
0
                return -EINVAL;
562
563
0
        h = htole64(siphash24_string(name, LONG_UNIT_NAME_HASH_KEY.bytes));
564
565
0
        hash = hexmem(&h, sizeof(h));
566
0
        if (!hash)
567
0
                return -ENOMEM;
568
569
0
        assert_se(strlen(hash) == UNIT_NAME_HASH_LENGTH_CHARS);
570
571
0
        len = UNIT_NAME_MAX - 1 - strlen(suffix+1) - UNIT_NAME_HASH_LENGTH_CHARS - 2;
572
0
        assert(len > 0 && len < UNIT_NAME_MAX);
573
574
0
        n = strndup(name, len);
575
0
        if (!n)
576
0
                return -ENOMEM;
577
578
0
        if (!strextend(&n, "_", hash, suffix))
579
0
                return -ENOMEM;
580
0
        assert_se(unit_name_is_valid(n, UNIT_NAME_PLAIN));
581
582
0
        *ret = TAKE_PTR(n);
583
584
0
        return 0;
585
0
}
586
587
893
int unit_name_from_path(const char *path, const char *suffix, char **ret) {
588
893
        _cleanup_free_ char *p = NULL, *s = NULL;
589
893
        int r;
590
591
893
        assert(path);
592
893
        assert(suffix);
593
893
        assert(ret);
594
595
893
        if (!unit_suffix_is_valid(suffix))
596
0
                return -EINVAL;
597
598
893
        r = unit_name_path_escape(path, &p);
599
893
        if (r < 0)
600
0
                return r;
601
602
893
        s = strjoin(p, suffix);
603
893
        if (!s)
604
0
                return -ENOMEM;
605
606
893
        if (strlen(s) >= UNIT_NAME_MAX) {
607
0
                _cleanup_free_ char *n = NULL;
608
609
0
                log_debug("Unit name \"%s\" too long, falling back to hashed unit name.", s);
610
611
0
                r = unit_name_hash_long(s, &n);
612
0
                if (r < 0)
613
0
                        return r;
614
615
0
                free_and_replace(s, n);
616
0
        }
617
618
        /* Refuse if this for some other reason didn't result in a valid name */
619
893
        if (!unit_name_is_valid(s, UNIT_NAME_PLAIN))
620
0
                return -EINVAL;
621
622
893
        *ret = TAKE_PTR(s);
623
893
        return 0;
624
893
}
625
626
0
int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
627
0
        _cleanup_free_ char *p = NULL, *s = NULL;
628
0
        int r;
629
630
0
        assert(prefix);
631
0
        assert(path);
632
0
        assert(suffix);
633
0
        assert(ret);
634
635
0
        if (!unit_prefix_is_valid(prefix))
636
0
                return -EINVAL;
637
638
0
        if (!unit_suffix_is_valid(suffix))
639
0
                return -EINVAL;
640
641
0
        r = unit_name_path_escape(path, &p);
642
0
        if (r < 0)
643
0
                return r;
644
645
0
        s = strjoin(prefix, "@", p, suffix);
646
0
        if (!s)
647
0
                return -ENOMEM;
648
649
0
        if (strlen(s) >= UNIT_NAME_MAX) /* Return a slightly more descriptive error for this specific condition */
650
0
                return -ENAMETOOLONG;
651
652
        /* Refuse if this for some other reason didn't result in a valid name */
653
0
        if (!unit_name_is_valid(s, UNIT_NAME_INSTANCE))
654
0
                return -EINVAL;
655
656
0
        *ret = TAKE_PTR(s);
657
0
        return 0;
658
0
}
659
660
111k
int unit_name_to_path(const char *name, char **ret) {
661
111k
        _cleanup_free_ char *prefix = NULL;
662
111k
        int r;
663
664
111k
        assert(name);
665
666
111k
        r = unit_name_to_prefix(name, &prefix);
667
111k
        if (r < 0)
668
0
                return r;
669
670
111k
        if (unit_name_is_hashed(name))
671
275
                return -ENAMETOOLONG;
672
673
111k
        return unit_name_path_unescape(prefix, ret);
674
111k
}
675
676
0
static bool do_escape_mangle(const char *f, bool allow_globs, char *t) {
677
0
        const char *valid_chars;
678
0
        bool mangled = false;
679
680
0
        assert(f);
681
0
        assert(t);
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
0
        valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
689
690
0
        for (; *f; f++)
691
0
                if (*f == '/') {
692
0
                        *(t++) = '-';
693
0
                        mangled = true;
694
0
                } else if (!strchr(valid_chars, *f)) {
695
0
                        t = do_escape_char(*f, t);
696
0
                        mangled = true;
697
0
                } else
698
0
                        *(t++) = *f;
699
0
        *t = 0;
700
701
0
        return mangled;
702
0
}
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(
712
                const char *name,
713
                const char *operation,
714
                UnitNameMangle flags,
715
                const char *suffix,
716
0
                char **ret) {
717
718
0
        _cleanup_free_ char *s = NULL;
719
0
        bool mangled, suggest_escape = true, warn = FLAGS_SET(flags, UNIT_NAME_MANGLE_WARN);
720
0
        int r;
721
722
0
        assert(name);
723
0
        assert(suffix);
724
0
        assert(ret);
725
726
0
        if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
727
0
                return -EINVAL;
728
729
0
        if (!unit_suffix_is_valid(suffix))
730
0
                return -EINVAL;
731
732
        /* Already a fully valid unit name? If so, no mangling is necessary... */
733
0
        if (unit_name_is_valid(name, UNIT_NAME_ANY)) {
734
0
                if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !endswith(name, suffix)) {
735
0
                        const char *e = ASSERT_PTR(strrchr(name, '.'));
736
737
0
                        return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG,
738
0
                                              SYNTHETIC_ERRNO(EINVAL),
739
0
                                              "Unit name \"%s\" has unit type \"%s\", but \"%s\" is expected%s%s.",
740
0
                                              name, e + 1, suffix + 1,
741
0
                                              operation ? " " : "", strempty(operation));
742
0
                }
743
744
0
                goto good;
745
0
        }
746
747
        /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
748
0
        if (string_is_glob(name) && in_charset(name, VALID_CHARS_GLOB)) {
749
0
                if (FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB))
750
0
                        goto good;
751
0
                log_full(warn ? LOG_NOTICE : LOG_DEBUG,
752
0
                         "Glob pattern passed%s%s, but globs are not supported for this.",
753
0
                         operation ? " " : "", strempty(operation));
754
0
                suggest_escape = false;
755
0
        }
756
757
0
        if (path_is_absolute(name)) {
758
0
                _cleanup_free_ char *n = NULL, *u = NULL;
759
760
0
                r = path_simplify_alloc(name, &n);
761
0
                if (r < 0)
762
0
                        return r;
763
764
0
                if (is_device_path(n)) {
765
0
                        r = unit_name_from_path(n, ".device", &u);
766
0
                        if (r >= 0) {
767
0
                                if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !streq(suffix, ".device"))
768
0
                                        return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG,
769
0
                                                              SYNTHETIC_ERRNO(EINVAL),
770
0
                                                              "Path \"%s\" resolves to unit type \"device\", but \"%s\" is expected%s%s.",
771
0
                                                              name, suffix + 1,
772
0
                                                              operation ? " " : "", strempty(operation));
773
774
0
                                *ret = TAKE_PTR(u);
775
0
                                return 1;
776
0
                        }
777
0
                        if (r != -EINVAL)
778
0
                                return r;
779
0
                }
780
781
0
                r = unit_name_from_path(n, ".mount", &u);
782
0
                if (r >= 0) {
783
0
                        if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !streq(suffix, ".mount"))
784
0
                                return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG,
785
0
                                                      SYNTHETIC_ERRNO(EINVAL),
786
0
                                                      "Path \"%s\" resolves to unit type \"mount\", but \"%s\" is expected%s%s.",
787
0
                                                      name, suffix + 1,
788
0
                                                      operation ? " " : "", strempty(operation));
789
790
0
                        *ret = TAKE_PTR(u);
791
0
                        return 1;
792
0
                }
793
0
                if (r != -EINVAL)
794
0
                        return r;
795
0
        }
796
797
0
        s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
798
0
        if (!s)
799
0
                return -ENOMEM;
800
801
0
        mangled = do_escape_mangle(name, FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB), s);
802
0
        if (mangled)
803
0
                log_full(warn ? LOG_NOTICE : LOG_DEBUG,
804
0
                         "Invalid unit name \"%s\" escaped as \"%s\"%s.",
805
0
                         name, s,
806
0
                         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
0
        if ((!FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
811
0
                strcat(s, suffix);
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
0
        if (!FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB) && !unit_name_is_valid(s, UNIT_NAME_ANY))
816
0
                return -EINVAL;
817
818
0
        *ret = TAKE_PTR(s);
819
0
        return 1;
820
821
0
good:
822
0
        return strdup_to(ret, name);
823
0
}
824
825
229k
int slice_build_parent_slice(const char *slice, char **ret) {
826
229k
        assert(slice);
827
229k
        assert(ret);
828
829
229k
        if (!slice_name_is_valid(slice))
830
841
                return -EINVAL;
831
832
228k
        if (streq(slice, SPECIAL_ROOT_SLICE)) {
833
9.35k
                *ret = NULL;
834
9.35k
                return 0;
835
9.35k
        }
836
837
219k
        _cleanup_free_ char *s = strdup(slice);
838
219k
        if (!s)
839
0
                return -ENOMEM;
840
841
219k
        char *dash = strrchr(s, '-');
842
219k
        if (!dash)
843
15.0k
                return strdup_to_full(ret, SPECIAL_ROOT_SLICE);
844
845
        /* We know that s ended with .slice before truncation, so we have enough space. */
846
204k
        strcpy(dash, ".slice");
847
848
204k
        *ret = TAKE_PTR(s);
849
204k
        return 1;
850
219k
}
851
852
0
int slice_build_subslice(const char *slice, const char *name, char **ret) {
853
0
        char *subslice;
854
855
0
        assert(slice);
856
0
        assert(name);
857
0
        assert(ret);
858
859
0
        if (!slice_name_is_valid(slice))
860
0
                return -EINVAL;
861
862
0
        if (!unit_prefix_is_valid(name))
863
0
                return -EINVAL;
864
865
0
        if (streq(slice, SPECIAL_ROOT_SLICE))
866
0
                subslice = strjoin(name, ".slice");
867
0
        else {
868
0
                const char *e;
869
870
0
                assert_se(e = endswith(slice, ".slice"));
871
872
0
                subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
873
0
                if (!subslice)
874
0
                        return -ENOMEM;
875
876
0
                stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
877
0
        }
878
879
0
        *ret = subslice;
880
0
        return 0;
881
0
}
882
883
344k
bool slice_name_is_valid(const char *name) {
884
344k
        const char *p, *e;
885
344k
        bool dash = false;
886
887
344k
        if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
888
0
                return false;
889
890
344k
        if (streq(name, SPECIAL_ROOT_SLICE))
891
14.0k
                return true;
892
893
330k
        e = endswith(name, ".slice");
894
330k
        if (!e)
895
0
                return false;
896
897
30.3M
        for (p = name; p < e; p++) {
898
899
30.0M
                if (*p == '-') {
900
901
                        /* Don't allow initial dash */
902
12.7M
                        if (p == name)
903
361
                                return false;
904
905
                        /* Don't allow multiple dashes */
906
12.7M
                        if (dash)
907
364
                                return false;
908
909
12.7M
                        dash = true;
910
12.7M
                } else
911
17.2M
                        dash = false;
912
30.0M
        }
913
914
        /* Don't allow trailing hash */
915
329k
        if (dash)
916
116
                return false;
917
918
329k
        return true;
919
329k
}
920
921
3.32k
bool unit_name_prefix_equal(const char *a, const char *b) {
922
3.32k
        const char *p, *q;
923
924
3.32k
        assert(a);
925
3.32k
        assert(b);
926
927
3.32k
        if (!unit_name_is_valid(a, UNIT_NAME_ANY) || !unit_name_is_valid(b, UNIT_NAME_ANY))
928
0
                return false;
929
930
3.32k
        p = strchr(a, '@');
931
3.32k
        if (!p)
932
3.32k
                p = strrchr(a, '.');
933
934
3.32k
        q = strchr(b, '@');
935
3.32k
        if (!q)
936
0
                q = strrchr(b, '.');
937
938
3.32k
        assert(p);
939
3.32k
        assert(q);
940
941
3.32k
        return memcmp_nn(a, p - a, b, q - b) == 0;
942
3.32k
}