As basic/path-lookup.[ch] depend on them.
unaligned.h
unit-def.c
unit-def.h
+ unit-file.c
+ unit-file.h
unit-name.c
unit-name.h
user-util.c
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-id128.h"
+
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "macro.h"
+#include "path-lookup.h"
+#include "set.h"
+#include "special.h"
+#include "stat-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "unit-file.h"
+
+bool unit_type_may_alias(UnitType type) {
+ return IN_SET(type,
+ UNIT_SERVICE,
+ UNIT_SOCKET,
+ UNIT_TARGET,
+ UNIT_DEVICE,
+ UNIT_TIMER,
+ UNIT_PATH);
+}
+
+bool unit_type_may_template(UnitType type) {
+ return IN_SET(type,
+ UNIT_SERVICE,
+ UNIT_SOCKET,
+ UNIT_TARGET,
+ UNIT_TIMER,
+ UNIT_PATH);
+}
+
+int unit_symlink_name_compatible(const char *symlink, const char *target, bool instance_propagation) {
+ _cleanup_free_ char *template = NULL;
+ int r, un_type1, un_type2;
+
+ un_type1 = unit_name_classify(symlink);
+
+ /* The straightforward case: the symlink name matches the target and we have a valid unit */
+ if (streq(symlink, target) &&
+ (un_type1 & (UNIT_NAME_PLAIN | UNIT_NAME_INSTANCE)))
+ return 1;
+
+ r = unit_name_template(symlink, &template);
+ if (r == -EINVAL)
+ return 0; /* Not a template */
+ if (r < 0)
+ return r;
+
+ un_type2 = unit_name_classify(target);
+
+ /* An instance name points to a target that is just the template name */
+ if (un_type1 == UNIT_NAME_INSTANCE &&
+ un_type2 == UNIT_NAME_TEMPLATE &&
+ streq(template, target))
+ return 1;
+
+ /* foo@.target.requires/bar@.service: instance will be propagated */
+ if (instance_propagation &&
+ un_type1 == UNIT_NAME_TEMPLATE &&
+ un_type2 == UNIT_NAME_TEMPLATE &&
+ streq(template, target))
+ return 1;
+
+ return 0;
+}
+
+int unit_validate_alias_symlink_and_warn(const char *filename, const char *target) {
+ const char *src, *dst;
+ _cleanup_free_ char *src_instance = NULL, *dst_instance = NULL;
+ UnitType src_unit_type, dst_unit_type;
+ int src_name_type, dst_name_type;
+
+ /* Check if the *alias* symlink is valid. This applies to symlinks like
+ * /etc/systemd/system/dbus.service → dbus-broker.service, but not to .wants or .requires symlinks
+ * and such. Neither does this apply to symlinks which *link* units, i.e. symlinks to outside of the
+ * unit lookup path.
+ *
+ * -EINVAL is returned if the something is wrong with the source filename or the source unit type is
+ * not allowed to symlink,
+ * -EXDEV if the target filename is not a valid unit name or doesn't match the source.
+ */
+
+ src = basename(filename);
+ dst = basename(target);
+
+ /* src checks */
+
+ src_name_type = unit_name_to_instance(src, &src_instance);
+ if (src_name_type < 0)
+ return log_notice_errno(src_name_type,
+ "%s: not a valid unit name \"%s\": %m", filename, src);
+
+ src_unit_type = unit_name_to_type(src);
+ assert(src_unit_type >= 0); /* unit_name_to_instance() checked the suffix already */
+
+ if (!unit_type_may_alias(src_unit_type))
+ return log_notice_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: symlinks are not allowed for units of this type, rejecting.",
+ filename);
+
+ if (src_name_type != UNIT_NAME_PLAIN &&
+ !unit_type_may_template(src_unit_type))
+ return log_notice_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: templates not allowed for %s units, rejecting.",
+ filename, unit_type_to_string(src_unit_type));
+
+ /* dst checks */
+
+ dst_name_type = unit_name_to_instance(dst, &dst_instance);
+ if (dst_name_type < 0)
+ return log_notice_errno(dst_name_type == -EINVAL ? SYNTHETIC_ERRNO(EXDEV) : dst_name_type,
+ "%s points to \"%s\" which is not a valid unit name: %m",
+ filename, dst);
+
+ if (!(dst_name_type == src_name_type ||
+ (src_name_type == UNIT_NAME_INSTANCE && dst_name_type == UNIT_NAME_TEMPLATE)))
+ return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
+ "%s: symlink target name type \"%s\" does not match source, rejecting.",
+ filename, dst);
+
+ if (dst_name_type == UNIT_NAME_INSTANCE) {
+ assert(src_instance);
+ assert(dst_instance);
+ if (!streq(src_instance, dst_instance))
+ return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
+ "%s: unit symlink target \"%s\" instance name doesn't match, rejecting.",
+ filename, dst);
+ }
+
+ dst_unit_type = unit_name_to_type(dst);
+ if (dst_unit_type != src_unit_type)
+ return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
+ "%s: symlink target \"%s\" has incompatible suffix, rejecting.",
+ filename, dst);
+
+ return 0;
+}
+
+#define FOLLOW_MAX 8
+
+static int unit_ids_map_get(
+ Hashmap *unit_ids_map,
+ const char *unit_name,
+ const char **ret_fragment_path) {
+
+ /* Resolve recursively until we hit an absolute path, i.e. a non-aliased unit.
+ *
+ * We distinguish the case where unit_name was not found in the hashmap at all, and the case where
+ * some symlink was broken.
+ *
+ * If a symlink target points to an instance name, then we also check for the template. */
+
+ const char *id = NULL;
+ int r;
+
+ for (unsigned n = 0; n < FOLLOW_MAX; n++) {
+ const char *t = hashmap_get(unit_ids_map, id ?: unit_name);
+ if (!t) {
+ _cleanup_free_ char *template = NULL;
+
+ if (!id)
+ return -ENOENT;
+
+ r = unit_name_template(id, &template);
+ if (r == -EINVAL)
+ return -ENXIO; /* we failed to find the symlink target */
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine template name for %s: %m", id);
+
+ t = hashmap_get(unit_ids_map, template);
+ if (!t)
+ return -ENXIO;
+
+ /* We successfully switched from instanced name to a template, let's continue */
+ }
+
+ if (path_is_absolute(t)) {
+ if (ret_fragment_path)
+ *ret_fragment_path = t;
+ return 0;
+ }
+
+ id = t;
+ }
+
+ return -ELOOP;
+}
+
+static bool lookup_paths_mtime_exclude(const LookupPaths *lp, const char *path) {
+ /* Paths that are under our exclusive control. Users shall not alter those directly. */
+
+ return streq_ptr(path, lp->generator) ||
+ streq_ptr(path, lp->generator_early) ||
+ streq_ptr(path, lp->generator_late) ||
+ streq_ptr(path, lp->transient) ||
+ streq_ptr(path, lp->persistent_control) ||
+ streq_ptr(path, lp->runtime_control);
+}
+
+#define HASH_KEY SD_ID128_MAKE(4e,86,1b,e3,39,b3,40,46,98,5d,b8,11,34,8f,c3,c1)
+
+bool lookup_paths_timestamp_hash_same(const LookupPaths *lp, uint64_t timestamp_hash, uint64_t *ret_new) {
+ struct siphash state;
+
+ siphash24_init(&state, HASH_KEY.bytes);
+
+ char **dir;
+ STRV_FOREACH(dir, (char**) lp->search_path) {
+ struct stat st;
+
+ if (lookup_paths_mtime_exclude(lp, *dir))
+ continue;
+
+ /* Determine the latest lookup path modification time */
+ if (stat(*dir, &st) < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ log_debug_errno(errno, "Failed to stat %s, ignoring: %m", *dir);
+ continue;
+ }
+
+ siphash24_compress_usec_t(timespec_load(&st.st_mtim), &state);
+ }
+
+ uint64_t updated = siphash24_finalize(&state);
+ if (ret_new)
+ *ret_new = updated;
+ if (updated != timestamp_hash)
+ log_debug("Modification times have changed, need to update cache.");
+ return updated == timestamp_hash;
+}
+
+int unit_file_build_name_map(
+ const LookupPaths *lp,
+ uint64_t *cache_timestamp_hash,
+ Hashmap **unit_ids_map,
+ Hashmap **unit_names_map,
+ Set **path_cache) {
+
+ /* Build two mappings: any name → main unit (i.e. the end result of symlink resolution), unit name →
+ * all aliases (i.e. the entry for a given key is a a list of all names which point to this key). The
+ * key is included in the value iff we saw a file or symlink with that name. In other words, if we
+ * have a key, but it is not present in the value for itself, there was an alias pointing to it, but
+ * the unit itself is not loadable.
+ *
+ * At the same, build a cache of paths where to find units. The non-const parameters are for input
+ * and output. Existing contents will be freed before the new contents are stored.
+ */
+
+ _cleanup_hashmap_free_ Hashmap *ids = NULL, *names = NULL;
+ _cleanup_set_free_free_ Set *paths = NULL;
+ uint64_t timestamp_hash;
+ char **dir;
+ int r;
+
+ /* Before doing anything, check if the timestamp hash that was passed is still valid.
+ * If yes, do nothing. */
+ if (cache_timestamp_hash &&
+ lookup_paths_timestamp_hash_same(lp, *cache_timestamp_hash, ×tamp_hash))
+ return 0;
+
+ /* The timestamp hash is now set based on the mtimes from before when we start reading files.
+ * If anything is modified concurrently, we'll consider the cache outdated. */
+
+ if (path_cache) {
+ paths = set_new(&path_hash_ops_free);
+ if (!paths)
+ return log_oom();
+ }
+
+ STRV_FOREACH(dir, (char**) lp->search_path) {
+ struct dirent *de;
+ _cleanup_closedir_ DIR *d = NULL;
+
+ d = opendir(*dir);
+ if (!d) {
+ if (errno != ENOENT)
+ log_warning_errno(errno, "Failed to open \"%s\", ignoring: %m", *dir);
+ continue;
+ }
+
+ FOREACH_DIRENT_ALL(de, d, log_warning_errno(errno, "Failed to read \"%s\", ignoring: %m", *dir)) {
+ char *filename;
+ _cleanup_free_ char *_filename_free = NULL, *simplified = NULL;
+ const char *suffix, *dst = NULL;
+ bool valid_unit_name;
+
+ valid_unit_name = unit_name_is_valid(de->d_name, UNIT_NAME_ANY);
+
+ /* We only care about valid units and dirs with certain suffixes, let's ignore the
+ * rest. */
+ if (!valid_unit_name &&
+ !ENDSWITH_SET(de->d_name, ".wants", ".requires", ".d"))
+ continue;
+
+ filename = path_join(*dir, de->d_name);
+ if (!filename)
+ return log_oom();
+
+ if (paths) {
+ r = set_consume(paths, filename);
+ if (r < 0)
+ return log_oom();
+ /* We will still use filename below. This is safe because we know the set
+ * holds a reference. */
+ } else
+ _filename_free = filename; /* Make sure we free the filename. */
+
+ if (!valid_unit_name)
+ continue;
+ assert_se(suffix = strrchr(de->d_name, '.'));
+
+ /* search_path is ordered by priority (highest first). If the name is already mapped
+ * to something (incl. itself), it means that we have already seen it, and we should
+ * ignore it here. */
+ if (hashmap_contains(ids, de->d_name))
+ continue;
+
+ dirent_ensure_type(d, de);
+ if (de->d_type == DT_LNK) {
+ /* We don't explicitly check for alias loops here. unit_ids_map_get() which
+ * limits the number of hops should be used to access the map. */
+
+ _cleanup_free_ char *target = NULL;
+
+ r = readlinkat_malloc(dirfd(d), de->d_name, &target);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to read symlink %s/%s, ignoring: %m",
+ *dir, de->d_name);
+ continue;
+ }
+
+ const bool is_abs = path_is_absolute(target);
+ if (lp->root_dir || !is_abs) {
+ char *target_abs = path_join(is_abs ? lp->root_dir : *dir, target);
+ if (!target_abs)
+ return log_oom();
+
+ free_and_replace(target, target_abs);
+ }
+
+ /* Get rid of "." and ".." components in target path */
+ r = chase_symlinks(target, lp->root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to resolve symlink %s pointing to %s, ignoring: %m",
+ filename, target);
+ continue;
+ }
+
+ /* Check if the symlink goes outside of our search path.
+ * If yes, it's a linked unit file or mask, and we don't care about the target name.
+ * Let's just store the link destination directly.
+ * If not, let's verify that it's a good symlink. */
+ char *tail = path_startswith_strv(simplified, lp->search_path);
+ if (tail) {
+ bool self_alias;
+
+ dst = basename(simplified);
+ self_alias = streq(dst, de->d_name);
+
+ if (is_path(tail))
+ log_full(self_alias ? LOG_DEBUG : LOG_WARNING,
+ "Suspicious symlink %s→%s, treating as alias.",
+ filename, simplified);
+
+ r = unit_validate_alias_symlink_and_warn(filename, simplified);
+ if (r < 0)
+ continue;
+
+ if (self_alias) {
+ /* A self-alias that has no effect */
+ log_debug("%s: self-alias: %s/%s → %s, ignoring.",
+ __func__, *dir, de->d_name, dst);
+ continue;
+ }
+
+ log_debug("%s: alias: %s/%s → %s", __func__, *dir, de->d_name, dst);
+ } else {
+ dst = simplified;
+
+ log_debug("%s: linked unit file: %s/%s → %s", __func__, *dir, de->d_name, dst);
+ }
+
+ } else {
+ dst = filename;
+ log_debug("%s: normal unit file: %s", __func__, dst);
+ }
+
+ r = hashmap_put_strdup(&ids, de->d_name, dst);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to add entry to hashmap (%s→%s): %m",
+ de->d_name, dst);
+ }
+ }
+
+ /* Let's also put the names in the reverse db. */
+ const char *dummy, *src;
+ HASHMAP_FOREACH_KEY(dummy, src, ids) {
+ const char *dst;
+
+ r = unit_ids_map_get(ids, src, &dst);
+ if (r < 0)
+ continue;
+
+ if (null_or_empty_path(dst) != 0)
+ continue;
+
+ /* Do not treat instance symlinks that point to the template as aliases */
+ if (unit_name_is_valid(basename(dst), UNIT_NAME_TEMPLATE) &&
+ unit_name_is_valid(src, UNIT_NAME_INSTANCE))
+ continue;
+
+ r = string_strv_hashmap_put(&names, basename(dst), src);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to add entry to hashmap (%s→%s): %m",
+ basename(dst), src);
+ }
+
+ if (cache_timestamp_hash)
+ *cache_timestamp_hash = timestamp_hash;
+
+ hashmap_free_and_replace(*unit_ids_map, ids);
+ hashmap_free_and_replace(*unit_names_map, names);
+ if (path_cache)
+ set_free_and_replace(*path_cache, paths);
+
+ return 1;
+}
+
+int unit_file_find_fragment(
+ Hashmap *unit_ids_map,
+ Hashmap *unit_name_map,
+ const char *unit_name,
+ const char **ret_fragment_path,
+ Set **ret_names) {
+
+ const char *fragment = NULL;
+ _cleanup_free_ char *template = NULL, *instance = NULL;
+ _cleanup_set_free_free_ Set *names = NULL;
+ char **t, **nnn;
+ int r, name_type;
+
+ /* Finds a fragment path, and returns the set of names:
+ * if we have …/foo.service and …/foo-alias.service→foo.service,
+ * and …/foo@.service and …/foo-alias@.service→foo@.service,
+ * and …/foo@inst.service,
+ * this should return:
+ * foo.service → …/foo.service, {foo.service, foo-alias.service},
+ * foo-alias.service → …/foo.service, {foo.service, foo-alias.service},
+ * foo@.service → …/foo@.service, {foo@.service, foo-alias@.service},
+ * foo-alias@.service → …/foo@.service, {foo@.service, foo-alias@.service},
+ * foo@bar.service → …/foo@.service, {foo@bar.service, foo-alias@bar.service},
+ * foo-alias@bar.service → …/foo@.service, {foo@bar.service, foo-alias@bar.service},
+ * foo-alias@inst.service → …/foo@inst.service, {foo@inst.service, foo-alias@inst.service}.
+ */
+
+ name_type = unit_name_to_instance(unit_name, &instance);
+ if (name_type < 0)
+ return name_type;
+
+ names = set_new(&string_hash_ops);
+ if (!names)
+ return -ENOMEM;
+
+ /* The unit always has its own name if it's not a template. */
+ if (IN_SET(name_type, UNIT_NAME_PLAIN, UNIT_NAME_INSTANCE)) {
+ r = set_put_strdup(&names, unit_name);
+ if (r < 0)
+ return r;
+ }
+
+ /* First try to load fragment under the original name */
+ r = unit_ids_map_get(unit_ids_map, unit_name, &fragment);
+ if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
+ return log_debug_errno(r, "Cannot load unit %s: %m", unit_name);
+
+ if (fragment) {
+ /* Add any aliases of the original name to the set of names */
+ nnn = hashmap_get(unit_name_map, basename(fragment));
+ STRV_FOREACH(t, nnn) {
+ if (name_type == UNIT_NAME_INSTANCE && unit_name_is_valid(*t, UNIT_NAME_TEMPLATE)) {
+ char *inst;
+
+ r = unit_name_replace_instance(*t, instance, &inst);
+ if (r < 0)
+ return log_debug_errno(r, "Cannot build instance name %s+%s: %m", *t, instance);
+
+ if (!streq(unit_name, inst))
+ log_debug("%s: %s has alias %s", __func__, unit_name, inst);
+
+ log_info("%s: %s+%s → %s", __func__, *t, instance, inst);
+ r = set_consume(names, inst);
+ } else {
+ if (!streq(unit_name, *t))
+ log_debug("%s: %s has alias %s", __func__, unit_name, *t);
+
+ r = set_put_strdup(&names, *t);
+ }
+ if (r < 0)
+ return r;
+ }
+ }
+
+ if (!fragment && name_type == UNIT_NAME_INSTANCE) {
+ /* Look for a fragment under the template name */
+
+ r = unit_name_template(unit_name, &template);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to determine template name: %m");
+
+ r = unit_ids_map_get(unit_ids_map, template, &fragment);
+ if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
+ return log_debug_errno(r, "Cannot load template %s: %m", template);
+
+ if (fragment) {
+ /* Add any aliases of the original name to the set of names */
+ nnn = hashmap_get(unit_name_map, basename(fragment));
+ STRV_FOREACH(t, nnn) {
+ _cleanup_free_ char *inst = NULL;
+ const char *inst_fragment = NULL;
+
+ r = unit_name_replace_instance(*t, instance, &inst);
+ if (r < 0)
+ return log_debug_errno(r, "Cannot build instance name %s+%s: %m", template, instance);
+
+ /* Exclude any aliases that point in some other direction. */
+ r = unit_ids_map_get(unit_ids_map, inst, &inst_fragment);
+ if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
+ return log_debug_errno(r, "Cannot find instance fragment %s: %m", inst);
+
+ if (inst_fragment &&
+ !streq(basename(inst_fragment), basename(fragment))) {
+ log_debug("Instance %s has fragment %s and is not an alias of %s.",
+ inst, inst_fragment, unit_name);
+ continue;
+ }
+
+ if (!streq(unit_name, inst))
+ log_debug("%s: %s has alias %s", __func__, unit_name, inst);
+ r = set_consume(names, TAKE_PTR(inst));
+ if (r < 0)
+ return r;
+ }
+ }
+ }
+
+ *ret_fragment_path = fragment;
+ *ret_names = TAKE_PTR(names);
+
+ // FIXME: if instance, consider any unit names with different template name
+ return 0;
+}
+
+static const char * const rlmap[] = {
+ "emergency", SPECIAL_EMERGENCY_TARGET,
+ "-b", SPECIAL_EMERGENCY_TARGET,
+ "rescue", SPECIAL_RESCUE_TARGET,
+ "single", SPECIAL_RESCUE_TARGET,
+ "-s", SPECIAL_RESCUE_TARGET,
+ "s", SPECIAL_RESCUE_TARGET,
+ "S", SPECIAL_RESCUE_TARGET,
+ "1", SPECIAL_RESCUE_TARGET,
+ "2", SPECIAL_MULTI_USER_TARGET,
+ "3", SPECIAL_MULTI_USER_TARGET,
+ "4", SPECIAL_MULTI_USER_TARGET,
+ "5", SPECIAL_GRAPHICAL_TARGET,
+ NULL
+};
+
+static const char * const rlmap_initrd[] = {
+ "emergency", SPECIAL_EMERGENCY_TARGET,
+ "rescue", SPECIAL_RESCUE_TARGET,
+ NULL
+};
+
+const char* runlevel_to_target(const char *word) {
+ const char * const *rlmap_ptr;
+ size_t i;
+
+ if (!word)
+ return NULL;
+
+ if (in_initrd()) {
+ word = startswith(word, "rd.");
+ if (!word)
+ return NULL;
+ }
+
+ rlmap_ptr = in_initrd() ? rlmap_initrd : rlmap;
+
+ for (i = 0; rlmap_ptr[i]; i += 2)
+ if (streq(word, rlmap_ptr[i]))
+ return rlmap_ptr[i+1];
+
+ return NULL;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+#include "hashmap.h"
+#include "time-util.h"
+#include "unit-name.h"
+
+typedef enum UnitFileState UnitFileState;
+typedef enum UnitFileScope UnitFileScope;
+typedef struct LookupPaths LookupPaths;
+
+enum UnitFileState {
+ UNIT_FILE_ENABLED,
+ UNIT_FILE_ENABLED_RUNTIME,
+ UNIT_FILE_LINKED,
+ UNIT_FILE_LINKED_RUNTIME,
+ UNIT_FILE_ALIAS,
+ UNIT_FILE_MASKED,
+ UNIT_FILE_MASKED_RUNTIME,
+ UNIT_FILE_STATIC,
+ UNIT_FILE_DISABLED,
+ UNIT_FILE_INDIRECT,
+ UNIT_FILE_GENERATED,
+ UNIT_FILE_TRANSIENT,
+ UNIT_FILE_BAD,
+ _UNIT_FILE_STATE_MAX,
+ _UNIT_FILE_STATE_INVALID = -1
+};
+
+enum UnitFileScope {
+ UNIT_FILE_SYSTEM,
+ UNIT_FILE_GLOBAL,
+ UNIT_FILE_USER,
+ _UNIT_FILE_SCOPE_MAX,
+ _UNIT_FILE_SCOPE_INVALID = -1
+};
+
+bool unit_type_may_alias(UnitType type) _const_;
+bool unit_type_may_template(UnitType type) _const_;
+
+int unit_symlink_name_compatible(const char *symlink, const char *target, bool instance_propagation);
+int unit_validate_alias_symlink_and_warn(const char *filename, const char *target);
+
+bool lookup_paths_timestamp_hash_same(const LookupPaths *lp, uint64_t timestamp_hash, uint64_t *ret_new);
+int unit_file_build_name_map(
+ const LookupPaths *lp,
+ uint64_t *cache_timestamp_hash,
+ Hashmap **unit_ids_map,
+ Hashmap **unit_names_map,
+ Set **path_cache);
+
+int unit_file_find_fragment(
+ Hashmap *unit_ids_map,
+ Hashmap *unit_name_map,
+ const char *unit_name,
+ const char **ret_fragment_path,
+ Set **ret_names);
+
+const char* runlevel_to_target(const char *rl);
udev-util.h
uid-range.c
uid-range.h
- unit-file.c
- unit-file.h
user-record-nss.c
user-record-nss.h
user-record-show.c
+++ /dev/null
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include "sd-id128.h"
-
-#include "dirent-util.h"
-#include "fd-util.h"
-#include "fs-util.h"
-#include "macro.h"
-#include "path-lookup.h"
-#include "set.h"
-#include "special.h"
-#include "stat-util.h"
-#include "string-util.h"
-#include "strv.h"
-#include "unit-file.h"
-
-bool unit_type_may_alias(UnitType type) {
- return IN_SET(type,
- UNIT_SERVICE,
- UNIT_SOCKET,
- UNIT_TARGET,
- UNIT_DEVICE,
- UNIT_TIMER,
- UNIT_PATH);
-}
-
-bool unit_type_may_template(UnitType type) {
- return IN_SET(type,
- UNIT_SERVICE,
- UNIT_SOCKET,
- UNIT_TARGET,
- UNIT_TIMER,
- UNIT_PATH);
-}
-
-int unit_symlink_name_compatible(const char *symlink, const char *target, bool instance_propagation) {
- _cleanup_free_ char *template = NULL;
- int r, un_type1, un_type2;
-
- un_type1 = unit_name_classify(symlink);
-
- /* The straightforward case: the symlink name matches the target and we have a valid unit */
- if (streq(symlink, target) &&
- (un_type1 & (UNIT_NAME_PLAIN | UNIT_NAME_INSTANCE)))
- return 1;
-
- r = unit_name_template(symlink, &template);
- if (r == -EINVAL)
- return 0; /* Not a template */
- if (r < 0)
- return r;
-
- un_type2 = unit_name_classify(target);
-
- /* An instance name points to a target that is just the template name */
- if (un_type1 == UNIT_NAME_INSTANCE &&
- un_type2 == UNIT_NAME_TEMPLATE &&
- streq(template, target))
- return 1;
-
- /* foo@.target.requires/bar@.service: instance will be propagated */
- if (instance_propagation &&
- un_type1 == UNIT_NAME_TEMPLATE &&
- un_type2 == UNIT_NAME_TEMPLATE &&
- streq(template, target))
- return 1;
-
- return 0;
-}
-
-int unit_validate_alias_symlink_and_warn(const char *filename, const char *target) {
- const char *src, *dst;
- _cleanup_free_ char *src_instance = NULL, *dst_instance = NULL;
- UnitType src_unit_type, dst_unit_type;
- int src_name_type, dst_name_type;
-
- /* Check if the *alias* symlink is valid. This applies to symlinks like
- * /etc/systemd/system/dbus.service → dbus-broker.service, but not to .wants or .requires symlinks
- * and such. Neither does this apply to symlinks which *link* units, i.e. symlinks to outside of the
- * unit lookup path.
- *
- * -EINVAL is returned if the something is wrong with the source filename or the source unit type is
- * not allowed to symlink,
- * -EXDEV if the target filename is not a valid unit name or doesn't match the source.
- */
-
- src = basename(filename);
- dst = basename(target);
-
- /* src checks */
-
- src_name_type = unit_name_to_instance(src, &src_instance);
- if (src_name_type < 0)
- return log_notice_errno(src_name_type,
- "%s: not a valid unit name \"%s\": %m", filename, src);
-
- src_unit_type = unit_name_to_type(src);
- assert(src_unit_type >= 0); /* unit_name_to_instance() checked the suffix already */
-
- if (!unit_type_may_alias(src_unit_type))
- return log_notice_errno(SYNTHETIC_ERRNO(EINVAL),
- "%s: symlinks are not allowed for units of this type, rejecting.",
- filename);
-
- if (src_name_type != UNIT_NAME_PLAIN &&
- !unit_type_may_template(src_unit_type))
- return log_notice_errno(SYNTHETIC_ERRNO(EINVAL),
- "%s: templates not allowed for %s units, rejecting.",
- filename, unit_type_to_string(src_unit_type));
-
- /* dst checks */
-
- dst_name_type = unit_name_to_instance(dst, &dst_instance);
- if (dst_name_type < 0)
- return log_notice_errno(dst_name_type == -EINVAL ? SYNTHETIC_ERRNO(EXDEV) : dst_name_type,
- "%s points to \"%s\" which is not a valid unit name: %m",
- filename, dst);
-
- if (!(dst_name_type == src_name_type ||
- (src_name_type == UNIT_NAME_INSTANCE && dst_name_type == UNIT_NAME_TEMPLATE)))
- return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
- "%s: symlink target name type \"%s\" does not match source, rejecting.",
- filename, dst);
-
- if (dst_name_type == UNIT_NAME_INSTANCE) {
- assert(src_instance);
- assert(dst_instance);
- if (!streq(src_instance, dst_instance))
- return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
- "%s: unit symlink target \"%s\" instance name doesn't match, rejecting.",
- filename, dst);
- }
-
- dst_unit_type = unit_name_to_type(dst);
- if (dst_unit_type != src_unit_type)
- return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
- "%s: symlink target \"%s\" has incompatible suffix, rejecting.",
- filename, dst);
-
- return 0;
-}
-
-#define FOLLOW_MAX 8
-
-static int unit_ids_map_get(
- Hashmap *unit_ids_map,
- const char *unit_name,
- const char **ret_fragment_path) {
-
- /* Resolve recursively until we hit an absolute path, i.e. a non-aliased unit.
- *
- * We distinguish the case where unit_name was not found in the hashmap at all, and the case where
- * some symlink was broken.
- *
- * If a symlink target points to an instance name, then we also check for the template. */
-
- const char *id = NULL;
- int r;
-
- for (unsigned n = 0; n < FOLLOW_MAX; n++) {
- const char *t = hashmap_get(unit_ids_map, id ?: unit_name);
- if (!t) {
- _cleanup_free_ char *template = NULL;
-
- if (!id)
- return -ENOENT;
-
- r = unit_name_template(id, &template);
- if (r == -EINVAL)
- return -ENXIO; /* we failed to find the symlink target */
- if (r < 0)
- return log_error_errno(r, "Failed to determine template name for %s: %m", id);
-
- t = hashmap_get(unit_ids_map, template);
- if (!t)
- return -ENXIO;
-
- /* We successfully switched from instanced name to a template, let's continue */
- }
-
- if (path_is_absolute(t)) {
- if (ret_fragment_path)
- *ret_fragment_path = t;
- return 0;
- }
-
- id = t;
- }
-
- return -ELOOP;
-}
-
-static bool lookup_paths_mtime_exclude(const LookupPaths *lp, const char *path) {
- /* Paths that are under our exclusive control. Users shall not alter those directly. */
-
- return streq_ptr(path, lp->generator) ||
- streq_ptr(path, lp->generator_early) ||
- streq_ptr(path, lp->generator_late) ||
- streq_ptr(path, lp->transient) ||
- streq_ptr(path, lp->persistent_control) ||
- streq_ptr(path, lp->runtime_control);
-}
-
-#define HASH_KEY SD_ID128_MAKE(4e,86,1b,e3,39,b3,40,46,98,5d,b8,11,34,8f,c3,c1)
-
-bool lookup_paths_timestamp_hash_same(const LookupPaths *lp, uint64_t timestamp_hash, uint64_t *ret_new) {
- struct siphash state;
-
- siphash24_init(&state, HASH_KEY.bytes);
-
- char **dir;
- STRV_FOREACH(dir, (char**) lp->search_path) {
- struct stat st;
-
- if (lookup_paths_mtime_exclude(lp, *dir))
- continue;
-
- /* Determine the latest lookup path modification time */
- if (stat(*dir, &st) < 0) {
- if (errno == ENOENT)
- continue;
-
- log_debug_errno(errno, "Failed to stat %s, ignoring: %m", *dir);
- continue;
- }
-
- siphash24_compress_usec_t(timespec_load(&st.st_mtim), &state);
- }
-
- uint64_t updated = siphash24_finalize(&state);
- if (ret_new)
- *ret_new = updated;
- if (updated != timestamp_hash)
- log_debug("Modification times have changed, need to update cache.");
- return updated == timestamp_hash;
-}
-
-int unit_file_build_name_map(
- const LookupPaths *lp,
- uint64_t *cache_timestamp_hash,
- Hashmap **unit_ids_map,
- Hashmap **unit_names_map,
- Set **path_cache) {
-
- /* Build two mappings: any name → main unit (i.e. the end result of symlink resolution), unit name →
- * all aliases (i.e. the entry for a given key is a a list of all names which point to this key). The
- * key is included in the value iff we saw a file or symlink with that name. In other words, if we
- * have a key, but it is not present in the value for itself, there was an alias pointing to it, but
- * the unit itself is not loadable.
- *
- * At the same, build a cache of paths where to find units. The non-const parameters are for input
- * and output. Existing contents will be freed before the new contents are stored.
- */
-
- _cleanup_hashmap_free_ Hashmap *ids = NULL, *names = NULL;
- _cleanup_set_free_free_ Set *paths = NULL;
- uint64_t timestamp_hash;
- char **dir;
- int r;
-
- /* Before doing anything, check if the timestamp hash that was passed is still valid.
- * If yes, do nothing. */
- if (cache_timestamp_hash &&
- lookup_paths_timestamp_hash_same(lp, *cache_timestamp_hash, ×tamp_hash))
- return 0;
-
- /* The timestamp hash is now set based on the mtimes from before when we start reading files.
- * If anything is modified concurrently, we'll consider the cache outdated. */
-
- if (path_cache) {
- paths = set_new(&path_hash_ops_free);
- if (!paths)
- return log_oom();
- }
-
- STRV_FOREACH(dir, (char**) lp->search_path) {
- struct dirent *de;
- _cleanup_closedir_ DIR *d = NULL;
-
- d = opendir(*dir);
- if (!d) {
- if (errno != ENOENT)
- log_warning_errno(errno, "Failed to open \"%s\", ignoring: %m", *dir);
- continue;
- }
-
- FOREACH_DIRENT_ALL(de, d, log_warning_errno(errno, "Failed to read \"%s\", ignoring: %m", *dir)) {
- char *filename;
- _cleanup_free_ char *_filename_free = NULL, *simplified = NULL;
- const char *suffix, *dst = NULL;
- bool valid_unit_name;
-
- valid_unit_name = unit_name_is_valid(de->d_name, UNIT_NAME_ANY);
-
- /* We only care about valid units and dirs with certain suffixes, let's ignore the
- * rest. */
- if (!valid_unit_name &&
- !ENDSWITH_SET(de->d_name, ".wants", ".requires", ".d"))
- continue;
-
- filename = path_join(*dir, de->d_name);
- if (!filename)
- return log_oom();
-
- if (paths) {
- r = set_consume(paths, filename);
- if (r < 0)
- return log_oom();
- /* We will still use filename below. This is safe because we know the set
- * holds a reference. */
- } else
- _filename_free = filename; /* Make sure we free the filename. */
-
- if (!valid_unit_name)
- continue;
- assert_se(suffix = strrchr(de->d_name, '.'));
-
- /* search_path is ordered by priority (highest first). If the name is already mapped
- * to something (incl. itself), it means that we have already seen it, and we should
- * ignore it here. */
- if (hashmap_contains(ids, de->d_name))
- continue;
-
- dirent_ensure_type(d, de);
- if (de->d_type == DT_LNK) {
- /* We don't explicitly check for alias loops here. unit_ids_map_get() which
- * limits the number of hops should be used to access the map. */
-
- _cleanup_free_ char *target = NULL;
-
- r = readlinkat_malloc(dirfd(d), de->d_name, &target);
- if (r < 0) {
- log_warning_errno(r, "Failed to read symlink %s/%s, ignoring: %m",
- *dir, de->d_name);
- continue;
- }
-
- const bool is_abs = path_is_absolute(target);
- if (lp->root_dir || !is_abs) {
- char *target_abs = path_join(is_abs ? lp->root_dir : *dir, target);
- if (!target_abs)
- return log_oom();
-
- free_and_replace(target, target_abs);
- }
-
- /* Get rid of "." and ".." components in target path */
- r = chase_symlinks(target, lp->root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL);
- if (r < 0) {
- log_warning_errno(r, "Failed to resolve symlink %s pointing to %s, ignoring: %m",
- filename, target);
- continue;
- }
-
- /* Check if the symlink goes outside of our search path.
- * If yes, it's a linked unit file or mask, and we don't care about the target name.
- * Let's just store the link destination directly.
- * If not, let's verify that it's a good symlink. */
- char *tail = path_startswith_strv(simplified, lp->search_path);
- if (tail) {
- bool self_alias;
-
- dst = basename(simplified);
- self_alias = streq(dst, de->d_name);
-
- if (is_path(tail))
- log_full(self_alias ? LOG_DEBUG : LOG_WARNING,
- "Suspicious symlink %s→%s, treating as alias.",
- filename, simplified);
-
- r = unit_validate_alias_symlink_and_warn(filename, simplified);
- if (r < 0)
- continue;
-
- if (self_alias) {
- /* A self-alias that has no effect */
- log_debug("%s: self-alias: %s/%s → %s, ignoring.",
- __func__, *dir, de->d_name, dst);
- continue;
- }
-
- log_debug("%s: alias: %s/%s → %s", __func__, *dir, de->d_name, dst);
- } else {
- dst = simplified;
-
- log_debug("%s: linked unit file: %s/%s → %s", __func__, *dir, de->d_name, dst);
- }
-
- } else {
- dst = filename;
- log_debug("%s: normal unit file: %s", __func__, dst);
- }
-
- r = hashmap_put_strdup(&ids, de->d_name, dst);
- if (r < 0)
- return log_warning_errno(r, "Failed to add entry to hashmap (%s→%s): %m",
- de->d_name, dst);
- }
- }
-
- /* Let's also put the names in the reverse db. */
- const char *dummy, *src;
- HASHMAP_FOREACH_KEY(dummy, src, ids) {
- const char *dst;
-
- r = unit_ids_map_get(ids, src, &dst);
- if (r < 0)
- continue;
-
- if (null_or_empty_path(dst) != 0)
- continue;
-
- /* Do not treat instance symlinks that point to the template as aliases */
- if (unit_name_is_valid(basename(dst), UNIT_NAME_TEMPLATE) &&
- unit_name_is_valid(src, UNIT_NAME_INSTANCE))
- continue;
-
- r = string_strv_hashmap_put(&names, basename(dst), src);
- if (r < 0)
- return log_warning_errno(r, "Failed to add entry to hashmap (%s→%s): %m",
- basename(dst), src);
- }
-
- if (cache_timestamp_hash)
- *cache_timestamp_hash = timestamp_hash;
-
- hashmap_free_and_replace(*unit_ids_map, ids);
- hashmap_free_and_replace(*unit_names_map, names);
- if (path_cache)
- set_free_and_replace(*path_cache, paths);
-
- return 1;
-}
-
-int unit_file_find_fragment(
- Hashmap *unit_ids_map,
- Hashmap *unit_name_map,
- const char *unit_name,
- const char **ret_fragment_path,
- Set **ret_names) {
-
- const char *fragment = NULL;
- _cleanup_free_ char *template = NULL, *instance = NULL;
- _cleanup_set_free_free_ Set *names = NULL;
- char **t, **nnn;
- int r, name_type;
-
- /* Finds a fragment path, and returns the set of names:
- * if we have …/foo.service and …/foo-alias.service→foo.service,
- * and …/foo@.service and …/foo-alias@.service→foo@.service,
- * and …/foo@inst.service,
- * this should return:
- * foo.service → …/foo.service, {foo.service, foo-alias.service},
- * foo-alias.service → …/foo.service, {foo.service, foo-alias.service},
- * foo@.service → …/foo@.service, {foo@.service, foo-alias@.service},
- * foo-alias@.service → …/foo@.service, {foo@.service, foo-alias@.service},
- * foo@bar.service → …/foo@.service, {foo@bar.service, foo-alias@bar.service},
- * foo-alias@bar.service → …/foo@.service, {foo@bar.service, foo-alias@bar.service},
- * foo-alias@inst.service → …/foo@inst.service, {foo@inst.service, foo-alias@inst.service}.
- */
-
- name_type = unit_name_to_instance(unit_name, &instance);
- if (name_type < 0)
- return name_type;
-
- names = set_new(&string_hash_ops);
- if (!names)
- return -ENOMEM;
-
- /* The unit always has its own name if it's not a template. */
- if (IN_SET(name_type, UNIT_NAME_PLAIN, UNIT_NAME_INSTANCE)) {
- r = set_put_strdup(&names, unit_name);
- if (r < 0)
- return r;
- }
-
- /* First try to load fragment under the original name */
- r = unit_ids_map_get(unit_ids_map, unit_name, &fragment);
- if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
- return log_debug_errno(r, "Cannot load unit %s: %m", unit_name);
-
- if (fragment) {
- /* Add any aliases of the original name to the set of names */
- nnn = hashmap_get(unit_name_map, basename(fragment));
- STRV_FOREACH(t, nnn) {
- if (name_type == UNIT_NAME_INSTANCE && unit_name_is_valid(*t, UNIT_NAME_TEMPLATE)) {
- char *inst;
-
- r = unit_name_replace_instance(*t, instance, &inst);
- if (r < 0)
- return log_debug_errno(r, "Cannot build instance name %s+%s: %m", *t, instance);
-
- if (!streq(unit_name, inst))
- log_debug("%s: %s has alias %s", __func__, unit_name, inst);
-
- log_info("%s: %s+%s → %s", __func__, *t, instance, inst);
- r = set_consume(names, inst);
- } else {
- if (!streq(unit_name, *t))
- log_debug("%s: %s has alias %s", __func__, unit_name, *t);
-
- r = set_put_strdup(&names, *t);
- }
- if (r < 0)
- return r;
- }
- }
-
- if (!fragment && name_type == UNIT_NAME_INSTANCE) {
- /* Look for a fragment under the template name */
-
- r = unit_name_template(unit_name, &template);
- if (r < 0)
- return log_debug_errno(r, "Failed to determine template name: %m");
-
- r = unit_ids_map_get(unit_ids_map, template, &fragment);
- if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
- return log_debug_errno(r, "Cannot load template %s: %m", template);
-
- if (fragment) {
- /* Add any aliases of the original name to the set of names */
- nnn = hashmap_get(unit_name_map, basename(fragment));
- STRV_FOREACH(t, nnn) {
- _cleanup_free_ char *inst = NULL;
- const char *inst_fragment = NULL;
-
- r = unit_name_replace_instance(*t, instance, &inst);
- if (r < 0)
- return log_debug_errno(r, "Cannot build instance name %s+%s: %m", template, instance);
-
- /* Exclude any aliases that point in some other direction. */
- r = unit_ids_map_get(unit_ids_map, inst, &inst_fragment);
- if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
- return log_debug_errno(r, "Cannot find instance fragment %s: %m", inst);
-
- if (inst_fragment &&
- !streq(basename(inst_fragment), basename(fragment))) {
- log_debug("Instance %s has fragment %s and is not an alias of %s.",
- inst, inst_fragment, unit_name);
- continue;
- }
-
- if (!streq(unit_name, inst))
- log_debug("%s: %s has alias %s", __func__, unit_name, inst);
- r = set_consume(names, TAKE_PTR(inst));
- if (r < 0)
- return r;
- }
- }
- }
-
- *ret_fragment_path = fragment;
- *ret_names = TAKE_PTR(names);
-
- // FIXME: if instance, consider any unit names with different template name
- return 0;
-}
-
-static const char * const rlmap[] = {
- "emergency", SPECIAL_EMERGENCY_TARGET,
- "-b", SPECIAL_EMERGENCY_TARGET,
- "rescue", SPECIAL_RESCUE_TARGET,
- "single", SPECIAL_RESCUE_TARGET,
- "-s", SPECIAL_RESCUE_TARGET,
- "s", SPECIAL_RESCUE_TARGET,
- "S", SPECIAL_RESCUE_TARGET,
- "1", SPECIAL_RESCUE_TARGET,
- "2", SPECIAL_MULTI_USER_TARGET,
- "3", SPECIAL_MULTI_USER_TARGET,
- "4", SPECIAL_MULTI_USER_TARGET,
- "5", SPECIAL_GRAPHICAL_TARGET,
- NULL
-};
-
-static const char * const rlmap_initrd[] = {
- "emergency", SPECIAL_EMERGENCY_TARGET,
- "rescue", SPECIAL_RESCUE_TARGET,
- NULL
-};
-
-const char* runlevel_to_target(const char *word) {
- const char * const *rlmap_ptr;
- size_t i;
-
- if (!word)
- return NULL;
-
- if (in_initrd()) {
- word = startswith(word, "rd.");
- if (!word)
- return NULL;
- }
-
- rlmap_ptr = in_initrd() ? rlmap_initrd : rlmap;
-
- for (i = 0; rlmap_ptr[i]; i += 2)
- if (streq(word, rlmap_ptr[i]))
- return rlmap_ptr[i+1];
-
- return NULL;
-}
+++ /dev/null
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include <stdbool.h>
-
-#include "hashmap.h"
-#include "time-util.h"
-#include "unit-name.h"
-
-typedef enum UnitFileState UnitFileState;
-typedef enum UnitFileScope UnitFileScope;
-typedef struct LookupPaths LookupPaths;
-
-enum UnitFileState {
- UNIT_FILE_ENABLED,
- UNIT_FILE_ENABLED_RUNTIME,
- UNIT_FILE_LINKED,
- UNIT_FILE_LINKED_RUNTIME,
- UNIT_FILE_ALIAS,
- UNIT_FILE_MASKED,
- UNIT_FILE_MASKED_RUNTIME,
- UNIT_FILE_STATIC,
- UNIT_FILE_DISABLED,
- UNIT_FILE_INDIRECT,
- UNIT_FILE_GENERATED,
- UNIT_FILE_TRANSIENT,
- UNIT_FILE_BAD,
- _UNIT_FILE_STATE_MAX,
- _UNIT_FILE_STATE_INVALID = -1
-};
-
-enum UnitFileScope {
- UNIT_FILE_SYSTEM,
- UNIT_FILE_GLOBAL,
- UNIT_FILE_USER,
- _UNIT_FILE_SCOPE_MAX,
- _UNIT_FILE_SCOPE_INVALID = -1
-};
-
-bool unit_type_may_alias(UnitType type) _const_;
-bool unit_type_may_template(UnitType type) _const_;
-
-int unit_symlink_name_compatible(const char *symlink, const char *target, bool instance_propagation);
-int unit_validate_alias_symlink_and_warn(const char *filename, const char *target);
-
-bool lookup_paths_timestamp_hash_same(const LookupPaths *lp, uint64_t timestamp_hash, uint64_t *ret_new);
-int unit_file_build_name_map(
- const LookupPaths *lp,
- uint64_t *cache_timestamp_hash,
- Hashmap **unit_ids_map,
- Hashmap **unit_names_map,
- Set **path_cache);
-
-int unit_file_find_fragment(
- Hashmap *unit_ids_map,
- Hashmap *unit_name_map,
- const char *unit_name,
- const char **ret_fragment_path,
- Set **ret_names);
-
-const char* runlevel_to_target(const char *rl);