]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/unit-name.c
core: add unit_dbus_interface_from_type() to unit-name.h
[thirdparty/systemd.git] / src / basic / unit-name.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24
25 #include "path-util.h"
26 #include "bus-label.h"
27 #include "util.h"
28 #include "unit-name.h"
29 #include "def.h"
30 #include "strv.h"
31
32 #define VALID_CHARS \
33 DIGITS LETTERS \
34 ":-_.\\"
35
36 bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
37 const char *e, *i, *at;
38
39 assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
40
41 if (_unlikely_(flags == 0))
42 return false;
43
44 if (isempty(n))
45 return false;
46
47 if (strlen(n) >= UNIT_NAME_MAX)
48 return false;
49
50 e = strrchr(n, '.');
51 if (!e || e == n)
52 return false;
53
54 if (unit_type_from_string(e + 1) < 0)
55 return false;
56
57 for (i = n, at = NULL; i < e; i++) {
58
59 if (*i == '@' && !at)
60 at = i;
61
62 if (!strchr("@" VALID_CHARS, *i))
63 return false;
64 }
65
66 if (at == n)
67 return false;
68
69 if (flags & UNIT_NAME_PLAIN)
70 if (!at)
71 return true;
72
73 if (flags & UNIT_NAME_INSTANCE)
74 if (at && e > at + 1)
75 return true;
76
77 if (flags & UNIT_NAME_TEMPLATE)
78 if (at && e == at + 1)
79 return true;
80
81 return false;
82 }
83
84 bool unit_prefix_is_valid(const char *p) {
85
86 /* We don't allow additional @ in the prefix string */
87
88 if (isempty(p))
89 return false;
90
91 return in_charset(p, VALID_CHARS);
92 }
93
94 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 if (isempty(i))
100 return false;
101
102 /* We allow additional @ in the instance string, we do not
103 * allow them in the prefix! */
104
105 return in_charset(i, "@" VALID_CHARS);
106 }
107
108 bool unit_suffix_is_valid(const char *s) {
109 if (isempty(s))
110 return false;
111
112 if (s[0] != '.')
113 return false;
114
115 if (unit_type_from_string(s + 1) < 0)
116 return false;
117
118 return true;
119 }
120
121 int unit_name_to_prefix(const char *n, char **ret) {
122 const char *p;
123 char *s;
124
125 assert(n);
126 assert(ret);
127
128 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
129 return -EINVAL;
130
131 p = strchr(n, '@');
132 if (!p)
133 p = strrchr(n, '.');
134
135 assert_se(p);
136
137 s = strndup(n, p - n);
138 if (!s)
139 return -ENOMEM;
140
141 *ret = s;
142 return 0;
143 }
144
145 int unit_name_to_instance(const char *n, char **instance) {
146 const char *p, *d;
147 char *i;
148
149 assert(n);
150 assert(instance);
151
152 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
153 return -EINVAL;
154
155 /* Everything past the first @ and before the last . is the instance */
156 p = strchr(n, '@');
157 if (!p) {
158 *instance = NULL;
159 return 0;
160 }
161
162 p++;
163
164 d = strrchr(p, '.');
165 if (!d)
166 return -EINVAL;
167
168 i = strndup(p, d-p);
169 if (!i)
170 return -ENOMEM;
171
172 *instance = i;
173 return 1;
174 }
175
176 int unit_name_to_prefix_and_instance(const char *n, char **ret) {
177 const char *d;
178 char *s;
179
180 assert(n);
181 assert(ret);
182
183 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
184 return -EINVAL;
185
186 d = strrchr(n, '.');
187 if (!d)
188 return -EINVAL;
189
190 s = strndup(n, d - n);
191 if (!s)
192 return -ENOMEM;
193
194 *ret = s;
195 return 0;
196 }
197
198 UnitType unit_name_to_type(const char *n) {
199 const char *e;
200
201 assert(n);
202
203 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
204 return _UNIT_TYPE_INVALID;
205
206 assert_se(e = strrchr(n, '.'));
207
208 return unit_type_from_string(e + 1);
209 }
210
211 int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
212 char *e, *s;
213 size_t a, b;
214
215 assert(n);
216 assert(suffix);
217 assert(ret);
218
219 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
220 return -EINVAL;
221
222 if (!unit_suffix_is_valid(suffix))
223 return -EINVAL;
224
225 assert_se(e = strrchr(n, '.'));
226
227 a = e - n;
228 b = strlen(suffix);
229
230 s = new(char, a + b + 1);
231 if (!s)
232 return -ENOMEM;
233
234 strcpy(mempcpy(s, n, a), suffix);
235 *ret = s;
236
237 return 0;
238 }
239
240 int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
241 char *s;
242
243 assert(prefix);
244 assert(suffix);
245 assert(ret);
246
247 if (!unit_prefix_is_valid(prefix))
248 return -EINVAL;
249
250 if (instance && !unit_instance_is_valid(instance))
251 return -EINVAL;
252
253 if (!unit_suffix_is_valid(suffix))
254 return -EINVAL;
255
256 if (!instance)
257 s = strappend(prefix, suffix);
258 else
259 s = strjoin(prefix, "@", instance, suffix, NULL);
260 if (!s)
261 return -ENOMEM;
262
263 *ret = s;
264 return 0;
265 }
266
267 static char *do_escape_char(char c, char *t) {
268 assert(t);
269
270 *(t++) = '\\';
271 *(t++) = 'x';
272 *(t++) = hexchar(c >> 4);
273 *(t++) = hexchar(c);
274
275 return t;
276 }
277
278 static char *do_escape(const char *f, char *t) {
279 assert(f);
280 assert(t);
281
282 /* do not create units with a leading '.', like for "/.dotdir" mount points */
283 if (*f == '.') {
284 t = do_escape_char(*f, t);
285 f++;
286 }
287
288 for (; *f; f++) {
289 if (*f == '/')
290 *(t++) = '-';
291 else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f))
292 t = do_escape_char(*f, t);
293 else
294 *(t++) = *f;
295 }
296
297 return t;
298 }
299
300 char *unit_name_escape(const char *f) {
301 char *r, *t;
302
303 assert(f);
304
305 r = new(char, strlen(f)*4+1);
306 if (!r)
307 return NULL;
308
309 t = do_escape(f, r);
310 *t = 0;
311
312 return r;
313 }
314
315 int unit_name_unescape(const char *f, char **ret) {
316 _cleanup_free_ char *r = NULL;
317 char *t;
318
319 assert(f);
320
321 r = strdup(f);
322 if (!r)
323 return -ENOMEM;
324
325 for (t = r; *f; f++) {
326 if (*f == '-')
327 *(t++) = '/';
328 else if (*f == '\\') {
329 int a, b;
330
331 if (f[1] != 'x')
332 return -EINVAL;
333
334 a = unhexchar(f[2]);
335 if (a < 0)
336 return -EINVAL;
337
338 b = unhexchar(f[3]);
339 if (b < 0)
340 return -EINVAL;
341
342 *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
343 f += 3;
344 } else
345 *(t++) = *f;
346 }
347
348 *t = 0;
349
350 *ret = r;
351 r = NULL;
352
353 return 0;
354 }
355
356 int unit_name_path_escape(const char *f, char **ret) {
357 char *p, *s;
358
359 assert(f);
360 assert(ret);
361
362 p = strdupa(f);
363 if (!p)
364 return -ENOMEM;
365
366 path_kill_slashes(p);
367
368 if (STR_IN_SET(p, "/", ""))
369 s = strdup("-");
370 else {
371 char *e;
372
373 if (!path_is_safe(p))
374 return -EINVAL;
375
376 /* Truncate trailing slashes */
377 e = endswith(p, "/");
378 if (e)
379 *e = 0;
380
381 /* Truncate leading slashes */
382 if (p[0] == '/')
383 p++;
384
385 s = unit_name_escape(p);
386 }
387 if (!s)
388 return -ENOMEM;
389
390 *ret = s;
391 return 0;
392 }
393
394 int unit_name_path_unescape(const char *f, char **ret) {
395 char *s;
396 int r;
397
398 assert(f);
399
400 if (isempty(f))
401 return -EINVAL;
402
403 if (streq(f, "-")) {
404 s = strdup("/");
405 if (!s)
406 return -ENOMEM;
407 } else {
408 char *w;
409
410 r = unit_name_unescape(f, &w);
411 if (r < 0)
412 return r;
413
414 /* Don't accept trailing or leading slashes */
415 if (startswith(w, "/") || endswith(w, "/")) {
416 free(w);
417 return -EINVAL;
418 }
419
420 /* Prefix a slash again */
421 s = strappend("/", w);
422 free(w);
423 if (!s)
424 return -ENOMEM;
425
426 if (!path_is_safe(s)) {
427 free(s);
428 return -EINVAL;
429 }
430 }
431
432 if (ret)
433 *ret = s;
434 else
435 free(s);
436
437 return 0;
438 }
439
440 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
441 const char *p, *e;
442 char *s;
443 size_t a, b;
444
445 assert(f);
446 assert(i);
447 assert(ret);
448
449 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
450 return -EINVAL;
451 if (!unit_instance_is_valid(i))
452 return -EINVAL;
453
454 assert_se(p = strchr(f, '@'));
455 assert_se(e = strrchr(f, '.'));
456
457 a = p - f;
458 b = strlen(i);
459
460 s = new(char, a + 1 + b + strlen(e) + 1);
461 if (!s)
462 return -ENOMEM;
463
464 strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
465
466 *ret = s;
467 return 0;
468 }
469
470 int unit_name_template(const char *f, char **ret) {
471 const char *p, *e;
472 char *s;
473 size_t a;
474
475 assert(f);
476 assert(ret);
477
478 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
479 return -EINVAL;
480
481 assert_se(p = strchr(f, '@'));
482 assert_se(e = strrchr(f, '.'));
483
484 a = p - f;
485
486 s = new(char, a + 1 + strlen(e) + 1);
487 if (!s)
488 return -ENOMEM;
489
490 strcpy(mempcpy(s, f, a + 1), e);
491
492 *ret = s;
493 return 0;
494 }
495
496 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
497 _cleanup_free_ char *p = NULL;
498 char *s = NULL;
499 int r;
500
501 assert(path);
502 assert(suffix);
503 assert(ret);
504
505 if (!unit_suffix_is_valid(suffix))
506 return -EINVAL;
507
508 r = unit_name_path_escape(path, &p);
509 if (r < 0)
510 return r;
511
512 s = strappend(p, suffix);
513 if (!s)
514 return -ENOMEM;
515
516 *ret = s;
517 return 0;
518 }
519
520 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
521 _cleanup_free_ char *p = NULL;
522 char *s;
523 int r;
524
525 assert(prefix);
526 assert(path);
527 assert(suffix);
528 assert(ret);
529
530 if (!unit_prefix_is_valid(prefix))
531 return -EINVAL;
532
533 if (!unit_suffix_is_valid(suffix))
534 return -EINVAL;
535
536 r = unit_name_path_escape(path, &p);
537 if (r < 0)
538 return r;
539
540 s = strjoin(prefix, "@", p, suffix, NULL);
541 if (!s)
542 return -ENOMEM;
543
544 *ret = s;
545 return 0;
546 }
547
548 int unit_name_to_path(const char *name, char **ret) {
549 _cleanup_free_ char *prefix = NULL;
550 int r;
551
552 assert(name);
553
554 r = unit_name_to_prefix(name, &prefix);
555 if (r < 0)
556 return r;
557
558 return unit_name_path_unescape(prefix, ret);
559 }
560
561 char *unit_dbus_path_from_name(const char *name) {
562 _cleanup_free_ char *e = NULL;
563
564 assert(name);
565
566 e = bus_label_escape(name);
567 if (!e)
568 return NULL;
569
570 return strappend("/org/freedesktop/systemd1/unit/", e);
571 }
572
573 int unit_name_from_dbus_path(const char *path, char **name) {
574 const char *e;
575 char *n;
576
577 e = startswith(path, "/org/freedesktop/systemd1/unit/");
578 if (!e)
579 return -EINVAL;
580
581 n = bus_label_unescape(e);
582 if (!n)
583 return -ENOMEM;
584
585 *name = n;
586 return 0;
587 }
588
589 const char* unit_dbus_interface_from_type(UnitType t) {
590
591 static const char *const table[_UNIT_TYPE_MAX] = {
592 [UNIT_SERVICE] = "org.freedesktop.systemd1.Service",
593 [UNIT_SOCKET] = "org.freedesktop.systemd1.Socket",
594 [UNIT_BUSNAME] = "org.freedesktop.systemd1.BusName",
595 [UNIT_TARGET] = "org.freedesktop.systemd1.Target",
596 [UNIT_SNAPSHOT] = "org.freedesktop.systemd1.Snapshot",
597 [UNIT_DEVICE] = "org.freedesktop.systemd1.Device",
598 [UNIT_MOUNT] = "org.freedesktop.systemd1.Mount",
599 [UNIT_AUTOMOUNT] = "org.freedesktop.systemd1.Automount",
600 [UNIT_SWAP] = "org.freedesktop.systemd1.Swap",
601 [UNIT_TIMER] = "org.freedesktop.systemd1.Timer",
602 [UNIT_PATH] = "org.freedesktop.systemd1.Path",
603 [UNIT_SLICE] = "org.freedesktop.systemd1.Slice",
604 [UNIT_SCOPE] = "org.freedesktop.systemd1.Scope",
605 };
606
607 if (t < 0)
608 return NULL;
609 if (t >= _UNIT_TYPE_MAX)
610 return NULL;
611
612 return table[t];
613 }
614
615 const char *unit_dbus_interface_from_name(const char *name) {
616 UnitType t;
617
618 t = unit_name_to_type(name);
619 if (t < 0)
620 return NULL;
621
622 return unit_dbus_interface_from_type(t);
623 }
624
625 static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) {
626 const char *valid_chars;
627
628 assert(f);
629 assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB));
630 assert(t);
631
632 /* We'll only escape the obvious characters here, to play
633 * safe. */
634
635 valid_chars = allow_globs == UNIT_NAME_GLOB ? "@" VALID_CHARS "[]!-*?" : "@" VALID_CHARS;
636
637 for (; *f; f++) {
638 if (*f == '/')
639 *(t++) = '-';
640 else if (!strchr(valid_chars, *f))
641 t = do_escape_char(*f, t);
642 else
643 *(t++) = *f;
644 }
645
646 return t;
647 }
648
649 /**
650 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
651 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
652 * except that @suffix is appended if a valid unit suffix is not present.
653 *
654 * If @allow_globs, globs characters are preserved. Otherwise they are escaped.
655 */
656 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) {
657 char *s, *t;
658 int r;
659
660 assert(name);
661 assert(suffix);
662 assert(ret);
663
664 if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
665 return -EINVAL;
666
667 if (!unit_suffix_is_valid(suffix))
668 return -EINVAL;
669
670 if (unit_name_is_valid(name, UNIT_NAME_ANY)) {
671 /* No mangling necessary... */
672 s = strdup(name);
673 if (!s)
674 return -ENOMEM;
675
676 *ret = s;
677 return 0;
678 }
679
680 if (is_device_path(name)) {
681 r = unit_name_from_path(name, ".device", ret);
682 if (r >= 0)
683 return 1;
684 if (r != -EINVAL)
685 return r;
686 }
687
688 if (path_is_absolute(name)) {
689 r = unit_name_from_path(name, ".mount", ret);
690 if (r >= 0)
691 return 1;
692 if (r != -EINVAL)
693 return r;
694 }
695
696 s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
697 if (!s)
698 return -ENOMEM;
699
700 t = do_escape_mangle(name, allow_globs, s);
701 *t = 0;
702
703 if (unit_name_to_type(s) < 0)
704 strcpy(t, suffix);
705
706 *ret = s;
707 return 1;
708 }
709
710 int slice_build_parent_slice(const char *slice, char **ret) {
711 char *s, *dash;
712 int r;
713
714 assert(slice);
715 assert(ret);
716
717 if (!slice_name_is_valid(slice))
718 return -EINVAL;
719
720 if (streq(slice, "-.slice")) {
721 *ret = NULL;
722 return 0;
723 }
724
725 s = strdup(slice);
726 if (!s)
727 return -ENOMEM;
728
729 dash = strrchr(s, '-');
730 if (dash)
731 strcpy(dash, ".slice");
732 else {
733 r = free_and_strdup(&s, "-.slice");
734 if (r < 0) {
735 free(s);
736 return r;
737 }
738 }
739
740 *ret = s;
741 return 1;
742 }
743
744 int slice_build_subslice(const char *slice, const char*name, char **ret) {
745 char *subslice;
746
747 assert(slice);
748 assert(name);
749 assert(ret);
750
751 if (!slice_name_is_valid(slice))
752 return -EINVAL;
753
754 if (!unit_prefix_is_valid(name))
755 return -EINVAL;
756
757 if (streq(slice, "-.slice"))
758 subslice = strappend(name, ".slice");
759 else {
760 char *e;
761
762 assert_se(e = endswith(slice, ".slice"));
763
764 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
765 if (!subslice)
766 return -ENOMEM;
767
768 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
769 }
770
771 *ret = subslice;
772 return 0;
773 }
774
775 bool slice_name_is_valid(const char *name) {
776 const char *p, *e;
777 bool dash = false;
778
779 if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
780 return false;
781
782 if (streq(name, "-.slice"))
783 return true;
784
785 e = endswith(name, ".slice");
786 if (!e)
787 return false;
788
789 for (p = name; p < e; p++) {
790
791 if (*p == '-') {
792
793 /* Don't allow initial dash */
794 if (p == name)
795 return false;
796
797 /* Don't allow multiple dashes */
798 if (dash)
799 return false;
800
801 dash = true;
802 } else
803 dash = false;
804 }
805
806 /* Don't allow trailing hash */
807 if (dash)
808 return false;
809
810 return true;
811 }
812
813 static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
814 [UNIT_SERVICE] = "service",
815 [UNIT_SOCKET] = "socket",
816 [UNIT_BUSNAME] = "busname",
817 [UNIT_TARGET] = "target",
818 [UNIT_SNAPSHOT] = "snapshot",
819 [UNIT_DEVICE] = "device",
820 [UNIT_MOUNT] = "mount",
821 [UNIT_AUTOMOUNT] = "automount",
822 [UNIT_SWAP] = "swap",
823 [UNIT_TIMER] = "timer",
824 [UNIT_PATH] = "path",
825 [UNIT_SLICE] = "slice",
826 [UNIT_SCOPE] = "scope",
827 };
828
829 DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
830
831 static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
832 [UNIT_STUB] = "stub",
833 [UNIT_LOADED] = "loaded",
834 [UNIT_NOT_FOUND] = "not-found",
835 [UNIT_ERROR] = "error",
836 [UNIT_MERGED] = "merged",
837 [UNIT_MASKED] = "masked"
838 };
839
840 DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
841
842 static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
843 [UNIT_REQUIRES] = "Requires",
844 [UNIT_REQUIRES_OVERRIDABLE] = "RequiresOverridable",
845 [UNIT_REQUISITE] = "Requisite",
846 [UNIT_REQUISITE_OVERRIDABLE] = "RequisiteOverridable",
847 [UNIT_WANTS] = "Wants",
848 [UNIT_BINDS_TO] = "BindsTo",
849 [UNIT_PART_OF] = "PartOf",
850 [UNIT_REQUIRED_BY] = "RequiredBy",
851 [UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable",
852 [UNIT_REQUISITE_OF] = "RequisiteOf",
853 [UNIT_REQUISITE_OF_OVERRIDABLE] = "RequisiteOfOverridable",
854 [UNIT_WANTED_BY] = "WantedBy",
855 [UNIT_BOUND_BY] = "BoundBy",
856 [UNIT_CONSISTS_OF] = "ConsistsOf",
857 [UNIT_CONFLICTS] = "Conflicts",
858 [UNIT_CONFLICTED_BY] = "ConflictedBy",
859 [UNIT_BEFORE] = "Before",
860 [UNIT_AFTER] = "After",
861 [UNIT_ON_FAILURE] = "OnFailure",
862 [UNIT_TRIGGERS] = "Triggers",
863 [UNIT_TRIGGERED_BY] = "TriggeredBy",
864 [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo",
865 [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom",
866 [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf",
867 [UNIT_REFERENCES] = "References",
868 [UNIT_REFERENCED_BY] = "ReferencedBy",
869 };
870
871 DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);