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

systemd / systemd / 14554080340

19 Apr 2025 11:46AM UTC coverage: 72.101% (-0.03%) from 72.13%
14554080340

push

github

web-flow
Add two new paragraphs to coding style about header files (#37188)

296880 of 411754 relevant lines covered (72.1%)

687547.52 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 <errno.h>
4
#include <stddef.h>
5
#include <stdint.h>
6
#include <stdlib.h>
7

8
#include "sd-id128.h"
9

10
#include "alloc-util.h"
11
#include "glob-util.h"
12
#include "hexdecoct.h"
13
#include "log.h"
14
#include "memory-util.h"
15
#include "path-util.h"
16
#include "random-util.h"
17
#include "sparse-endian.h"
18
#include "special.h"
19
#include "stdio-util.h"
20
#include "string-util.h"
21
#include "strv.h"
22
#include "unit-name.h"
23

24
/* Characters valid in a unit name. */
25
#define VALID_CHARS                             \
26
        DIGITS                                  \
27
        LETTERS                                 \
28
        ":-_.\\"
29

30
/* The same, but also permits the single @ character that may appear */
31
#define VALID_CHARS_WITH_AT                     \
32
        "@"                                     \
33
        VALID_CHARS
34

35
/* All chars valid in a unit name glob */
36
#define VALID_CHARS_GLOB                        \
37
        VALID_CHARS_WITH_AT                     \
38
        "[]!-*?"
39

40
#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)
41
#define UNIT_NAME_HASH_LENGTH_CHARS 16
42

43
bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
44,031,735✔
44
        const char *e, *i, *at;
44,031,735✔
45

46
        assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
44,031,735✔
47

48
        if (_unlikely_(flags == 0))
44,031,735✔
49
                return false;
50

51
        if (isempty(n))
44,031,735✔
52
                return false;
53

54
        if (strlen(n) >= UNIT_NAME_MAX)
44,031,621✔
55
                return false;
56

57
        e = strrchr(n, '.');
44,031,617✔
58
        if (!e || e == n)
44,031,617✔
59
                return false;
60

61
        if (unit_type_from_string(e + 1) < 0)
40,566,989✔
62
                return false;
63

64
        for (i = n, at = NULL; i < e; i++) {
765,803,823✔
65

66
                if (*i == '@' && !at)
725,317,035✔
67
                        at = i;
2,114,824✔
68

69
                if (!strchr(VALID_CHARS_WITH_AT, *i))
725,317,035✔
70
                        return false;
71
        }
72

73
        if (at == n)
40,486,788✔
74
                return false;
75

76
        if (flags & UNIT_NAME_PLAIN)
40,486,782✔
77
                if (!at)
27,756,883✔
78
                        return true;
79

80
        if (flags & UNIT_NAME_INSTANCE)
13,812,068✔
81
                if (at && e > at + 1)
13,139,226✔
82
                        return true;
83

84
        if (flags & UNIT_NAME_TEMPLATE)
12,560,955✔
85
                if (at && e == at + 1)
1,901,448✔
86
                        return true;
590,029✔
87

88
        return false;
89
}
90

91
bool unit_prefix_is_valid(const char *p) {
14,029,898✔
92

93
        /* We don't allow additional @ in the prefix string */
94

95
        if (isempty(p))
14,029,898✔
96
                return false;
97

98
        return in_charset(p, VALID_CHARS);
14,029,897✔
99
}
100

101
bool unit_instance_is_valid(const char *i) {
119,171✔
102

103
        /* The max length depends on the length of the string, so we
104
         * don't really check this here. */
105

106
        if (isempty(i))
119,171✔
107
                return false;
108

109
        /* We allow additional @ in the instance string, we do not
110
         * allow them in the prefix! */
111

112
        return in_charset(i, "@" VALID_CHARS);
119,169✔
113
}
114

115
bool unit_suffix_is_valid(const char *s) {
262,603✔
116
        if (isempty(s))
262,603✔
117
                return false;
118

119
        if (s[0] != '.')
262,603✔
120
                return false;
121

122
        if (unit_type_from_string(s + 1) < 0)
262,603✔
123
                return false;
1✔
124

125
        return true;
126
}
127

128
int unit_name_to_prefix(const char *n, char **ret) {
10,824,401✔
129
        const char *p;
10,824,401✔
130
        char *s;
10,824,401✔
131

132
        assert(n);
10,824,401✔
133
        assert(ret);
10,824,401✔
134

135
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
10,824,401✔
136
                return -EINVAL;
137

138
        p = strchr(n, '@');
10,824,394✔
139
        if (!p)
10,824,394✔
140
                p = strrchr(n, '.');
10,307,168✔
141

142
        assert_se(p);
10,307,168✔
143

144
        s = strndup(n, p - n);
10,824,394✔
145
        if (!s)
10,824,394✔
146
                return -ENOMEM;
147

148
        *ret = s;
10,824,394✔
149
        return 0;
10,824,394✔
150
}
151

152
UnitNameFlags unit_name_to_instance(const char *n, char **ret) {
351,342✔
153
        const char *p, *d;
351,342✔
154

155
        assert(n);
351,342✔
156

157
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
351,342✔
158
                return -EINVAL;
159

160
        /* Everything past the first @ and before the last . is the instance */
161
        p = strchr(n, '@');
351,326✔
162
        if (!p) {
351,326✔
163
                if (ret)
197,875✔
164
                        *ret = NULL;
184,199✔
165
                return UNIT_NAME_PLAIN;
197,875✔
166
        }
167

168
        p++;
153,451✔
169

170
        d = strrchr(p, '.');
153,451✔
171
        if (!d)
153,451✔
172
                return -EINVAL;
173

174
        if (ret) {
153,451✔
175
                char *i = strndup(p, d-p);
151,887✔
176
                if (!i)
151,887✔
177
                        return -ENOMEM;
178

179
                *ret = i;
151,887✔
180
        }
181
        return d > p ? UNIT_NAME_INSTANCE : UNIT_NAME_TEMPLATE;
153,451✔
182
}
183

184
int unit_name_to_prefix_and_instance(const char *n, char **ret) {
1,000✔
185
        const char *d;
1,000✔
186
        char *s;
1,000✔
187

188
        assert(n);
1,000✔
189
        assert(ret);
1,000✔
190

191
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
1,000✔
192
                return -EINVAL;
193

194
        d = strrchr(n, '.');
1,000✔
195
        if (!d)
1,000✔
196
                return -EINVAL;
197

198
        s = strndup(n, d - n);
1,000✔
199
        if (!s)
1,000✔
200
                return -ENOMEM;
201

202
        *ret = s;
1,000✔
203
        return 0;
1,000✔
204
}
205

206
UnitType unit_name_to_type(const char *n) {
7,991,674✔
207
        const char *e;
7,991,674✔
208

209
        assert(n);
7,991,674✔
210

211
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
7,991,674✔
212
                return _UNIT_TYPE_INVALID;
213

214
        assert_se(e = strrchr(n, '.'));
7,990,378✔
215

216
        return unit_type_from_string(e + 1);
7,990,378✔
217
}
218

219
int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
3,263✔
220
        _cleanup_free_ char *s = NULL;
3,263✔
221
        size_t a, b;
3,263✔
222
        char *e;
3,263✔
223

224
        assert(n);
3,263✔
225
        assert(suffix);
3,263✔
226
        assert(ret);
3,263✔
227

228
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
3,263✔
229
                return -EINVAL;
230

231
        if (!unit_suffix_is_valid(suffix))
3,263✔
232
                return -EINVAL;
233

234
        assert_se(e = strrchr(n, '.'));
3,263✔
235

236
        a = e - n;
3,263✔
237
        b = strlen(suffix);
3,263✔
238

239
        s = new(char, a + b + 1);
3,263✔
240
        if (!s)
3,263✔
241
                return -ENOMEM;
242

243
        strcpy(mempcpy(s, n, a), suffix);
3,263✔
244

245
        /* Make sure the name is still valid (i.e. didn't grow too large due to longer suffix) */
246
        if (!unit_name_is_valid(s, UNIT_NAME_ANY))
3,263✔
247
                return -EINVAL;
248

249
        *ret = TAKE_PTR(s);
3,263✔
250
        return 0;
3,263✔
251
}
252

253
int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
1,309✔
254
        UnitType type;
1,309✔
255

256
        assert(prefix);
1,309✔
257
        assert(suffix);
1,309✔
258
        assert(ret);
1,309✔
259

260
        if (suffix[0] != '.')
1,309✔
261
                return -EINVAL;
262

263
        type = unit_type_from_string(suffix + 1);
1,309✔
264
        if (type < 0)
1,309✔
265
                return type;
266

267
        return unit_name_build_from_type(prefix, instance, type, ret);
1,309✔
268
}
269

270
int unit_name_build_from_type(const char *prefix, const char *instance, UnitType type, char **ret) {
7,015,492✔
271
        _cleanup_free_ char *s = NULL;
7,015,492✔
272
        const char *ut;
7,015,492✔
273

274
        assert(prefix);
7,015,492✔
275
        assert(type >= 0);
7,015,492✔
276
        assert(type < _UNIT_TYPE_MAX);
7,015,492✔
277
        assert(ret);
7,015,492✔
278

279
        if (!unit_prefix_is_valid(prefix))
7,015,492✔
280
                return -EINVAL;
281

282
        ut = unit_type_to_string(type);
7,015,492✔
283

284
        if (instance) {
7,015,492✔
285
                if (!unit_instance_is_valid(instance))
110,652✔
286
                        return -EINVAL;
287

288
                s = strjoin(prefix, "@", instance, ".", ut);
110,652✔
289
        } else
290
                s = strjoin(prefix, ".", ut);
6,904,840✔
291
        if (!s)
7,015,492✔
292
                return -ENOMEM;
293

294
        /* Verify that this didn't grow too large (or otherwise is invalid) */
295
        if (!unit_name_is_valid(s, instance ? UNIT_NAME_INSTANCE : UNIT_NAME_PLAIN))
13,920,332✔
296
                return -EINVAL;
297

298
        *ret = TAKE_PTR(s);
7,015,492✔
299
        return 0;
7,015,492✔
300
}
301

302
static char *do_escape_char(char c, char *t) {
181,740✔
303
        assert(t);
181,740✔
304

305
        *(t++) = '\\';
181,740✔
306
        *(t++) = 'x';
181,740✔
307
        *(t++) = hexchar(c >> 4);
181,740✔
308
        *(t++) = hexchar(c);
181,740✔
309

310
        return t;
181,740✔
311
}
312

313
static char *do_escape(const char *f, char *t) {
224,575✔
314
        assert(f);
224,575✔
315
        assert(t);
224,575✔
316

317
        /* do not create units with a leading '.', like for "/.dotdir" mount points */
318
        if (*f == '.') {
224,575✔
319
                t = do_escape_char(*f, t);
×
320
                f++;
×
321
        }
322

323
        for (; *f; f++) {
5,132,240✔
324
                if (*f == '/')
4,907,665✔
325
                        *(t++) = '-';
425,153✔
326
                else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
4,482,512✔
327
                        t = do_escape_char(*f, t);
181,712✔
328
                else
329
                        *(t++) = *f;
4,300,800✔
330
        }
331

332
        return t;
224,575✔
333
}
334

335
char* unit_name_escape(const char *f) {
224,575✔
336
        char *r, *t;
224,575✔
337

338
        assert(f);
224,575✔
339

340
        r = new(char, strlen(f)*4+1);
224,575✔
341
        if (!r)
224,575✔
342
                return NULL;
343

344
        t = do_escape(f, r);
224,575✔
345
        *t = 0;
224,575✔
346

347
        return r;
224,575✔
348
}
349

350
int unit_name_unescape(const char *f, char **ret) {
9,948✔
351
        _cleanup_free_ char *r = NULL;
9,948✔
352
        char *t;
9,948✔
353

354
        assert(f);
9,948✔
355

356
        r = strdup(f);
9,948✔
357
        if (!r)
9,948✔
358
                return -ENOMEM;
359

360
        for (t = r; *f; f++) {
262,785✔
361
                if (*f == '-')
252,837✔
362
                        *(t++) = '/';
20,797✔
363
                else if (*f == '\\') {
232,040✔
364
                        int a, b;
13,430✔
365

366
                        if (f[1] != 'x')
13,430✔
367
                                return -EINVAL;
368

369
                        a = unhexchar(f[2]);
13,430✔
370
                        if (a < 0)
13,430✔
371
                                return -EINVAL;
372

373
                        b = unhexchar(f[3]);
13,430✔
374
                        if (b < 0)
13,430✔
375
                                return -EINVAL;
376

377
                        *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
13,430✔
378
                        f += 3;
13,430✔
379
                } else
380
                        *(t++) = *f;
218,610✔
381
        }
382

383
        *t = 0;
9,948✔
384

385
        *ret = TAKE_PTR(r);
9,948✔
386

387
        return 0;
9,948✔
388
}
389

390
int unit_name_path_escape(const char *f, char **ret) {
251,156✔
391
        _cleanup_free_ char *p = NULL;
251,156✔
392
        char *s;
251,156✔
393
        int r;
251,156✔
394

395
        assert(f);
251,156✔
396
        assert(ret);
251,156✔
397

398
        r = path_simplify_alloc(f, &p);
251,156✔
399
        if (r < 0)
251,156✔
400
                return r;
401

402
        if (empty_or_root(p))
251,156✔
403
                s = strdup("-");
28,768✔
404
        else {
405
                if (!path_is_normalized(p))
222,388✔
406
                        return -EINVAL;
407

408
                /* Truncate trailing slashes and skip leading slashes */
409
                delete_trailing_chars(p, "/");
222,377✔
410
                s = unit_name_escape(skip_leading_chars(p, "/"));
444,754✔
411
        }
412
        if (!s)
251,145✔
413
                return -ENOMEM;
414

415
        *ret = s;
251,145✔
416
        return 0;
251,145✔
417
}
418

419
int unit_name_path_unescape(const char *f, char **ret) {
9,523✔
420
        _cleanup_free_ char *s = NULL;
9,523✔
421
        int r;
9,523✔
422

423
        assert(f);
9,523✔
424

425
        if (isempty(f))
19,044✔
426
                return -EINVAL;
427

428
        if (streq(f, "-")) {
9,521✔
429
                s = strdup("/");
143✔
430
                if (!s)
143✔
431
                        return -ENOMEM;
432
        } else {
433
                _cleanup_free_ char *w = NULL;
9,378✔
434

435
                r = unit_name_unescape(f, &w);
9,378✔
436
                if (r < 0)
9,378✔
437
                        return r;
438

439
                /* Don't accept trailing or leading slashes */
440
                if (startswith(w, "/") || endswith(w, "/"))
9,378✔
441
                        return -EINVAL;
442

443
                /* Prefix a slash again */
444
                s = strjoin("/", w);
9,371✔
445
                if (!s)
9,371✔
446
                        return -ENOMEM;
447

448
                if (!path_is_normalized(s))
9,371✔
449
                        return -EINVAL;
450
        }
451

452
        if (ret)
9,510✔
453
                *ret = TAKE_PTR(s);
9,510✔
454

455
        return 0;
456
}
457

458
int unit_name_replace_instance_full(
7,759✔
459
                const char *original,
460
                const char *instance,
461
                bool accept_glob,
462
                char **ret) {
463

464
        _cleanup_free_ char *s = NULL;
7,759✔
465
        const char *prefix, *suffix;
7,759✔
466
        size_t pl;
7,759✔
467

468
        assert(original);
7,759✔
469
        assert(instance);
7,759✔
470
        assert(ret);
7,759✔
471

472
        if (!unit_name_is_valid(original, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
7,759✔
473
                return -EINVAL;
474
        if (!unit_instance_is_valid(instance) && !(accept_glob && in_charset(instance, VALID_CHARS_GLOB)))
7,753✔
475
                return -EINVAL;
476

477
        prefix = ASSERT_PTR(strchr(original, '@'));
7,752✔
478
        suffix = ASSERT_PTR(strrchr(original, '.'));
7,752✔
479
        assert(prefix < suffix);
7,752✔
480

481
        pl = prefix - original + 1; /* include '@' */
7,752✔
482

483
        s = new(char, pl + strlen(instance) + strlen(suffix) + 1);
7,752✔
484
        if (!s)
7,752✔
485
                return -ENOMEM;
486

487
#if HAS_FEATURE_MEMORY_SANITIZER
488
        /* MSan doesn't like stpncpy... See also https://github.com/google/sanitizers/issues/926 */
489
        memzero(s, pl + strlen(instance) + strlen(suffix) + 1);
490
#endif
491

492
        strcpy(stpcpy(stpncpy(s, original, pl), instance), suffix);
7,752✔
493

494
        /* Make sure the resulting name still is valid, i.e. didn't grow too large. Globs will be expanded
495
         * by clients when used, so the check is pointless. */
496
        if (!accept_glob && !unit_name_is_valid(s, UNIT_NAME_INSTANCE))
7,752✔
497
                return -EINVAL;
498

499
        *ret = TAKE_PTR(s);
7,752✔
500
        return 0;
7,752✔
501
}
502

503
int unit_name_template(const char *f, char **ret) {
773,101✔
504
        const char *p, *e;
773,101✔
505
        char *s;
773,101✔
506
        size_t a;
773,101✔
507

508
        assert(f);
773,101✔
509
        assert(ret);
773,101✔
510

511
        if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
773,101✔
512
                return -EINVAL;
513

514
        assert_se(p = strchr(f, '@'));
293,926✔
515
        assert_se(e = strrchr(f, '.'));
293,926✔
516

517
        a = p - f;
293,926✔
518

519
        s = new(char, a + 1 + strlen(e) + 1);
293,926✔
520
        if (!s)
293,926✔
521
                return -ENOMEM;
522

523
        strcpy(mempcpy(s, f, a + 1), e);
293,926✔
524

525
        *ret = s;
293,926✔
526
        return 0;
293,926✔
527
}
528

529
bool unit_name_is_hashed(const char *name) {
1,988✔
530
        char *s;
1,988✔
531

532
        if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
1,988✔
533
                return false;
534

535
        assert_se(s = strrchr(name, '.'));
1,982✔
536

537
        if (s - name < UNIT_NAME_HASH_LENGTH_CHARS + 1)
1,982✔
538
                return false;
539

540
        s -= UNIT_NAME_HASH_LENGTH_CHARS;
1,129✔
541
        if (s[-1] != '_')
1,129✔
542
                return false;
543

544
        for (size_t i = 0; i < UNIT_NAME_HASH_LENGTH_CHARS; i++)
46✔
545
                if (!strchr(LOWERCASE_HEXDIGITS, s[i]))
44✔
546
                        return false;
547

548
        return true;
549
}
550

551
int unit_name_hash_long(const char *name, char **ret) {
21✔
552
        _cleanup_free_ char *n = NULL, *hash = NULL;
21✔
553
        char *suffix;
21✔
554
        le64_t h;
21✔
555
        size_t len;
21✔
556

557
        if (strlen(name) < UNIT_NAME_MAX)
21✔
558
                return -EMSGSIZE;
559

560
        suffix = strrchr(name, '.');
21✔
561
        if (!suffix)
21✔
562
                return -EINVAL;
563

564
        if (unit_type_from_string(suffix+1) < 0)
21✔
565
                return -EINVAL;
566

567
        h = htole64(siphash24_string(name, LONG_UNIT_NAME_HASH_KEY.bytes));
21✔
568

569
        hash = hexmem(&h, sizeof(h));
21✔
570
        if (!hash)
21✔
571
                return -ENOMEM;
572

573
        assert_se(strlen(hash) == UNIT_NAME_HASH_LENGTH_CHARS);
21✔
574

575
        len = UNIT_NAME_MAX - 1 - strlen(suffix+1) - UNIT_NAME_HASH_LENGTH_CHARS - 2;
21✔
576
        assert(len > 0 && len < UNIT_NAME_MAX);
21✔
577

578
        n = strndup(name, len);
21✔
579
        if (!n)
21✔
580
                return -ENOMEM;
581

582
        if (!strextend(&n, "_", hash, suffix))
21✔
583
                return -ENOMEM;
584
        assert_se(unit_name_is_valid(n, UNIT_NAME_PLAIN));
21✔
585

586
        *ret = TAKE_PTR(n);
21✔
587

588
        return 0;
21✔
589
}
590

591
int unit_name_from_path(const char *path, const char *suffix, char **ret) {
246,483✔
592
        _cleanup_free_ char *p = NULL, *s = NULL;
246,483✔
593
        int r;
246,483✔
594

595
        assert(path);
246,483✔
596
        assert(suffix);
246,483✔
597
        assert(ret);
246,483✔
598

599
        if (!unit_suffix_is_valid(suffix))
246,483✔
600
                return -EINVAL;
601

602
        r = unit_name_path_escape(path, &p);
246,483✔
603
        if (r < 0)
246,483✔
604
                return r;
605

606
        s = strjoin(p, suffix);
246,477✔
607
        if (!s)
246,477✔
608
                return -ENOMEM;
609

610
        if (strlen(s) >= UNIT_NAME_MAX) {
246,477✔
611
                _cleanup_free_ char *n = NULL;
×
612

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

615
                r = unit_name_hash_long(s, &n);
21✔
616
                if (r < 0)
21✔
617
                        return r;
×
618

619
                free_and_replace(s, n);
21✔
620
        }
621

622
        /* Refuse if this for some other reason didn't result in a valid name */
623
        if (!unit_name_is_valid(s, UNIT_NAME_PLAIN))
246,477✔
624
                return -EINVAL;
625

626
        *ret = TAKE_PTR(s);
246,477✔
627
        return 0;
246,477✔
628
}
629

630
int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
25✔
631
        _cleanup_free_ char *p = NULL, *s = NULL;
25✔
632
        int r;
25✔
633

634
        assert(prefix);
25✔
635
        assert(path);
25✔
636
        assert(suffix);
25✔
637
        assert(ret);
25✔
638

639
        if (!unit_prefix_is_valid(prefix))
25✔
640
                return -EINVAL;
641

642
        if (!unit_suffix_is_valid(suffix))
25✔
643
                return -EINVAL;
644

645
        r = unit_name_path_escape(path, &p);
24✔
646
        if (r < 0)
24✔
647
                return r;
648

649
        s = strjoin(prefix, "@", p, suffix);
23✔
650
        if (!s)
23✔
651
                return -ENOMEM;
652

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

656
        /* Refuse if this for some other reason didn't result in a valid name */
657
        if (!unit_name_is_valid(s, UNIT_NAME_INSTANCE))
23✔
658
                return -EINVAL;
659

660
        *ret = TAKE_PTR(s);
23✔
661
        return 0;
23✔
662
}
663

664
int unit_name_to_path(const char *name, char **ret) {
1,980✔
665
        _cleanup_free_ char *prefix = NULL;
1,980✔
666
        int r;
1,980✔
667

668
        assert(name);
1,980✔
669

670
        r = unit_name_to_prefix(name, &prefix);
1,980✔
671
        if (r < 0)
1,980✔
672
                return r;
673

674
        if (unit_name_is_hashed(name))
1,978✔
675
                return -ENAMETOOLONG;
676

677
        return unit_name_path_unescape(prefix, ret);
1,977✔
678
}
679

680
static bool do_escape_mangle(const char *f, bool allow_globs, char *t) {
1,297✔
681
        const char *valid_chars;
1,297✔
682
        bool mangled = false;
1,297✔
683

684
        assert(f);
1,297✔
685
        assert(t);
1,297✔
686

687
        /* We'll only escape the obvious characters here, to play safe.
688
         *
689
         * Returns true if any characters were mangled, false otherwise.
690
         */
691

692
        valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
1,297✔
693

694
        for (; *f; f++)
23,698✔
695
                if (*f == '/') {
22,401✔
696
                        *(t++) = '-';
17✔
697
                        mangled = true;
17✔
698
                } else if (!strchr(valid_chars, *f)) {
22,384✔
699
                        t = do_escape_char(*f, t);
28✔
700
                        mangled = true;
28✔
701
                } else
702
                        *(t++) = *f;
22,356✔
703
        *t = 0;
1,297✔
704

705
        return mangled;
1,297✔
706
}
707

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

722
        _cleanup_free_ char *s = NULL;
12,836✔
723
        bool mangled, suggest_escape = true, warn = flags & UNIT_NAME_MANGLE_WARN;
12,836✔
724
        int r;
12,836✔
725

726
        assert(name);
12,836✔
727
        assert(suffix);
12,836✔
728
        assert(ret);
12,836✔
729

730
        if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
25,672✔
731
                return -EINVAL;
732

733
        if (!unit_suffix_is_valid(suffix))
12,832✔
734
                return -EINVAL;
735

736
        /* Already a fully valid unit name? If so, no mangling is necessary... */
737
        if (unit_name_is_valid(name, UNIT_NAME_ANY))
12,832✔
738
                goto good;
11,366✔
739

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

750
        if (path_is_absolute(name)) {
1,387✔
751
                _cleanup_free_ char *n = NULL;
93✔
752

753
                r = path_simplify_alloc(name, &n);
93✔
754
                if (r < 0)
93✔
755
                        return r;
756

757
                if (is_device_path(n)) {
93✔
758
                        r = unit_name_from_path(n, ".device", ret);
58✔
759
                        if (r >= 0)
58✔
760
                                return 1;
761
                        if (r != -EINVAL)
2✔
762
                                return r;
763
                }
764

765
                r = unit_name_from_path(n, ".mount", ret);
37✔
766
                if (r >= 0)
37✔
767
                        return 1;
768
                if (r != -EINVAL)
3✔
769
                        return r;
770
        }
771

772
        s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
1,297✔
773
        if (!s)
1,297✔
774
                return -ENOMEM;
775

776
        mangled = do_escape_mangle(name, flags & UNIT_NAME_MANGLE_GLOB, s);
1,297✔
777
        if (mangled)
1,297✔
778
                log_full(warn ? LOG_NOTICE : LOG_DEBUG,
17✔
779
                         "Invalid unit name \"%s\" escaped as \"%s\"%s.",
780
                         name, s,
781
                         suggest_escape ? " (maybe you should use systemd-escape?)" : "");
782

783
        /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow
784
         * "foo.*" as a valid glob. */
785
        if ((!(flags & UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0)
1,297✔
786
                strcat(s, suffix);
1,291✔
787

788
        /* Make sure mangling didn't grow this too large (but don't do this check if globbing is allowed,
789
         * since globs generally do not qualify as valid unit names) */
790
        if (!FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB) && !unit_name_is_valid(s, UNIT_NAME_ANY))
1,297✔
791
                return -EINVAL;
792

793
        *ret = TAKE_PTR(s);
1,297✔
794
        return 1;
1,297✔
795

796
good:
11,445✔
797
        return strdup_to(ret, name);
11,445✔
798
}
799

800
int slice_build_parent_slice(const char *slice, char **ret) {
5,204✔
801
        assert(slice);
5,204✔
802
        assert(ret);
5,204✔
803

804
        if (!slice_name_is_valid(slice))
5,204✔
805
                return -EINVAL;
5,204✔
806

807
        if (streq(slice, SPECIAL_ROOT_SLICE)) {
5,192✔
808
                *ret = NULL;
593✔
809
                return 0;
593✔
810
        }
811

812
        _cleanup_free_ char *s = strdup(slice);
4,599✔
813
        if (!s)
4,599✔
814
                return -ENOMEM;
815

816
        char *dash = strrchr(s, '-');
4,599✔
817
        if (!dash)
4,599✔
818
                return strdup_to_full(ret, SPECIAL_ROOT_SLICE);
1,737✔
819

820
        /* We know that s ended with .slice before truncation, so we have enough space. */
821
        strcpy(dash, ".slice");
2,862✔
822

823
        *ret = TAKE_PTR(s);
2,862✔
824
        return 1;
2,862✔
825
}
826

827
int slice_build_subslice(const char *slice, const char *name, char **ret) {
190✔
828
        char *subslice;
190✔
829

830
        assert(slice);
190✔
831
        assert(name);
190✔
832
        assert(ret);
190✔
833

834
        if (!slice_name_is_valid(slice))
190✔
835
                return -EINVAL;
836

837
        if (!unit_prefix_is_valid(name))
188✔
838
                return -EINVAL;
839

840
        if (streq(slice, SPECIAL_ROOT_SLICE))
188✔
841
                subslice = strjoin(name, ".slice");
1✔
842
        else {
843
                char *e;
187✔
844

845
                assert_se(e = endswith(slice, ".slice"));
187✔
846

847
                subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
187✔
848
                if (!subslice)
187✔
849
                        return -ENOMEM;
850

851
                stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
187✔
852
        }
853

854
        *ret = subslice;
188✔
855
        return 0;
188✔
856
}
857

858
bool slice_name_is_valid(const char *name) {
8,013✔
859
        const char *p, *e;
8,013✔
860
        bool dash = false;
8,013✔
861

862
        if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
8,013✔
863
                return false;
864

865
        if (streq(name, SPECIAL_ROOT_SLICE))
7,989✔
866
                return true;
867

868
        e = endswith(name, ".slice");
7,098✔
869
        if (!e)
7,098✔
870
                return false;
871

872
        for (p = name; p < e; p++) {
115,031✔
873

874
                if (*p == '-') {
107,942✔
875

876
                        /* Don't allow initial dash */
877
                        if (p == name)
4,324✔
878
                                return false;
879

880
                        /* Don't allow multiple dashes */
881
                        if (dash)
4,321✔
882
                                return false;
883

884
                        dash = true;
885
                } else
886
                        dash = false;
887
        }
888

889
        /* Don't allow trailing hash */
890
        if (dash)
7,089✔
891
                return false;
2✔
892

893
        return true;
894
}
895

896
bool unit_name_prefix_equal(const char *a, const char *b) {
3,586✔
897
        const char *p, *q;
3,586✔
898

899
        assert(a);
3,586✔
900
        assert(b);
3,586✔
901

902
        if (!unit_name_is_valid(a, UNIT_NAME_ANY) || !unit_name_is_valid(b, UNIT_NAME_ANY))
3,586✔
903
                return false;
904

905
        p = strchr(a, '@');
3,584✔
906
        if (!p)
3,584✔
907
                p = strrchr(a, '.');
2,975✔
908

909
        q = strchr(b, '@');
3,584✔
910
        if (!q)
3,584✔
911
                q = strrchr(b, '.');
6✔
912

913
        assert(p);
3,584✔
914
        assert(q);
3,584✔
915

916
        return memcmp_nn(a, p - a, b, q - b) == 0;
3,584✔
917
}
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