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

systemd / systemd / 14895667988

07 May 2025 08:57PM UTC coverage: 72.225% (-0.007%) from 72.232%
14895667988

push

github

yuwata
network: log_link_message_debug_errno() automatically append %m if necessary

Follow-up for d28746ef5.
Fixes CID#1609753.

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

20297 existing lines in 338 files now uncovered.

297407 of 411780 relevant lines covered (72.22%)

695716.85 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 "hash-funcs.h"
13
#include "hexdecoct.h"
14
#include "log.h"
15
#include "memory-util.h"
16
#include "path-util.h"
17
#include "random-util.h"
18
#include "sparse-endian.h"
19
#include "special.h"
20
#include "stdio-util.h"
21
#include "string-util.h"
22
#include "strv.h"
23
#include "unit-name.h"
24

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

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

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

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

44
bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
44,791,175✔
45
        const char *e, *i, *at;
44,791,175✔
46

47
        assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
44,791,175✔
48

49
        if (_unlikely_(flags == 0))
44,791,175✔
50
                return false;
51

52
        if (isempty(n))
44,791,175✔
53
                return false;
54

55
        if (strlen(n) >= UNIT_NAME_MAX)
44,791,061✔
56
                return false;
57

58
        e = strrchr(n, '.');
44,791,057✔
59
        if (!e || e == n)
44,791,057✔
60
                return false;
61

62
        if (unit_type_from_string(e + 1) < 0)
41,264,235✔
63
                return false;
64

65
        for (i = n, at = NULL; i < e; i++) {
774,383,317✔
66

67
                if (*i == '@' && !at)
733,200,298✔
68
                        at = i;
2,156,833✔
69

70
                if (!strchr(VALID_CHARS_WITH_AT, *i))
733,200,298✔
71
                        return false;
72
        }
73

74
        if (at == n)
41,183,019✔
75
                return false;
76

77
        if (flags & UNIT_NAME_PLAIN)
41,183,013✔
78
                if (!at)
28,235,194✔
79
                        return true;
80

81
        if (flags & UNIT_NAME_INSTANCE)
14,052,788✔
82
                if (at && e > at + 1)
13,367,828✔
83
                        return true;
84

85
        if (flags & UNIT_NAME_TEMPLATE)
12,773,575✔
86
                if (at && e == at + 1)
1,932,067✔
87
                        return true;
598,243✔
88

89
        return false;
90
}
91

92
bool unit_prefix_is_valid(const char *p) {
14,264,072✔
93

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

96
        if (isempty(p))
14,264,072✔
97
                return false;
98

99
        return in_charset(p, VALID_CHARS);
14,264,071✔
100
}
101

102
bool unit_instance_is_valid(const char *i) {
120,935✔
103

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

107
        if (isempty(i))
120,935✔
108
                return false;
109

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

113
        return in_charset(i, "@" VALID_CHARS);
120,933✔
114
}
115

116
bool unit_suffix_is_valid(const char *s) {
274,826✔
117
        if (isempty(s))
274,826✔
118
                return false;
119

120
        if (s[0] != '.')
274,826✔
121
                return false;
122

123
        if (unit_type_from_string(s + 1) < 0)
274,826✔
124
                return false;
1✔
125

126
        return true;
127
}
128

129
int unit_name_to_prefix(const char *n, char **ret) {
11,010,462✔
130
        const char *p;
11,010,462✔
131
        char *s;
11,010,462✔
132

133
        assert(n);
11,010,462✔
134
        assert(ret);
11,010,462✔
135

136
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
11,010,462✔
137
                return -EINVAL;
138

139
        p = strchr(n, '@');
11,010,455✔
140
        if (!p)
11,010,455✔
141
                p = strrchr(n, '.');
10,482,398✔
142

143
        assert_se(p);
10,482,398✔
144

145
        s = strndup(n, p - n);
11,010,455✔
146
        if (!s)
11,010,455✔
147
                return -ENOMEM;
148

149
        *ret = s;
11,010,455✔
150
        return 0;
11,010,455✔
151
}
152

153
UnitNameFlags unit_name_to_instance(const char *n, char **ret) {
356,951✔
154
        const char *p, *d;
356,951✔
155

156
        assert(n);
356,951✔
157

158
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
356,951✔
159
                return -EINVAL;
160

161
        /* Everything past the first @ and before the last . is the instance */
162
        p = strchr(n, '@');
356,935✔
163
        if (!p) {
356,935✔
164
                if (ret)
201,468✔
165
                        *ret = NULL;
187,343✔
166
                return UNIT_NAME_PLAIN;
201,468✔
167
        }
168

169
        p++;
155,467✔
170

171
        d = strrchr(p, '.');
155,467✔
172
        if (!d)
155,467✔
173
                return -EINVAL;
174

175
        if (ret) {
155,467✔
176
                char *i = strndup(p, d-p);
153,875✔
177
                if (!i)
153,875✔
178
                        return -ENOMEM;
179

180
                *ret = i;
153,875✔
181
        }
182
        return d > p ? UNIT_NAME_INSTANCE : UNIT_NAME_TEMPLATE;
155,467✔
183
}
184

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

189
        assert(n);
1,004✔
190
        assert(ret);
1,004✔
191

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

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

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

203
        *ret = s;
1,004✔
204
        return 0;
1,004✔
205
}
206

207
UnitType unit_name_to_type(const char *n) {
8,127,205✔
208
        const char *e;
8,127,205✔
209

210
        assert(n);
8,127,205✔
211

212
        if (!unit_name_is_valid(n, UNIT_NAME_ANY))
8,127,205✔
213
                return _UNIT_TYPE_INVALID;
214

215
        assert_se(e = strrchr(n, '.'));
8,125,908✔
216

217
        return unit_type_from_string(e + 1);
8,125,908✔
218
}
219

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

225
        assert(n);
3,311✔
226
        assert(suffix);
3,311✔
227
        assert(ret);
3,311✔
228

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

232
        if (!unit_suffix_is_valid(suffix))
3,311✔
233
                return -EINVAL;
234

235
        assert_se(e = strrchr(n, '.'));
3,311✔
236

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

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

244
        strcpy(mempcpy(s, n, a), suffix);
3,311✔
245

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

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

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

257
        assert(prefix);
1,318✔
258
        assert(suffix);
1,318✔
259
        assert(ret);
1,318✔
260

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

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

268
        return unit_name_build_from_type(prefix, instance, type, ret);
1,318✔
269
}
270

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

275
        assert(prefix);
7,132,583✔
276
        assert(type >= 0);
7,132,583✔
277
        assert(type < _UNIT_TYPE_MAX);
7,132,583✔
278
        assert(ret);
7,132,583✔
279

280
        if (!unit_prefix_is_valid(prefix))
7,132,583✔
281
                return -EINVAL;
282

283
        ut = unit_type_to_string(type);
7,132,583✔
284

285
        if (instance) {
7,132,583✔
286
                if (!unit_instance_is_valid(instance))
112,225✔
287
                        return -EINVAL;
288

289
                s = strjoin(prefix, "@", instance, ".", ut);
112,225✔
290
        } else
291
                s = strjoin(prefix, ".", ut);
7,020,358✔
292
        if (!s)
7,132,583✔
293
                return -ENOMEM;
294

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

299
        *ret = TAKE_PTR(s);
7,132,583✔
300
        return 0;
7,132,583✔
301
}
302

303
static char *do_escape_char(char c, char *t) {
191,579✔
304
        assert(t);
191,579✔
305

306
        *(t++) = '\\';
191,579✔
307
        *(t++) = 'x';
191,579✔
308
        *(t++) = hexchar(c >> 4);
191,579✔
309
        *(t++) = hexchar(c);
191,579✔
310

311
        return t;
191,579✔
312
}
313

314
static char *do_escape(const char *f, char *t) {
236,019✔
315
        assert(f);
236,019✔
316
        assert(t);
236,019✔
317

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

324
        for (; *f; f++) {
5,375,914✔
325
                if (*f == '/')
5,139,895✔
326
                        *(t++) = '-';
449,192✔
327
                else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
4,690,703✔
328
                        t = do_escape_char(*f, t);
191,551✔
329
                else
330
                        *(t++) = *f;
4,499,152✔
331
        }
332

333
        return t;
236,019✔
334
}
335

336
char* unit_name_escape(const char *f) {
236,019✔
337
        char *r, *t;
236,019✔
338

339
        assert(f);
236,019✔
340

341
        r = new(char, strlen(f)*4+1);
236,019✔
342
        if (!r)
236,019✔
343
                return NULL;
344

345
        t = do_escape(f, r);
236,019✔
346
        *t = 0;
236,019✔
347

348
        return r;
236,019✔
349
}
350

351
int unit_name_unescape(const char *f, char **ret) {
10,056✔
352
        _cleanup_free_ char *r = NULL;
10,056✔
353
        char *t;
10,056✔
354

355
        assert(f);
10,056✔
356

357
        r = strdup(f);
10,056✔
358
        if (!r)
10,056✔
359
                return -ENOMEM;
360

361
        for (t = r; *f; f++) {
264,779✔
362
                if (*f == '-')
254,723✔
363
                        *(t++) = '/';
20,956✔
364
                else if (*f == '\\') {
233,767✔
365
                        int a, b;
13,450✔
366

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

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

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

378
                        *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
13,450✔
379
                        f += 3;
13,450✔
380
                } else
381
                        *(t++) = *f;
220,317✔
382
        }
383

384
        *t = 0;
10,056✔
385

386
        *ret = TAKE_PTR(r);
10,056✔
387

388
        return 0;
10,056✔
389
}
390

391
int unit_name_path_escape(const char *f, char **ret) {
263,271✔
392
        _cleanup_free_ char *p = NULL;
263,271✔
393
        char *s;
263,271✔
394
        int r;
263,271✔
395

396
        assert(f);
263,271✔
397
        assert(ret);
263,271✔
398

399
        r = path_simplify_alloc(f, &p);
263,271✔
400
        if (r < 0)
263,271✔
401
                return r;
402

403
        if (empty_or_root(p))
263,271✔
404
                s = strdup("-");
29,474✔
405
        else {
406
                if (!path_is_normalized(p))
233,797✔
407
                        return -EINVAL;
408

409
                /* Truncate trailing slashes and skip leading slashes */
410
                delete_trailing_chars(p, "/");
233,786✔
411
                s = unit_name_escape(skip_leading_chars(p, "/"));
467,572✔
412
        }
413
        if (!s)
263,260✔
414
                return -ENOMEM;
415

416
        *ret = s;
263,260✔
417
        return 0;
263,260✔
418
}
419

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

424
        assert(f);
9,623✔
425

426
        if (isempty(f))
19,244✔
427
                return -EINVAL;
428

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

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

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

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

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

453
        if (ret)
9,610✔
454
                *ret = TAKE_PTR(s);
9,610✔
455

456
        return 0;
457
}
458

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

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

469
        assert(original);
7,950✔
470
        assert(instance);
7,950✔
471
        assert(ret);
7,950✔
472

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

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

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

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

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

493
        strcpy(stpcpy(stpncpy(s, original, pl), instance), suffix);
7,943✔
494

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

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

504
int unit_name_template(const char *f, char **ret) {
787,963✔
505
        const char *p, *e;
787,963✔
506
        char *s;
787,963✔
507
        size_t a;
787,963✔
508

509
        assert(f);
787,963✔
510
        assert(ret);
787,963✔
511

512
        if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
787,963✔
513
                return -EINVAL;
514

515
        assert_se(p = strchr(f, '@'));
299,557✔
516
        assert_se(e = strrchr(f, '.'));
299,557✔
517

518
        a = p - f;
299,557✔
519

520
        s = new(char, a + 1 + strlen(e) + 1);
299,557✔
521
        if (!s)
299,557✔
522
                return -ENOMEM;
523

524
        strcpy(mempcpy(s, f, a + 1), e);
299,557✔
525

526
        *ret = s;
299,557✔
527
        return 0;
299,557✔
528
}
529

530
bool unit_name_is_hashed(const char *name) {
2,022✔
531
        char *s;
2,022✔
532

533
        if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
2,022✔
534
                return false;
535

536
        assert_se(s = strrchr(name, '.'));
2,016✔
537

538
        if (s - name < UNIT_NAME_HASH_LENGTH_CHARS + 1)
2,016✔
539
                return false;
540

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

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

549
        return true;
550
}
551

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

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

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

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

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

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

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

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

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

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

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

589
        return 0;
21✔
590
}
591

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

596
        assert(path);
258,593✔
597
        assert(suffix);
258,593✔
598
        assert(ret);
258,593✔
599

600
        if (!unit_suffix_is_valid(suffix))
258,593✔
601
                return -EINVAL;
602

603
        r = unit_name_path_escape(path, &p);
258,593✔
604
        if (r < 0)
258,593✔
605
                return r;
606

607
        s = strjoin(p, suffix);
258,587✔
608
        if (!s)
258,587✔
609
                return -ENOMEM;
610

611
        if (strlen(s) >= UNIT_NAME_MAX) {
258,587✔
UNCOV
612
                _cleanup_free_ char *n = NULL;
×
613

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

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

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

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

627
        *ret = TAKE_PTR(s);
258,587✔
628
        return 0;
258,587✔
629
}
630

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

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

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

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

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

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

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

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

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

665
int unit_name_to_path(const char *name, char **ret) {
2,014✔
666
        _cleanup_free_ char *prefix = NULL;
2,014✔
667
        int r;
2,014✔
668

669
        assert(name);
2,014✔
670

671
        r = unit_name_to_prefix(name, &prefix);
2,014✔
672
        if (r < 0)
2,014✔
673
                return r;
674

675
        if (unit_name_is_hashed(name))
2,012✔
676
                return -ENAMETOOLONG;
677

678
        return unit_name_path_unescape(prefix, ret);
2,011✔
679
}
680

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

685
        assert(f);
1,298✔
686
        assert(t);
1,298✔
687

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

693
        valid_chars = allow_globs ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
1,298✔
694

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

706
        return mangled;
1,298✔
707
}
708

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

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

727
        assert(name);
12,901✔
728
        assert(suffix);
12,901✔
729
        assert(ret);
12,901✔
730

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

734
        if (!unit_suffix_is_valid(suffix))
12,897✔
735
                return -EINVAL;
736

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

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

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

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

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

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

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

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

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

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

794
        *ret = TAKE_PTR(s);
1,298✔
795
        return 1;
1,298✔
796

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

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

805
        if (!slice_name_is_valid(slice))
5,290✔
806
                return -EINVAL;
5,290✔
807

808
        if (streq(slice, SPECIAL_ROOT_SLICE)) {
5,278✔
809
                *ret = NULL;
599✔
810
                return 0;
599✔
811
        }
812

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

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

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

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

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

831
        assert(slice);
191✔
832
        assert(name);
191✔
833
        assert(ret);
191✔
834

835
        if (!slice_name_is_valid(slice))
191✔
836
                return -EINVAL;
837

838
        if (!unit_prefix_is_valid(name))
189✔
839
                return -EINVAL;
840

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

846
                assert_se(e = endswith(slice, ".slice"));
188✔
847

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

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

855
        *ret = subslice;
189✔
856
        return 0;
189✔
857
}
858

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

863
        if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
8,143✔
864
                return false;
865

866
        if (streq(name, SPECIAL_ROOT_SLICE))
8,119✔
867
                return true;
868

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

873
        for (p = name; p < e; p++) {
117,139✔
874

875
                if (*p == '-') {
109,929✔
876

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

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

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

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

894
        return true;
895
}
896

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

900
        assert(a);
3,655✔
901
        assert(b);
3,655✔
902

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

906
        p = strchr(a, '@');
3,653✔
907
        if (!p)
3,653✔
908
                p = strrchr(a, '.');
3,031✔
909

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

914
        assert(p);
3,653✔
915
        assert(q);
3,653✔
916

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