1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2010 Lennart Poettering
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.
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.
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/>.
25 #include "path-util.h"
26 #include "bus-label.h"
28 #include "unit-name.h"
36 bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
37 const char *e, *i, *at;
39 assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
41 if (_unlikely_(flags == 0))
47 if (strlen(n) >= UNIT_NAME_MAX)
54 if (unit_type_from_string(e + 1) < 0)
57 for (i = n, at = NULL; i < e; i++) {
62 if (!strchr("@" VALID_CHARS, *i))
69 if (flags & UNIT_NAME_PLAIN)
73 if (flags & UNIT_NAME_INSTANCE)
77 if (flags & UNIT_NAME_TEMPLATE)
78 if (at && e == at + 1)
84 bool unit_prefix_is_valid(const char *p) {
86 /* We don't allow additional @ in the prefix string */
91 return in_charset(p, VALID_CHARS);
94 bool unit_instance_is_valid(const char *i) {
96 /* The max length depends on the length of the string, so we
97 * don't really check this here. */
102 /* We allow additional @ in the instance string, we do not
103 * allow them in the prefix! */
105 return in_charset(i, "@" VALID_CHARS);
108 bool unit_suffix_is_valid(const char *s) {
115 if (unit_type_from_string(s + 1) < 0)
121 /// UNNEEDED by elogind
123 int unit_name_to_prefix(const char *n, char **ret) {
130 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
139 s = strndup(n, p - n);
147 int unit_name_to_instance(const char *n, char **instance) {
154 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
157 /* Everything past the first @ and before the last . is the instance */
178 int unit_name_to_prefix_and_instance(const char *n, char **ret) {
185 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
192 s = strndup(n, d - n);
201 UnitType unit_name_to_type(const char *n) {
206 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
207 return _UNIT_TYPE_INVALID;
209 assert_se(e = strrchr(n, '.'));
211 return unit_type_from_string(e + 1);
214 /// UNNEEDED by elogind
216 int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
224 if (!unit_name_is_valid(n, UNIT_NAME_ANY))
227 if (!unit_suffix_is_valid(suffix))
230 assert_se(e = strrchr(n, '.'));
235 s = new(char, a + b + 1);
239 strcpy(mempcpy(s, n, a), suffix);
246 int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
253 if (!unit_prefix_is_valid(prefix))
256 if (instance && !unit_instance_is_valid(instance))
259 if (!unit_suffix_is_valid(suffix))
263 s = strappend(prefix, suffix);
265 s = strjoin(prefix, "@", instance, suffix, NULL);
273 static char *do_escape_char(char c, char *t) {
278 *(t++) = hexchar(c >> 4);
284 static char *do_escape(const char *f, char *t) {
288 /* do not create units with a leading '.', like for "/.dotdir" mount points */
290 t = do_escape_char(*f, t);
297 else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f))
298 t = do_escape_char(*f, t);
306 char *unit_name_escape(const char *f) {
311 r = new(char, strlen(f)*4+1);
321 int unit_name_unescape(const char *f, char **ret) {
322 _cleanup_free_ char *r = NULL;
331 for (t = r; *f; f++) {
334 else if (*f == '\\') {
348 *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
362 int unit_name_path_escape(const char *f, char **ret) {
372 path_kill_slashes(p);
374 if (STR_IN_SET(p, "/", ""))
379 if (!path_is_safe(p))
382 /* Truncate trailing slashes */
383 e = endswith(p, "/");
387 /* Truncate leading slashes */
391 s = unit_name_escape(p);
400 int unit_name_path_unescape(const char *f, char **ret) {
416 r = unit_name_unescape(f, &w);
420 /* Don't accept trailing or leading slashes */
421 if (startswith(w, "/") || endswith(w, "/")) {
426 /* Prefix a slash again */
427 s = strappend("/", w);
432 if (!path_is_safe(s)) {
446 /// UNNEEDED by elogind
448 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
457 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
459 if (!unit_instance_is_valid(i))
462 assert_se(p = strchr(f, '@'));
463 assert_se(e = strrchr(f, '.'));
468 s = new(char, a + 1 + b + strlen(e) + 1);
472 strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
478 int unit_name_template(const char *f, char **ret) {
486 if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
489 assert_se(p = strchr(f, '@'));
490 assert_se(e = strrchr(f, '.'));
494 s = new(char, a + 1 + strlen(e) + 1);
498 strcpy(mempcpy(s, f, a + 1), e);
505 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
506 _cleanup_free_ char *p = NULL;
514 if (!unit_suffix_is_valid(suffix))
517 r = unit_name_path_escape(path, &p);
521 s = strappend(p, suffix);
529 /// UNNEEDED by elogind
531 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
532 _cleanup_free_ char *p = NULL;
541 if (!unit_prefix_is_valid(prefix))
544 if (!unit_suffix_is_valid(suffix))
547 r = unit_name_path_escape(path, &p);
551 s = strjoin(prefix, "@", p, suffix, NULL);
559 int unit_name_to_path(const char *name, char **ret) {
560 _cleanup_free_ char *prefix = NULL;
565 r = unit_name_to_prefix(name, &prefix);
569 return unit_name_path_unescape(prefix, ret);
573 char *unit_dbus_path_from_name(const char *name) {
574 _cleanup_free_ char *e = NULL;
578 e = bus_label_escape(name);
582 return strappend("/org/freedesktop/systemd1/unit/", e);
585 int unit_name_from_dbus_path(const char *path, char **name) {
589 e = startswith(path, "/org/freedesktop/systemd1/unit/");
593 n = bus_label_unescape(e);
601 /// UNNEEDED by elogind
603 const char* unit_dbus_interface_from_type(UnitType t) {
605 static const char *const table[_UNIT_TYPE_MAX] = {
606 [UNIT_SERVICE] = "org.freedesktop.systemd1.Service",
607 [UNIT_SOCKET] = "org.freedesktop.systemd1.Socket",
608 [UNIT_BUSNAME] = "org.freedesktop.systemd1.BusName",
609 [UNIT_TARGET] = "org.freedesktop.systemd1.Target",
610 [UNIT_SNAPSHOT] = "org.freedesktop.systemd1.Snapshot",
611 [UNIT_DEVICE] = "org.freedesktop.systemd1.Device",
612 [UNIT_MOUNT] = "org.freedesktop.systemd1.Mount",
613 [UNIT_AUTOMOUNT] = "org.freedesktop.systemd1.Automount",
614 [UNIT_SWAP] = "org.freedesktop.systemd1.Swap",
615 [UNIT_TIMER] = "org.freedesktop.systemd1.Timer",
616 [UNIT_PATH] = "org.freedesktop.systemd1.Path",
617 [UNIT_SLICE] = "org.freedesktop.systemd1.Slice",
618 [UNIT_SCOPE] = "org.freedesktop.systemd1.Scope",
623 if (t >= _UNIT_TYPE_MAX)
629 const char *unit_dbus_interface_from_name(const char *name) {
632 t = unit_name_to_type(name);
636 return unit_dbus_interface_from_type(t);
639 static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) {
640 const char *valid_chars;
643 assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB));
646 /* We'll only escape the obvious characters here, to play
649 valid_chars = allow_globs == UNIT_NAME_GLOB ? "@" VALID_CHARS "[]!-*?" : "@" VALID_CHARS;
654 else if (!strchr(valid_chars, *f))
655 t = do_escape_char(*f, t);
664 * Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
665 * /blah/blah is converted to blah-blah.mount, anything else is left alone,
666 * except that @suffix is appended if a valid unit suffix is not present.
668 * If @allow_globs, globs characters are preserved. Otherwise they are escaped.
670 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) {
678 if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
681 if (!unit_suffix_is_valid(suffix))
684 if (unit_name_is_valid(name, UNIT_NAME_ANY)) {
685 /* No mangling necessary... */
694 if (is_device_path(name)) {
695 r = unit_name_from_path(name, ".device", ret);
702 if (path_is_absolute(name)) {
703 r = unit_name_from_path(name, ".mount", ret);
710 s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
714 t = do_escape_mangle(name, allow_globs, s);
717 if (unit_name_to_type(s) < 0)
724 int slice_build_parent_slice(const char *slice, char **ret) {
731 if (!slice_name_is_valid(slice))
734 if (streq(slice, "-.slice")) {
743 dash = strrchr(s, '-');
745 strcpy(dash, ".slice");
747 r = free_and_strdup(&s, "-.slice");
759 int slice_build_subslice(const char *slice, const char*name, char **ret) {
766 if (!slice_name_is_valid(slice))
769 if (!unit_prefix_is_valid(name))
772 if (streq(slice, "-.slice"))
773 subslice = strappend(name, ".slice");
777 assert_se(e = endswith(slice, ".slice"));
779 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
783 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
790 bool slice_name_is_valid(const char *name) {
794 if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
797 if (streq(name, "-.slice"))
800 e = endswith(name, ".slice");
804 for (p = name; p < e; p++) {
808 /* Don't allow initial dash */
812 /* Don't allow multiple dashes */
821 /* Don't allow trailing hash */
828 static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
829 [UNIT_SERVICE] = "service",
830 [UNIT_SOCKET] = "socket",
831 [UNIT_BUSNAME] = "busname",
832 [UNIT_TARGET] = "target",
833 [UNIT_SNAPSHOT] = "snapshot",
834 [UNIT_DEVICE] = "device",
835 [UNIT_MOUNT] = "mount",
836 [UNIT_AUTOMOUNT] = "automount",
837 [UNIT_SWAP] = "swap",
838 [UNIT_TIMER] = "timer",
839 [UNIT_PATH] = "path",
840 [UNIT_SLICE] = "slice",
841 [UNIT_SCOPE] = "scope",
844 DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
846 /// UNNEEDED by elogind
848 static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
849 [UNIT_STUB] = "stub",
850 [UNIT_LOADED] = "loaded",
851 [UNIT_NOT_FOUND] = "not-found",
852 [UNIT_ERROR] = "error",
853 [UNIT_MERGED] = "merged",
854 [UNIT_MASKED] = "masked"
857 DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
859 static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
860 [UNIT_REQUIRES] = "Requires",
861 [UNIT_REQUIRES_OVERRIDABLE] = "RequiresOverridable",
862 [UNIT_REQUISITE] = "Requisite",
863 [UNIT_REQUISITE_OVERRIDABLE] = "RequisiteOverridable",
864 [UNIT_WANTS] = "Wants",
865 [UNIT_BINDS_TO] = "BindsTo",
866 [UNIT_PART_OF] = "PartOf",
867 [UNIT_REQUIRED_BY] = "RequiredBy",
868 [UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable",
869 [UNIT_REQUISITE_OF] = "RequisiteOf",
870 [UNIT_REQUISITE_OF_OVERRIDABLE] = "RequisiteOfOverridable",
871 [UNIT_WANTED_BY] = "WantedBy",
872 [UNIT_BOUND_BY] = "BoundBy",
873 [UNIT_CONSISTS_OF] = "ConsistsOf",
874 [UNIT_CONFLICTS] = "Conflicts",
875 [UNIT_CONFLICTED_BY] = "ConflictedBy",
876 [UNIT_BEFORE] = "Before",
877 [UNIT_AFTER] = "After",
878 [UNIT_ON_FAILURE] = "OnFailure",
879 [UNIT_TRIGGERS] = "Triggers",
880 [UNIT_TRIGGERED_BY] = "TriggeredBy",
881 [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo",
882 [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom",
883 [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf",
884 [UNIT_REFERENCES] = "References",
885 [UNIT_REFERENCED_BY] = "ReferencedBy",
888 DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);