]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/basic/unit-name.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / basic / unit-name.c
... / ...
CommitLineData
1/* SPDX-License-Identifier: LGPL-2.1+ */
2/***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
21#include <errno.h>
22#include <stddef.h>
23#include <stdint.h>
24#include <stdlib.h>
25#include <string.h>
26
27#include "alloc-util.h"
28#include "glob-util.h"
29#include "hexdecoct.h"
30#include "path-util.h"
31#include "string-util.h"
32#include "strv.h"
33#include "unit-name.h"
34
35/* Characters valid in a unit name. */
36#define VALID_CHARS \
37 DIGITS \
38 LETTERS \
39 ":-_.\\"
40
41/* The same, but also permits the single @ character that may appear */
42#define VALID_CHARS_WITH_AT \
43 "@" \
44 VALID_CHARS
45
46/* All chars valid in a unit name glob */
47#define VALID_CHARS_GLOB \
48 VALID_CHARS_WITH_AT \
49 "[]!-*?"
50
51bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
52 const char *e, *i, *at;
53
54 assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
55
56 if (_unlikely_(flags == 0))
57 return false;
58
59 if (isempty(n))
60 return false;
61
62 if (strlen(n) >= UNIT_NAME_MAX)
63 return false;
64
65 e = strrchr(n, '.');
66 if (!e || e == n)
67 return false;
68
69 if (unit_type_from_string(e + 1) < 0)
70 return false;
71
72 for (i = n, at = NULL; i < e; i++) {
73
74 if (*i == '@' && !at)
75 at = i;
76
77 if (!strchr("@" VALID_CHARS, *i))
78 return false;
79 }
80
81 if (at == n)
82 return false;
83
84 if (flags & UNIT_NAME_PLAIN)
85 if (!at)
86 return true;
87
88 if (flags & UNIT_NAME_INSTANCE)
89 if (at && e > at + 1)
90 return true;
91
92 if (flags & UNIT_NAME_TEMPLATE)
93 if (at && e == at + 1)
94 return true;
95
96 return false;
97}
98
99bool unit_prefix_is_valid(const char *p) {
100
101 /* We don't allow additional @ in the prefix string */
102
103 if (isempty(p))
104 return false;
105
106 return in_charset(p, VALID_CHARS);
107}
108
109bool unit_instance_is_valid(const char *i) {
110
111 /* The max length depends on the length of the string, so we
112 * don't really check this here. */
113
114 if (isempty(i))
115 return false;
116
117 /* We allow additional @ in the instance string, we do not
118 * allow them in the prefix! */
119
120 return in_charset(i, "@" VALID_CHARS);
121}
122
123bool unit_suffix_is_valid(const char *s) {
124 if (isempty(s))
125 return false;
126
127 if (s[0] != '.')
128 return false;
129
130 if (unit_type_from_string(s + 1) < 0)
131 return false;
132
133 return true;
134}
135
136int unit_name_to_prefix(const char *n, char **ret) {
137 const char *p;
138 char *s;
139
140 assert(n);
141 assert(ret);
142
143 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
144 return -EINVAL;
145
146 p = strchr(n, '@');
147 if (!p)
148 p = strrchr(n, '.');
149
150 assert_se(p);
151
152 s = strndup(n, p - n);
153 if (!s)
154 return -ENOMEM;
155
156 *ret = s;
157 return 0;
158}
159
160int unit_name_to_instance(const char *n, char **instance) {
161 const char *p, *d;
162 char *i;
163
164 assert(n);
165 assert(instance);
166
167 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
168 return -EINVAL;
169
170 /* Everything past the first @ and before the last . is the instance */
171 p = strchr(n, '@');
172 if (!p) {
173 *instance = NULL;
174 return 0;
175 }
176
177 p++;
178
179 d = strrchr(p, '.');
180 if (!d)
181 return -EINVAL;
182
183 i = strndup(p, d-p);
184 if (!i)
185 return -ENOMEM;
186
187 *instance = i;
188 return 1;
189}
190
191int unit_name_to_prefix_and_instance(const char *n, char **ret) {
192 const char *d;
193 char *s;
194
195 assert(n);
196 assert(ret);
197
198 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
199 return -EINVAL;
200
201 d = strrchr(n, '.');
202 if (!d)
203 return -EINVAL;
204
205 s = strndup(n, d - n);
206 if (!s)
207 return -ENOMEM;
208
209 *ret = s;
210 return 0;
211}
212
213UnitType unit_name_to_type(const char *n) {
214 const char *e;
215
216 assert(n);
217
218 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
219 return _UNIT_TYPE_INVALID;
220
221 assert_se(e = strrchr(n, '.'));
222
223 return unit_type_from_string(e + 1);
224}
225
226int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
227 char *e, *s;
228 size_t a, b;
229
230 assert(n);
231 assert(suffix);
232 assert(ret);
233
234 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
235 return -EINVAL;
236
237 if (!unit_suffix_is_valid(suffix))
238 return -EINVAL;
239
240 assert_se(e = strrchr(n, '.'));
241
242 a = e - n;
243 b = strlen(suffix);
244
245 s = new(char, a + b + 1);
246 if (!s)
247 return -ENOMEM;
248
249 strcpy(mempcpy(s, n, a), suffix);
250 *ret = s;
251
252 return 0;
253}
254
255int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
256 char *s;
257
258 assert(prefix);
259 assert(suffix);
260 assert(ret);
261
262 if (!unit_prefix_is_valid(prefix))
263 return -EINVAL;
264
265 if (instance && !unit_instance_is_valid(instance))
266 return -EINVAL;
267
268 if (!unit_suffix_is_valid(suffix))
269 return -EINVAL;
270
271 if (!instance)
272 s = strappend(prefix, suffix);
273 else
274 s = strjoin(prefix, "@", instance, suffix);
275 if (!s)
276 return -ENOMEM;
277
278 *ret = s;
279 return 0;
280}
281
282static char *do_escape_char(char c, char *t) {
283 assert(t);
284
285 *(t++) = '\\';
286 *(t++) = 'x';
287 *(t++) = hexchar(c >> 4);
288 *(t++) = hexchar(c);
289
290 return t;
291}
292
293static char *do_escape(const char *f, char *t) {
294 assert(f);
295 assert(t);
296
297 /* do not create units with a leading '.', like for "/.dotdir" mount points */
298 if (*f == '.') {
299 t = do_escape_char(*f, t);
300 f++;
301 }
302
303 for (; *f; f++) {
304 if (*f == '/')
305 *(t++) = '-';
306 else if (IN_SET(*f, '-', '\\') || !strchr(VALID_CHARS, *f))
307 t = do_escape_char(*f, t);
308 else
309 *(t++) = *f;
310 }
311
312 return t;
313}
314
315char *unit_name_escape(const char *f) {
316 char *r, *t;
317
318 assert(f);
319
320 r = new(char, strlen(f)*4+1);
321 if (!r)
322 return NULL;
323
324 t = do_escape(f, r);
325 *t = 0;
326
327 return r;
328}
329
330int unit_name_unescape(const char *f, char **ret) {
331 _cleanup_free_ char *r = NULL;
332 char *t;
333
334 assert(f);
335
336 r = strdup(f);
337 if (!r)
338 return -ENOMEM;
339
340 for (t = r; *f; f++) {
341 if (*f == '-')
342 *(t++) = '/';
343 else if (*f == '\\') {
344 int a, b;
345
346 if (f[1] != 'x')
347 return -EINVAL;
348
349 a = unhexchar(f[2]);
350 if (a < 0)
351 return -EINVAL;
352
353 b = unhexchar(f[3]);
354 if (b < 0)
355 return -EINVAL;
356
357 *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
358 f += 3;
359 } else
360 *(t++) = *f;
361 }
362
363 *t = 0;
364
365 *ret = r;
366 r = NULL;
367
368 return 0;
369}
370
371int unit_name_path_escape(const char *f, char **ret) {
372 char *p, *s;
373
374 assert(f);
375 assert(ret);
376
377 p = strdupa(f);
378 if (!p)
379 return -ENOMEM;
380
381 path_kill_slashes(p);
382
383 if (STR_IN_SET(p, "/", ""))
384 s = strdup("-");
385 else {
386 if (!path_is_safe(p))
387 return -EINVAL;
388
389 /* Truncate trailing slashes */
390 delete_trailing_chars(p, "/");
391
392 /* Truncate leading slashes */
393 p = skip_leading_chars(p, "/");
394
395 s = unit_name_escape(p);
396 }
397 if (!s)
398 return -ENOMEM;
399
400 *ret = s;
401 return 0;
402}
403
404int unit_name_path_unescape(const char *f, char **ret) {
405 char *s;
406 int r;
407
408 assert(f);
409
410 if (isempty(f))
411 return -EINVAL;
412
413 if (streq(f, "-")) {
414 s = strdup("/");
415 if (!s)
416 return -ENOMEM;
417 } else {
418 char *w;
419
420 r = unit_name_unescape(f, &w);
421 if (r < 0)
422 return r;
423
424 /* Don't accept trailing or leading slashes */
425 if (startswith(w, "/") || endswith(w, "/")) {
426 free(w);
427 return -EINVAL;
428 }
429
430 /* Prefix a slash again */
431 s = strappend("/", w);
432 free(w);
433 if (!s)
434 return -ENOMEM;
435
436 if (!path_is_safe(s)) {
437 free(s);
438 return -EINVAL;
439 }
440 }
441
442 if (ret)
443 *ret = s;
444 else
445 free(s);
446
447 return 0;
448}
449
450int unit_name_replace_instance(const char *f, const char *i, char **ret) {
451 const char *p, *e;
452 char *s;
453 size_t a, b;
454
455 assert(f);
456 assert(i);
457 assert(ret);
458
459 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
460 return -EINVAL;
461 if (!unit_instance_is_valid(i))
462 return -EINVAL;
463
464 assert_se(p = strchr(f, '@'));
465 assert_se(e = strrchr(f, '.'));
466
467 a = p - f;
468 b = strlen(i);
469
470 s = new(char, a + 1 + b + strlen(e) + 1);
471 if (!s)
472 return -ENOMEM;
473
474 strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
475
476 *ret = s;
477 return 0;
478}
479
480int unit_name_template(const char *f, char **ret) {
481 const char *p, *e;
482 char *s;
483 size_t a;
484
485 assert(f);
486 assert(ret);
487
488 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
489 return -EINVAL;
490
491 assert_se(p = strchr(f, '@'));
492 assert_se(e = strrchr(f, '.'));
493
494 a = p - f;
495
496 s = new(char, a + 1 + strlen(e) + 1);
497 if (!s)
498 return -ENOMEM;
499
500 strcpy(mempcpy(s, f, a + 1), e);
501
502 *ret = s;
503 return 0;
504}
505
506int unit_name_from_path(const char *path, const char *suffix, char **ret) {
507 _cleanup_free_ char *p = NULL;
508 char *s = NULL;
509 int r;
510
511 assert(path);
512 assert(suffix);
513 assert(ret);
514
515 if (!unit_suffix_is_valid(suffix))
516 return -EINVAL;
517
518 r = unit_name_path_escape(path, &p);
519 if (r < 0)
520 return r;
521
522 s = strappend(p, suffix);
523 if (!s)
524 return -ENOMEM;
525
526 *ret = s;
527 return 0;
528}
529
530int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
531 _cleanup_free_ char *p = NULL;
532 char *s;
533 int r;
534
535 assert(prefix);
536 assert(path);
537 assert(suffix);
538 assert(ret);
539
540 if (!unit_prefix_is_valid(prefix))
541 return -EINVAL;
542
543 if (!unit_suffix_is_valid(suffix))
544 return -EINVAL;
545
546 r = unit_name_path_escape(path, &p);
547 if (r < 0)
548 return r;
549
550 s = strjoin(prefix, "@", p, suffix);
551 if (!s)
552 return -ENOMEM;
553
554 *ret = s;
555 return 0;
556}
557
558int unit_name_to_path(const char *name, char **ret) {
559 _cleanup_free_ char *prefix = NULL;
560 int r;
561
562 assert(name);
563
564 r = unit_name_to_prefix(name, &prefix);
565 if (r < 0)
566 return r;
567
568 return unit_name_path_unescape(prefix, ret);
569}
570
571static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) {
572 const char *valid_chars;
573
574 assert(f);
575 assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB));
576 assert(t);
577
578 /* We'll only escape the obvious characters here, to play
579 * safe. */
580
581 valid_chars = allow_globs == UNIT_NAME_GLOB ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT;
582
583 for (; *f; f++) {
584 if (*f == '/')
585 *(t++) = '-';
586 else if (!strchr(valid_chars, *f))
587 t = do_escape_char(*f, t);
588 else
589 *(t++) = *f;
590 }
591
592 return t;
593}
594
595/**
596 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
597 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
598 * except that @suffix is appended if a valid unit suffix is not present.
599 *
600 * If @allow_globs, globs characters are preserved. Otherwise, they are escaped.
601 */
602int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) {
603 char *s, *t;
604 int r;
605
606 assert(name);
607 assert(suffix);
608 assert(ret);
609
610 if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
611 return -EINVAL;
612
613 if (!unit_suffix_is_valid(suffix))
614 return -EINVAL;
615
616 /* Already a fully valid unit name? If so, no mangling is necessary... */
617 if (unit_name_is_valid(name, UNIT_NAME_ANY))
618 goto good;
619
620 /* Already a fully valid globbing expression? If so, no mangling is necessary either... */
621 if (allow_globs == UNIT_NAME_GLOB &&
622 string_is_glob(name) &&
623 in_charset(name, VALID_CHARS_GLOB))
624 goto good;
625
626 if (is_device_path(name)) {
627 r = unit_name_from_path(name, ".device", ret);
628 if (r >= 0)
629 return 1;
630 if (r != -EINVAL)
631 return r;
632 }
633
634 if (path_is_absolute(name)) {
635 r = unit_name_from_path(name, ".mount", ret);
636 if (r >= 0)
637 return 1;
638 if (r != -EINVAL)
639 return r;
640 }
641
642 s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
643 if (!s)
644 return -ENOMEM;
645
646 t = do_escape_mangle(name, allow_globs, s);
647 *t = 0;
648
649 /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow "foo.*" as a
650 * valid glob. */
651 if ((allow_globs != UNIT_NAME_GLOB || !string_is_glob(s)) && unit_name_to_type(s) < 0)
652 strcpy(t, suffix);
653
654 *ret = s;
655 return 1;
656
657good:
658 s = strdup(name);
659 if (!s)
660 return -ENOMEM;
661
662 *ret = s;
663 return 0;
664}
665
666int slice_build_parent_slice(const char *slice, char **ret) {
667 char *s, *dash;
668 int r;
669
670 assert(slice);
671 assert(ret);
672
673 if (!slice_name_is_valid(slice))
674 return -EINVAL;
675
676 if (streq(slice, "-.slice")) {
677 *ret = NULL;
678 return 0;
679 }
680
681 s = strdup(slice);
682 if (!s)
683 return -ENOMEM;
684
685 dash = strrchr(s, '-');
686 if (dash)
687 strcpy(dash, ".slice");
688 else {
689 r = free_and_strdup(&s, "-.slice");
690 if (r < 0) {
691 free(s);
692 return r;
693 }
694 }
695
696 *ret = s;
697 return 1;
698}
699
700int slice_build_subslice(const char *slice, const char*name, char **ret) {
701 char *subslice;
702
703 assert(slice);
704 assert(name);
705 assert(ret);
706
707 if (!slice_name_is_valid(slice))
708 return -EINVAL;
709
710 if (!unit_prefix_is_valid(name))
711 return -EINVAL;
712
713 if (streq(slice, "-.slice"))
714 subslice = strappend(name, ".slice");
715 else {
716 char *e;
717
718 assert_se(e = endswith(slice, ".slice"));
719
720 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
721 if (!subslice)
722 return -ENOMEM;
723
724 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
725 }
726
727 *ret = subslice;
728 return 0;
729}
730
731bool slice_name_is_valid(const char *name) {
732 const char *p, *e;
733 bool dash = false;
734
735 if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
736 return false;
737
738 if (streq(name, "-.slice"))
739 return true;
740
741 e = endswith(name, ".slice");
742 if (!e)
743 return false;
744
745 for (p = name; p < e; p++) {
746
747 if (*p == '-') {
748
749 /* Don't allow initial dash */
750 if (p == name)
751 return false;
752
753 /* Don't allow multiple dashes */
754 if (dash)
755 return false;
756
757 dash = true;
758 } else
759 dash = false;
760 }
761
762 /* Don't allow trailing hash */
763 if (dash)
764 return false;
765
766 return true;
767}