]> git.proxmox.com Git - systemd.git/blobdiff - src/shared/install.c
New upstream version 240
[systemd.git] / src / shared / install.c
index 3d2b5ae77fef417f84e71a7bf448b90d73144434..3104043af64233534d5518fcd792496379d089db 100644 (file)
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
-  This file is part of systemd.
-
-  Copyright 2011 Lennart Poettering
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty <of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
+/* SPDX-License-Identifier: LGPL-2.1+ */
 
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <unistd.h>
-#include <string.h>
 #include <fnmatch.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
 
-#include "util.h"
-#include "mkdir.h"
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "conf-parser.h"
+#include "dirent-util.h"
+#include "extract-word.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
 #include "hashmap.h"
-#include "set.h"
-#include "path-util.h"
+#include "install-printf.h"
+#include "install.h"
+#include "locale-util.h"
+#include "log.h"
+#include "macro.h"
+#include "mkdir.h"
 #include "path-lookup.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "set.h"
+#include "special.h"
+#include "stat-util.h"
+#include "string-table.h"
+#include "string-util.h"
 #include "strv.h"
 #include "unit-name.h"
-#include "install.h"
-#include "conf-parser.h"
-#include "conf-files.h"
-#include "install-printf.h"
-#include "special.h"
+
+#define UNIT_FILE_FOLLOW_SYMLINK_MAX 64
+
+typedef enum SearchFlags {
+        SEARCH_LOAD                   = 1 << 0,
+        SEARCH_FOLLOW_CONFIG_SYMLINKS = 1 << 1,
+        SEARCH_DROPIN                 = 1 << 2,
+} SearchFlags;
 
 typedef struct {
-        OrderedHashmap *will_install;
-        OrderedHashmap *have_installed;
+        OrderedHashmap *will_process;
+        OrderedHashmap *have_processed;
 } InstallContext;
 
-static int in_search_path(const char *path, char **search) {
+typedef enum {
+        PRESET_UNKNOWN,
+        PRESET_ENABLE,
+        PRESET_DISABLE,
+} PresetAction;
+
+typedef struct {
+        char *pattern;
+        PresetAction action;
+        char **instances;
+} PresetRule;
+
+typedef struct {
+        PresetRule *rules;
+        size_t n_rules;
+} Presets;
+
+static inline bool unit_file_install_info_has_rules(const UnitFileInstallInfo *i) {
+        assert(i);
+
+        return !strv_isempty(i->aliases) ||
+               !strv_isempty(i->wanted_by) ||
+               !strv_isempty(i->required_by);
+}
+
+static inline bool unit_file_install_info_has_also(const UnitFileInstallInfo *i) {
+        assert(i);
+
+        return !strv_isempty(i->also);
+}
+
+static inline void presets_freep(Presets *p) {
+        size_t i;
+
+        if (!p)
+                return;
+
+        for (i = 0; i < p->n_rules; i++) {
+                free(p->rules[i].pattern);
+                strv_free(p->rules[i].instances);
+        }
+
+        free(p->rules);
+        p->n_rules = 0;
+}
+
+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);
+}
+
+static const char *unit_file_type_table[_UNIT_FILE_TYPE_MAX] = {
+        [UNIT_FILE_TYPE_REGULAR] = "regular",
+        [UNIT_FILE_TYPE_SYMLINK] = "symlink",
+        [UNIT_FILE_TYPE_MASKED] = "masked",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(unit_file_type, UnitFileType);
+
+static int in_search_path(const LookupPaths *p, const char *path) {
         _cleanup_free_ char *parent = NULL;
-        int r;
+        char **i;
 
         assert(path);
 
-        r = path_get_parent(path, &parent);
-        if (r < 0)
-                return r;
+        parent = dirname_malloc(path);
+        if (!parent)
+                return -ENOMEM;
 
-        return strv_contains(search, parent);
+        STRV_FOREACH(i, p->search_path)
+                if (path_equal(parent, *i))
+                        return true;
+
+        return false;
 }
 
-static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) {
-        char *p = NULL;
-        int r;
+static const char* skip_root(const LookupPaths *p, const char *path) {
+        char *e;
 
-        assert(scope >= 0);
-        assert(scope < _UNIT_FILE_SCOPE_MAX);
-        assert(ret);
+        assert(p);
+        assert(path);
 
-        switch (scope) {
+        if (!p->root_dir)
+                return path;
 
-        case UNIT_FILE_SYSTEM:
+        e = path_startswith(path, p->root_dir);
+        if (!e)
+                return NULL;
 
-                if (runtime)
-                        p = path_join(root_dir, "/run/systemd/system", NULL);
-                else
-                        p = path_join(root_dir, SYSTEM_CONFIG_UNIT_PATH, NULL);
-                break;
+        /* Make sure the returned path starts with a slash */
+        if (e[0] != '/') {
+                if (e == path || e[-1] != '/')
+                        return NULL;
 
-        case UNIT_FILE_GLOBAL:
+                e--;
+        }
 
-                if (root_dir)
-                        return -EINVAL;
+        return e;
+}
 
-                if (runtime)
-                        p = strdup("/run/systemd/user");
-                else
-                        p = strdup(USER_CONFIG_UNIT_PATH);
-                break;
+static int path_is_generator(const LookupPaths *p, const char *path) {
+        _cleanup_free_ char *parent = NULL;
 
-        case UNIT_FILE_USER:
+        assert(p);
+        assert(path);
 
-                if (root_dir)
-                        return -EINVAL;
+        parent = dirname_malloc(path);
+        if (!parent)
+                return -ENOMEM;
 
-                if (runtime)
-                        r = user_runtime_dir(&p);
-                else
-                        r = user_config_home(&p);
+        return path_equal_ptr(parent, p->generator) ||
+               path_equal_ptr(parent, p->generator_early) ||
+               path_equal_ptr(parent, p->generator_late);
+}
 
-                if (r <= 0)
-                        return r < 0 ? r : -ENOENT;
+static int path_is_transient(const LookupPaths *p, const char *path) {
+        _cleanup_free_ char *parent = NULL;
 
-                break;
+        assert(p);
+        assert(path);
 
-        default:
-                assert_not_reached("Bad scope");
+        parent = dirname_malloc(path);
+        if (!parent)
+                return -ENOMEM;
+
+        return path_equal_ptr(parent, p->transient);
+}
+
+static int path_is_control(const LookupPaths *p, const char *path) {
+        _cleanup_free_ char *parent = NULL;
+
+        assert(p);
+        assert(path);
+
+        parent = dirname_malloc(path);
+        if (!parent)
+                return -ENOMEM;
+
+        return path_equal_ptr(parent, p->persistent_control) ||
+               path_equal_ptr(parent, p->runtime_control);
+}
+
+static int path_is_config(const LookupPaths *p, const char *path, bool check_parent) {
+        _cleanup_free_ char *parent = NULL;
+
+        assert(p);
+        assert(path);
+
+        /* Note that we do *not* have generic checks for /etc or /run in place, since with
+         * them we couldn't discern configuration from transient or generated units */
+
+        if (check_parent) {
+                parent = dirname_malloc(path);
+                if (!parent)
+                        return -ENOMEM;
+
+                path = parent;
         }
 
-        if (!p)
+        return path_equal_ptr(path, p->persistent_config) ||
+               path_equal_ptr(path, p->runtime_config);
+}
+
+static int path_is_runtime(const LookupPaths *p, const char *path, bool check_parent) {
+        _cleanup_free_ char *parent = NULL;
+        const char *rpath;
+
+        assert(p);
+        assert(path);
+
+        /* Everything in /run is considered runtime. On top of that we also add
+         * explicit checks for the various runtime directories, as safety net. */
+
+        rpath = skip_root(p, path);
+        if (rpath && path_startswith(rpath, "/run"))
+                return true;
+
+        if (check_parent) {
+                parent = dirname_malloc(path);
+                if (!parent)
+                        return -ENOMEM;
+
+                path = parent;
+        }
+
+        return path_equal_ptr(path, p->runtime_config) ||
+               path_equal_ptr(path, p->generator) ||
+               path_equal_ptr(path, p->generator_early) ||
+               path_equal_ptr(path, p->generator_late) ||
+               path_equal_ptr(path, p->transient) ||
+               path_equal_ptr(path, p->runtime_control);
+}
+
+static int path_is_vendor(const LookupPaths *p, const char *path) {
+        const char *rpath;
+
+        assert(p);
+        assert(path);
+
+        rpath = skip_root(p, path);
+        if (!rpath)
+                return 0;
+
+        if (path_startswith(rpath, "/usr"))
+                return true;
+
+#if HAVE_SPLIT_USR
+        if (path_startswith(rpath, "/lib"))
+                return true;
+#endif
+
+        return path_equal(rpath, SYSTEM_DATA_UNIT_PATH);
+}
+
+int unit_file_changes_add(
+                UnitFileChange **changes,
+                size_t *n_changes,
+                UnitFileChangeType type,
+                const char *path,
+                const char *source) {
+
+        _cleanup_free_ char *p = NULL, *s = NULL;
+        UnitFileChange *c;
+
+        assert(path);
+        assert(!changes == !n_changes);
+
+        if (!changes)
+                return 0;
+
+        c = reallocarray(*changes, *n_changes + 1, sizeof(UnitFileChange));
+        if (!c)
+                return -ENOMEM;
+        *changes = c;
+
+        p = strdup(path);
+        if (source)
+                s = strdup(source);
+
+        if (!p || (source && !s))
                 return -ENOMEM;
 
-        *ret = p;
+        path_simplify(p, false);
+        if (s)
+                path_simplify(s, false);
+
+        c[*n_changes] = (UnitFileChange) { type, p, s };
+        p = s = NULL;
+        (*n_changes) ++;
         return 0;
 }
 
+void unit_file_changes_free(UnitFileChange *changes, size_t n_changes) {
+        size_t i;
+
+        assert(changes || n_changes == 0);
+
+        for (i = 0; i < n_changes; i++) {
+                free(changes[i].path);
+                free(changes[i].source);
+        }
+
+        free(changes);
+}
+
+void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *changes, size_t n_changes, bool quiet) {
+        size_t i;
+        bool logged = false;
+
+        assert(changes || n_changes == 0);
+        /* If verb is not specified, errors are not allowed! */
+        assert(verb || r >= 0);
+
+        for (i = 0; i < n_changes; i++) {
+                assert(verb || changes[i].type >= 0);
+
+                switch(changes[i].type) {
+                case UNIT_FILE_SYMLINK:
+                        if (!quiet)
+                                log_info("Created symlink %s %s %s.",
+                                         changes[i].path,
+                                         special_glyph(SPECIAL_GLYPH_ARROW),
+                                         changes[i].source);
+                        break;
+                case UNIT_FILE_UNLINK:
+                        if (!quiet)
+                                log_info("Removed %s.", changes[i].path);
+                        break;
+                case UNIT_FILE_IS_MASKED:
+                        if (!quiet)
+                                log_info("Unit %s is masked, ignoring.", changes[i].path);
+                        break;
+                case UNIT_FILE_IS_DANGLING:
+                        if (!quiet)
+                                log_info("Unit %s is an alias to a unit that is not present, ignoring.",
+                                         changes[i].path);
+                        break;
+                case -EEXIST:
+                        if (changes[i].source)
+                                log_error_errno(changes[i].type,
+                                                "Failed to %s unit, file %s already exists and is a symlink to %s.",
+                                                verb, changes[i].path, changes[i].source);
+                        else
+                                log_error_errno(changes[i].type,
+                                                "Failed to %s unit, file %s already exists.",
+                                                verb, changes[i].path);
+                        logged = true;
+                        break;
+                case -ERFKILL:
+                        log_error_errno(changes[i].type, "Failed to %s unit, unit %s is masked.",
+                                        verb, changes[i].path);
+                        logged = true;
+                        break;
+                case -EADDRNOTAVAIL:
+                        log_error_errno(changes[i].type, "Failed to %s unit, unit %s is transient or generated.",
+                                        verb, changes[i].path);
+                        logged = true;
+                        break;
+                case -ELOOP:
+                        log_error_errno(changes[i].type, "Failed to %s unit, refusing to operate on linked unit file %s",
+                                        verb, changes[i].path);
+                        logged = true;
+                        break;
+
+                case -ENOENT:
+                        log_error_errno(changes[i].type, "Failed to %s unit, unit %s does not exist.", verb, changes[i].path);
+                        logged = true;
+                        break;
+
+                default:
+                        assert(changes[i].type < 0);
+                        log_error_errno(changes[i].type, "Failed to %s unit, file %s: %m.",
+                                        verb, changes[i].path);
+                        logged = true;
+                }
+        }
+
+        if (r < 0 && !logged)
+                log_error_errno(r, "Failed to %s: %m.", verb);
+}
+
+/**
+ * Checks if two paths or symlinks from wd are the same, when root is the root of the filesystem.
+ * wc should be the full path in the host file system.
+ */
+static bool chroot_symlinks_same(const char *root, const char *wd, const char *a, const char *b) {
+        assert(path_is_absolute(wd));
+
+        /* This will give incorrect results if the paths are relative and go outside
+         * of the chroot. False negatives are possible. */
+
+        if (!root)
+                root = "/";
+
+        a = strjoina(path_is_absolute(a) ? root : wd, "/", a);
+        b = strjoina(path_is_absolute(b) ? root : wd, "/", b);
+        return path_equal_or_files_same(a, b, 0);
+}
+
+static int create_symlink(
+                const LookupPaths *paths,
+                const char *old_path,
+                const char *new_path,
+                bool force,
+                UnitFileChange **changes,
+                size_t *n_changes) {
+
+        _cleanup_free_ char *dest = NULL, *dirname = NULL;
+        const char *rp;
+        int r;
+
+        assert(old_path);
+        assert(new_path);
+
+        rp = skip_root(paths, old_path);
+        if (rp)
+                old_path = rp;
+
+        /* Actually create a symlink, and remember that we did. Is
+         * smart enough to check if there's already a valid symlink in
+         * place.
+         *
+         * Returns 1 if a symlink was created or already exists and points to
+         * the right place, or negative on error.
+         */
+
+        mkdir_parents_label(new_path, 0755);
+
+        if (symlink(old_path, new_path) >= 0) {
+                unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
+                return 1;
+        }
+
+        if (errno != EEXIST) {
+                unit_file_changes_add(changes, n_changes, -errno, new_path, NULL);
+                return -errno;
+        }
+
+        r = readlink_malloc(new_path, &dest);
+        if (r < 0) {
+                /* translate EINVAL (non-symlink exists) to EEXIST */
+                if (r == -EINVAL)
+                        r = -EEXIST;
+
+                unit_file_changes_add(changes, n_changes, r, new_path, NULL);
+                return r;
+        }
+
+        dirname = dirname_malloc(new_path);
+        if (!dirname)
+                return -ENOMEM;
+
+        if (chroot_symlinks_same(paths->root_dir, dirname, dest, old_path)) {
+                log_debug("Symlink %s â†’ %s already exists", new_path, dest);
+                return 1;
+        }
+
+        if (!force) {
+                unit_file_changes_add(changes, n_changes, -EEXIST, new_path, dest);
+                return -EEXIST;
+        }
+
+        r = symlink_atomic(old_path, new_path);
+        if (r < 0) {
+                unit_file_changes_add(changes, n_changes, r, new_path, NULL);
+                return r;
+        }
+
+        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL);
+        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
+
+        return 1;
+}
+
 static int mark_symlink_for_removal(
                 Set **remove_symlinks_to,
                 const char *p) {
@@ -121,7 +508,7 @@ static int mark_symlink_for_removal(
 
         assert(p);
 
-        r = set_ensure_allocated(remove_symlinks_to, &string_hash_ops);
+        r = set_ensure_allocated(remove_symlinks_to, &path_hash_ops);
         if (r < 0)
                 return r;
 
@@ -129,13 +516,15 @@ static int mark_symlink_for_removal(
         if (!n)
                 return -ENOMEM;
 
-        path_kill_slashes(n);
+        path_simplify(n, false);
 
         r = set_consume(*remove_symlinks_to, n);
+        if (r == -EEXIST)
+                return 0;
         if (r < 0)
-                return r == -EEXIST ? 0 : r;
+                return r;
 
-        return 0;
+        return 1;
 }
 
 static int remove_marked_symlinks_fd(
@@ -143,19 +532,22 @@ static int remove_marked_symlinks_fd(
                 int fd,
                 const char *path,
                 const char *config_path,
-                bool *deleted,
+                const LookupPaths *lp,
+                bool dry_run,
+                bool *restart,
                 UnitFileChange **changes,
-                unsigned *n_changes,
-                char** instance_whitelist) {
+                size_t *n_changes) {
 
         _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
         int r = 0;
 
         assert(remove_symlinks_to);
         assert(fd >= 0);
         assert(path);
         assert(config_path);
-        assert(deleted);
+        assert(lp);
+        assert(restart);
 
         d = fdopendir(fd);
         if (!d) {
@@ -165,27 +557,13 @@ static int remove_marked_symlinks_fd(
 
         rewinddir(d);
 
-        for (;;) {
-                struct dirent *de;
-
-                errno = 0;
-                de = readdir(d);
-                if (!de && errno != 0) {
-                        r = -errno;
-                        break;
-                }
-
-                if (!de)
-                        break;
-
-                if (hidden_file(de->d_name))
-                        continue;
+        FOREACH_DIRENT(de, d, return -errno) {
 
                 dirent_ensure_type(d, de);
 
                 if (de->d_type == DT_DIR) {
-                        int nfd, q;
                         _cleanup_free_ char *p = NULL;
+                        int nfd, q;
 
                         nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
                         if (nfd < 0) {
@@ -204,77 +582,65 @@ static int remove_marked_symlinks_fd(
                         }
 
                         /* This will close nfd, regardless whether it succeeds or not */
-                        q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, deleted, changes, n_changes, instance_whitelist);
+                        q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, lp, dry_run, restart, changes, n_changes);
                         if (q < 0 && r == 0)
                                 r = q;
 
                 } else if (de->d_type == DT_LNK) {
                         _cleanup_free_ char *p = NULL, *dest = NULL;
-                        int q;
+                        const char *rp;
                         bool found;
+                        int q;
 
                         if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
                                 continue;
 
-                        if (unit_name_is_valid(de->d_name, UNIT_NAME_INSTANCE) &&
-                            instance_whitelist &&
-                            !strv_contains(instance_whitelist, de->d_name)) {
-
-                                _cleanup_free_ char *w = NULL;
-
-                                /* OK, the file is not listed directly
-                                 * in the whitelist, so let's check if
-                                 * the template of it might be
-                                 * listed. */
-
-                                r = unit_name_template(de->d_name, &w);
-                                if (r < 0)
-                                        return r;
-
-                                if (!strv_contains(instance_whitelist, w))
-                                        continue;
-                        }
-
                         p = path_make_absolute(de->d_name, path);
                         if (!p)
                                 return -ENOMEM;
+                        path_simplify(p, false);
 
-                        q = readlink_and_canonicalize(p, &dest);
+                        q = readlink_malloc(p, &dest);
+                        if (q == -ENOENT)
+                                continue;
                         if (q < 0) {
-                                if (q == -ENOENT)
-                                        continue;
-
                                 if (r == 0)
                                         r = q;
                                 continue;
                         }
 
-                        found =
-                                set_get(remove_symlinks_to, dest) ||
-                                set_get(remove_symlinks_to, basename(dest));
+                        /* We remove all links pointing to a file or path that is marked, as well as all files sharing
+                         * the same name as a file that is marked. */
+
+                        found = set_contains(remove_symlinks_to, dest) ||
+                                set_contains(remove_symlinks_to, basename(dest)) ||
+                                set_contains(remove_symlinks_to, de->d_name);
 
                         if (!found)
                                 continue;
 
-                        if (unlink(p) < 0 && errno != ENOENT) {
-                                if (r == 0)
-                                        r = -errno;
-                                continue;
+                        if (!dry_run) {
+                                if (unlinkat(fd, de->d_name, 0) < 0 && errno != ENOENT) {
+                                        if (r == 0)
+                                                r = -errno;
+                                        unit_file_changes_add(changes, n_changes, -errno, p, NULL);
+                                        continue;
+                                }
+
+                                (void) rmdir_parents(p, config_path);
                         }
 
-                        path_kill_slashes(p);
-                        rmdir_parents(p, config_path);
                         unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, p, NULL);
 
-                        if (!set_get(remove_symlinks_to, p)) {
+                        /* Now, remember the full path (but with the root prefix removed) of
+                         * the symlink we just removed, and remove any symlinks to it, too. */
 
-                                q = mark_symlink_for_removal(&remove_symlinks_to, p);
-                                if (q < 0) {
-                                        if (r == 0)
-                                                r = q;
-                                } else
-                                        *deleted = true;
-                        }
+                        rp = skip_root(lp, p);
+                        q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: p);
+                        if (q < 0)
+                                return q;
+                        if (q > 0 && !dry_run)
+                                *restart = true;
                 }
         }
 
@@ -284,53 +650,82 @@ static int remove_marked_symlinks_fd(
 static int remove_marked_symlinks(
                 Set *remove_symlinks_to,
                 const char *config_path,
+                const LookupPaths *lp,
+                bool dry_run,
                 UnitFileChange **changes,
-                unsigned *n_changes,
-                char** instance_whitelist) {
+                size_t *n_changes) {
 
         _cleanup_close_ int fd = -1;
+        bool restart;
         int r = 0;
-        bool deleted;
 
         assert(config_path);
+        assert(lp);
 
         if (set_size(remove_symlinks_to) <= 0)
                 return 0;
 
-        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC);
         if (fd < 0)
-                return -errno;
+                return errno == ENOENT ? 0 : -errno;
 
         do {
                 int q, cfd;
-                deleted = false;
+                restart = false;
 
                 cfd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
-                if (cfd < 0) {
-                        r = -errno;
-                        break;
-                }
+                if (cfd < 0)
+                        return -errno;
 
                 /* This takes possession of cfd and closes it */
-                q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &deleted, changes, n_changes, instance_whitelist);
+                q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, lp, dry_run, &restart, changes, n_changes);
                 if (r == 0)
                         r = q;
-        } while (deleted);
+        } while (restart);
 
         return r;
 }
 
+static int is_symlink_with_known_name(const UnitFileInstallInfo *i, const char *name) {
+        int r;
+
+        if (streq(name, i->name))
+                return true;
+
+        if (strv_contains(i->aliases, name))
+                return true;
+
+        /* Look for template symlink matching DefaultInstance */
+        if (i->default_instance && unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) {
+                _cleanup_free_ char *s = NULL;
+
+                r = unit_name_replace_instance(i->name, i->default_instance, &s);
+                if (r < 0) {
+                        if (r != -EINVAL)
+                                return r;
+
+                } else if (streq(name, s))
+                        return true;
+        }
+
+        return false;
+}
+
 static int find_symlinks_fd(
-                const char *name,
+                const char *root_dir,
+                const UnitFileInstallInfo *i,
+                bool match_aliases,
+                bool ignore_same_name,
                 int fd,
                 const char *path,
                 const char *config_path,
                 bool *same_name_link) {
 
-        int r = 0;
         _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
+        int r = 0;
 
-        assert(name);
+        assert(i);
         assert(fd >= 0);
         assert(path);
         assert(config_path);
@@ -342,25 +737,13 @@ static int find_symlinks_fd(
                 return -errno;
         }
 
-        for (;;) {
-                struct dirent *de;
-
-                errno = 0;
-                de = readdir(d);
-                if (!de && errno != 0)
-                        return -errno;
-
-                if (!de)
-                        return r;
-
-                if (hidden_file(de->d_name))
-                        continue;
+        FOREACH_DIRENT(de, d, return -errno) {
 
                 dirent_ensure_type(d, de);
 
                 if (de->d_type == DT_DIR) {
-                        int nfd, q;
                         _cleanup_free_ char *p = NULL;
+                        int nfd, q;
 
                         nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
                         if (nfd < 0) {
@@ -379,7 +762,8 @@ static int find_symlinks_fd(
                         }
 
                         /* This will close nfd, regardless whether it succeeds or not */
-                        q = find_symlinks_fd(name, nfd, p, config_path, same_name_link);
+                        q = find_symlinks_fd(root_dir, i, match_aliases, ignore_same_name, nfd,
+                                             p, config_path, same_name_link);
                         if (q > 0)
                                 return 1;
                         if (r == 0)
@@ -387,7 +771,7 @@ static int find_symlinks_fd(
 
                 } else if (de->d_type == DT_LNK) {
                         _cleanup_free_ char *p = NULL, *dest = NULL;
-                        bool found_path, found_dest, b = false;
+                        bool found_path = false, found_dest, b = false;
                         int q;
 
                         /* Acquire symlink name */
@@ -396,36 +780,44 @@ static int find_symlinks_fd(
                                 return -ENOMEM;
 
                         /* Acquire symlink destination */
-                        q = readlink_and_canonicalize(p, &dest);
+                        q = readlink_malloc(p, &dest);
+                        if (q == -ENOENT)
+                                continue;
                         if (q < 0) {
-                                if (q == -ENOENT)
-                                        continue;
-
                                 if (r == 0)
                                         r = q;
                                 continue;
                         }
 
-                        /* Check if the symlink itself matches what we
-                         * are looking for */
-                        if (path_is_absolute(name))
-                                found_path = path_equal(p, name);
-                        else
-                                found_path = streq(de->d_name, name);
+                        /* Make absolute */
+                        if (!path_is_absolute(dest)) {
+                                char *x;
 
-                        /* Check if what the symlink points to
-                         * matches what we are looking for */
-                        if (path_is_absolute(name))
-                                found_dest = path_equal(dest, name);
-                        else
-                                found_dest = streq(basename(dest), name);
+                                x = prefix_root(root_dir, dest);
+                                if (!x)
+                                        return -ENOMEM;
+
+                                free_and_replace(dest, x);
+                        }
+
+                        assert(unit_name_is_valid(i->name, UNIT_NAME_ANY));
+                        if (!ignore_same_name)
+                                /* Check if the symlink itself matches what we are looking for.
+                                 *
+                                 * If ignore_same_name is specified, we are in one of the directories which
+                                 * have lower priority than the unit file, and even if a file or symlink with
+                                 * this name was found, we should ignore it. */
+                                 found_path = streq(de->d_name, i->name);
+
+                        /* Check if what the symlink points to matches what we are looking for */
+                        found_dest = streq(basename(dest), i->name);
 
                         if (found_path && found_dest) {
                                 _cleanup_free_ char *t = NULL;
 
                                 /* Filter out same name links in the main
                                  * config path */
-                                t = path_make_absolute(name, config_path);
+                                t = path_make_absolute(i->name, config_path);
                                 if (!t)
                                         return -ENOMEM;
 
@@ -434,484 +826,277 @@ static int find_symlinks_fd(
 
                         if (b)
                                 *same_name_link = true;
-                        else if (found_path || found_dest)
-                                return 1;
+                        else if (found_path || found_dest) {
+                                if (!match_aliases)
+                                        return 1;
+
+                                /* Check if symlink name is in the set of names used by [Install] */
+                                q = is_symlink_with_known_name(i, de->d_name);
+                                if (q < 0)
+                                        return q;
+                                if (q > 0)
+                                        return 1;
+                        }
                 }
         }
+
+        return r;
 }
 
 static int find_symlinks(
-                const char *name,
+                const char *root_dir,
+                const UnitFileInstallInfo *i,
+                bool match_name,
+                bool ignore_same_name,
                 const char *config_path,
                 bool *same_name_link) {
 
         int fd;
 
-        assert(name);
+        assert(i);
         assert(config_path);
         assert(same_name_link);
 
-        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC);
         if (fd < 0) {
-                if (errno == ENOENT)
+                if (IN_SET(errno, ENOENT, ENOTDIR, EACCES))
                         return 0;
                 return -errno;
         }
 
         /* This takes possession of fd and closes it */
-        return find_symlinks_fd(name, fd, config_path, config_path, same_name_link);
+        return find_symlinks_fd(root_dir, i, match_name, ignore_same_name, fd,
+                                config_path, config_path, same_name_link);
 }
 
 static int find_symlinks_in_scope(
                 UnitFileScope scope,
-                const char *root_dir,
-                const char *name,
+                const LookupPaths *paths,
+                const UnitFileInstallInfo *i,
+                bool match_name,
                 UnitFileState *state) {
 
+        bool same_name_link_runtime = false, same_name_link_config = false;
+        bool enabled_in_runtime = false, enabled_at_all = false;
+        bool ignore_same_name = false;
+        char **p;
         int r;
-        _cleanup_free_ char *normal_path = NULL, *runtime_path = NULL;
-        bool same_name_link_runtime = false, same_name_link = false;
 
-        assert(scope >= 0);
-        assert(scope < _UNIT_FILE_SCOPE_MAX);
-        assert(name);
+        assert(paths);
+        assert(i);
 
-        /* First look in runtime config path */
-        r = get_config_path(scope, true, root_dir, &normal_path);
-        if (r < 0)
-                return r;
+        /* As we iterate over the list of search paths in paths->search_path, we may encounter "same name"
+         * symlinks. The ones which are "below" (i.e. have lower priority) than the unit file itself are
+         * efectively masked, so we should ignore them. */
 
-        r = find_symlinks(name, normal_path, &same_name_link_runtime);
-        if (r < 0)
-                return r;
-        else if (r > 0) {
-                *state = UNIT_FILE_ENABLED_RUNTIME;
-                return r;
+        STRV_FOREACH(p, paths->search_path)  {
+                bool same_name_link = false;
+
+                r = find_symlinks(paths->root_dir, i, match_name, ignore_same_name, *p, &same_name_link);
+                if (r < 0)
+                        return r;
+                if (r > 0) {
+                        /* We found symlinks in this dir? Yay! Let's see where precisely it is enabled. */
+
+                        if (path_equal_ptr(*p, paths->persistent_config)) {
+                                /* This is the best outcome, let's return it immediately. */
+                                *state = UNIT_FILE_ENABLED;
+                                return 1;
+                        }
+
+                        /* look for global enablement of user units */
+                        if (scope == UNIT_FILE_USER && path_is_user_config_dir(*p)) {
+                                *state = UNIT_FILE_ENABLED;
+                                return 1;
+                        }
+
+                        r = path_is_runtime(paths, *p, false);
+                        if (r < 0)
+                                return r;
+                        if (r > 0)
+                                enabled_in_runtime = true;
+                        else
+                                enabled_at_all = true;
+
+                } else if (same_name_link) {
+                        if (path_equal_ptr(*p, paths->persistent_config))
+                                same_name_link_config = true;
+                        else {
+                                r = path_is_runtime(paths, *p, false);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0)
+                                        same_name_link_runtime = true;
+                        }
+                }
+
+                /* Check if next iteration will be "below" the unit file (either a regular file
+                 * or a symlink), and hence should be ignored */
+                if (!ignore_same_name && path_startswith(i->path, *p))
+                        ignore_same_name = true;
         }
 
-        /* Then look in the normal config path */
-        r = get_config_path(scope, false, root_dir, &runtime_path);
-        if (r < 0)
-                return r;
+        if (enabled_in_runtime) {
+                *state = UNIT_FILE_ENABLED_RUNTIME;
+                return 1;
+        }
 
-        r = find_symlinks(name, runtime_path, &same_name_link);
-        if (r < 0)
-                return r;
-        else if (r > 0) {
-                *state = UNIT_FILE_ENABLED;
-                return r;
+        /* Here's a special rule: if the unit we are looking for is an instance, and it symlinked in the search path
+         * outside of runtime and configuration directory, then we consider it statically enabled. Note we do that only
+         * for instance, not for regular names, as those are merely aliases, while instances explicitly instantiate
+         * something, and hence are a much stronger concept. */
+        if (enabled_at_all && unit_name_is_valid(i->name, UNIT_NAME_INSTANCE)) {
+                *state = UNIT_FILE_STATIC;
+                return 1;
         }
 
         /* Hmm, we didn't find it, but maybe we found the same name
          * link? */
+        if (same_name_link_config) {
+                *state = UNIT_FILE_LINKED;
+                return 1;
+        }
         if (same_name_link_runtime) {
                 *state = UNIT_FILE_LINKED_RUNTIME;
                 return 1;
-        } else if (same_name_link) {
-                *state = UNIT_FILE_LINKED;
-                return 1;
         }
 
         return 0;
 }
 
-int unit_file_mask(
-                UnitFileScope scope,
-                bool runtime,
-                const char *root_dir,
-                char **files,
-                bool force,
-                UnitFileChange **changes,
-                unsigned *n_changes) {
-
-        char **i;
-        _cleanup_free_ char *prefix = NULL;
-        int r;
+static void install_info_free(UnitFileInstallInfo *i) {
 
-        assert(scope >= 0);
-        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        if (!i)
+                return;
 
-        r = get_config_path(scope, runtime, root_dir, &prefix);
-        if (r < 0)
-                return r;
+        free(i->name);
+        free(i->path);
+        strv_free(i->aliases);
+        strv_free(i->wanted_by);
+        strv_free(i->required_by);
+        strv_free(i->also);
+        free(i->default_instance);
+        free(i->symlink_target);
+        free(i);
+}
 
-        STRV_FOREACH(i, files) {
-                _cleanup_free_ char *path = NULL;
+static void install_context_done(InstallContext *c) {
+        assert(c);
 
-                if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) {
-                        if (r == 0)
-                                r = -EINVAL;
-                        continue;
-                }
+        c->will_process = ordered_hashmap_free_with_destructor(c->will_process, install_info_free);
+        c->have_processed = ordered_hashmap_free_with_destructor(c->have_processed, install_info_free);
+}
 
-                path = path_make_absolute(*i, prefix);
-                if (!path) {
-                        r = -ENOMEM;
-                        break;
-                }
+static UnitFileInstallInfo *install_info_find(InstallContext *c, const char *name) {
+        UnitFileInstallInfo *i;
 
-                if (symlink("/dev/null", path) >= 0) {
-                        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");
-                        continue;
-                }
+        i = ordered_hashmap_get(c->have_processed, name);
+        if (i)
+                return i;
 
-                if (errno == EEXIST) {
+        return ordered_hashmap_get(c->will_process, name);
+}
 
-                        if (null_or_empty_path(path) > 0)
-                                continue;
+static int install_info_may_process(
+                const UnitFileInstallInfo *i,
+                const LookupPaths *paths,
+                UnitFileChange **changes,
+                size_t *n_changes) {
+        assert(i);
+        assert(paths);
 
-                        if (force) {
-                                if (symlink_atomic("/dev/null", path) >= 0) {
-                                        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
-                                        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");
-                                        continue;
-                                }
-                        }
+        /* Checks whether the loaded unit file is one we should process, or is masked,
+         * transient or generated and thus not subject to enable/disable operations. */
 
-                        if (r == 0)
-                                r = -EEXIST;
-                } else {
-                        if (r == 0)
-                                r = -errno;
-                }
+        if (i->type == UNIT_FILE_TYPE_MASKED) {
+                unit_file_changes_add(changes, n_changes, -ERFKILL, i->path, NULL);
+                return -ERFKILL;
+        }
+        if (path_is_generator(paths, i->path) ||
+            path_is_transient(paths, i->path)) {
+                unit_file_changes_add(changes, n_changes, -EADDRNOTAVAIL, i->path, NULL);
+                return -EADDRNOTAVAIL;
         }
 
-        return r;
+        return 0;
 }
 
-int unit_file_unmask(
-                UnitFileScope scope,
-                bool runtime,
-                const char *root_dir,
-                char **files,
-                UnitFileChange **changes,
-                unsigned *n_changes) {
+/**
+ * Adds a new UnitFileInstallInfo entry under name in the InstallContext.will_process
+ * hashmap, or retrieves the existing one if already present.
+ *
+ * Returns negative on error, 0 if the unit was already known, 1 otherwise.
+ */
+static int install_info_add(
+                InstallContext *c,
+                const char *name,
+                const char *path,
+                bool auxiliary,
+                UnitFileInstallInfo **ret) {
 
-        char **i, *config_path = NULL;
-        int r, q;
-        Set *remove_symlinks_to = NULL;
+        UnitFileInstallInfo *i = NULL;
+        int r;
 
-        assert(scope >= 0);
-        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(c);
+        assert(name || path);
+
+        if (!name)
+                name = basename(path);
+
+        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
+                return -EINVAL;
+
+        i = install_info_find(c, name);
+        if (i) {
+                i->auxiliary = i->auxiliary && auxiliary;
+
+                if (ret)
+                        *ret = i;
+                return 0;
+        }
 
-        r = get_config_path(scope, runtime, root_dir, &config_path);
+        r = ordered_hashmap_ensure_allocated(&c->will_process, &string_hash_ops);
         if (r < 0)
-                goto finish;
+                return r;
 
-        STRV_FOREACH(i, files) {
-                _cleanup_free_ char *path = NULL;
+        i = new(UnitFileInstallInfo, 1);
+        if (!i)
+                return -ENOMEM;
 
-                if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) {
-                        if (r == 0)
-                                r = -EINVAL;
-                        continue;
-                }
+        *i = (UnitFileInstallInfo) {
+                .type = _UNIT_FILE_TYPE_INVALID,
+                .auxiliary = auxiliary,
+        };
 
-                path = path_make_absolute(*i, config_path);
-                if (!path) {
-                        r = -ENOMEM;
-                        break;
-                }
+        i->name = strdup(name);
+        if (!i->name) {
+                r = -ENOMEM;
+                goto fail;
+        }
 
-                q = null_or_empty_path(path);
-                if (q > 0) {
-                        if (unlink(path) < 0)
-                                q = -errno;
-                        else {
-                                q = mark_symlink_for_removal(&remove_symlinks_to, path);
-                                unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
-                        }
+        if (path) {
+                i->path = strdup(path);
+                if (!i->path) {
+                        r = -ENOMEM;
+                        goto fail;
                 }
-
-                if (q != -ENOENT && r == 0)
-                        r = q;
         }
 
+        r = ordered_hashmap_put(c->will_process, i->name, i);
+        if (r < 0)
+                goto fail;
 
-finish:
-        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
-        if (r == 0)
-                r = q;
+        if (ret)
+                *ret = i;
 
-        set_free_free(remove_symlinks_to);
-        free(config_path);
+        return 1;
 
+fail:
+        install_info_free(i);
         return r;
 }
 
-int unit_file_link(
-                UnitFileScope scope,
-                bool runtime,
-                const char *root_dir,
-                char **files,
-                bool force,
-                UnitFileChange **changes,
-                unsigned *n_changes) {
-
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
-        char **i;
-        _cleanup_free_ char *config_path = NULL;
-        int r, q;
-
-        assert(scope >= 0);
-        assert(scope < _UNIT_FILE_SCOPE_MAX);
-
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
-        if (r < 0)
-                return r;
-
-        r = get_config_path(scope, runtime, root_dir, &config_path);
-        if (r < 0)
-                return r;
-
-        STRV_FOREACH(i, files) {
-                _cleanup_free_ char *path = NULL;
-                char *fn;
-                struct stat st;
-
-                fn = basename(*i);
-
-                if (!path_is_absolute(*i) ||
-                    !unit_name_is_valid(fn, UNIT_NAME_ANY)) {
-                        if (r == 0)
-                                r = -EINVAL;
-                        continue;
-                }
-
-                if (lstat(*i, &st) < 0) {
-                        if (r == 0)
-                                r = -errno;
-                        continue;
-                }
-
-                if (!S_ISREG(st.st_mode)) {
-                        r = -ENOENT;
-                        continue;
-                }
-
-                q = in_search_path(*i, paths.unit_path);
-                if (q < 0)
-                        return q;
-
-                if (q > 0)
-                        continue;
-
-                path = path_make_absolute(fn, config_path);
-                if (!path)
-                        return -ENOMEM;
-
-                if (symlink(*i, path) >= 0) {
-                        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);
-                        continue;
-                }
-
-                if (errno == EEXIST) {
-                        _cleanup_free_ char *dest = NULL;
-
-                        q = readlink_and_make_absolute(path, &dest);
-                        if (q < 0 && errno != ENOENT) {
-                                if (r == 0)
-                                        r = q;
-                                continue;
-                        }
-
-                        if (q >= 0 && path_equal(dest, *i))
-                                continue;
-
-                        if (force) {
-                                if (symlink_atomic(*i, path) >= 0) {
-                                        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
-                                        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);
-                                        continue;
-                                }
-                        }
-
-                        if (r == 0)
-                                r = -EEXIST;
-                } else {
-                        if (r == 0)
-                                r = -errno;
-                }
-        }
-
-        return r;
-}
-
-void unit_file_list_free(Hashmap *h) {
-        UnitFileList *i;
-
-        while ((i = hashmap_steal_first(h))) {
-                free(i->path);
-                free(i);
-        }
-
-        hashmap_free(h);
-}
-
-int unit_file_changes_add(
-                UnitFileChange **changes,
-                unsigned *n_changes,
-                UnitFileChangeType type,
-                const char *path,
-                const char *source) {
-
-        UnitFileChange *c;
-        unsigned i;
-
-        assert(path);
-        assert(!changes == !n_changes);
-
-        if (!changes)
-                return 0;
-
-        c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange));
-        if (!c)
-                return -ENOMEM;
-
-        *changes = c;
-        i = *n_changes;
-
-        c[i].type = type;
-        c[i].path = strdup(path);
-        if (!c[i].path)
-                return -ENOMEM;
-
-        path_kill_slashes(c[i].path);
-
-        if (source) {
-                c[i].source = strdup(source);
-                if (!c[i].source) {
-                        free(c[i].path);
-                        return -ENOMEM;
-                }
-
-                path_kill_slashes(c[i].path);
-        } else
-                c[i].source = NULL;
-
-        *n_changes = i+1;
-        return 0;
-}
-
-void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) {
-        unsigned i;
-
-        assert(changes || n_changes == 0);
-
-        if (!changes)
-                return;
-
-        for (i = 0; i < n_changes; i++) {
-                free(changes[i].path);
-                free(changes[i].source);
-        }
-
-        free(changes);
-}
-
-static void install_info_free(UnitFileInstallInfo *i) {
-        assert(i);
-
-        free(i->name);
-        free(i->path);
-        strv_free(i->aliases);
-        strv_free(i->wanted_by);
-        strv_free(i->required_by);
-        strv_free(i->also);
-        free(i->default_instance);
-        free(i);
-}
-
-static void install_info_hashmap_free(OrderedHashmap *m) {
-        UnitFileInstallInfo *i;
-
-        if (!m)
-                return;
-
-        while ((i = ordered_hashmap_steal_first(m)))
-                install_info_free(i);
-
-        ordered_hashmap_free(m);
-}
-
-static void install_context_done(InstallContext *c) {
-        assert(c);
-
-        install_info_hashmap_free(c->will_install);
-        install_info_hashmap_free(c->have_installed);
-
-        c->will_install = c->have_installed = NULL;
-}
-
-static int install_info_add(
-                InstallContext *c,
-                const char *name,
-                const char *path) {
-        UnitFileInstallInfo *i = NULL;
-        int r;
-
-        assert(c);
-        assert(name || path);
-
-        if (!name)
-                name = basename(path);
-
-        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
-                return -EINVAL;
-
-        if (ordered_hashmap_get(c->have_installed, name) ||
-            ordered_hashmap_get(c->will_install, name))
-                return 0;
-
-        r = ordered_hashmap_ensure_allocated(&c->will_install, &string_hash_ops);
-        if (r < 0)
-                return r;
-
-        i = new0(UnitFileInstallInfo, 1);
-        if (!i)
-                return -ENOMEM;
-
-        i->name = strdup(name);
-        if (!i->name) {
-                r = -ENOMEM;
-                goto fail;
-        }
-
-        if (path) {
-                i->path = strdup(path);
-                if (!i->path) {
-                        r = -ENOMEM;
-                        goto fail;
-                }
-        }
-
-        r = ordered_hashmap_put(c->will_install, i->name, i);
-        if (r < 0)
-                goto fail;
-
-        return 0;
-
-fail:
-        if (i)
-                install_info_free(i);
-
-        return r;
-}
-
-static int install_info_add_auto(
-                InstallContext *c,
-                const char *name_or_path) {
-
-        assert(c);
-        assert(name_or_path);
-
-        if (path_is_absolute(name_or_path))
-                return install_info_add(c, NULL, name_or_path);
-        else
-                return install_info_add(c, name_or_path, NULL);
-}
-
-static int config_parse_also(
+static int config_parse_alias(
                 const char *unit,
                 const char *filename,
                 unsigned line,
@@ -923,39 +1108,24 @@ static int config_parse_also(
                 void *data,
                 void *userdata) {
 
-        size_t l;
-        const char *word, *state;
-        InstallContext *c = data;
-        UnitFileInstallInfo *i = userdata;
+        UnitType type;
 
+        assert(unit);
         assert(filename);
         assert(lvalue);
         assert(rvalue);
 
-        FOREACH_WORD_QUOTED(word, l, rvalue, state) {
-                _cleanup_free_ char *n;
-                int r;
-
-                n = strndup(word, l);
-                if (!n)
-                        return -ENOMEM;
-
-                r = install_info_add(c, n, NULL);
-                if (r < 0)
-                        return r;
-
-                r = strv_extend(&i->also, n);
-                if (r < 0)
-                        return r;
-        }
-        if (!isempty(state))
-                log_syntax(unit, LOG_ERR, filename, line, EINVAL,
-                           "Trailing garbage, ignoring.");
+        type = unit_name_to_type(unit);
+        if (!unit_type_may_alias(type))
+                return log_syntax(unit, LOG_WARNING, filename, line, 0,
+                                  "Alias= is not allowed for %s units, ignoring.",
+                                  unit_type_to_string(type));
 
-        return 0;
+        return config_parse_strv(unit, filename, line, section, section_line,
+                                 lvalue, ltype, rvalue, data, userdata);
 }
 
-static int config_parse_user(
+static int config_parse_also(
                 const char *unit,
                 const char *filename,
                 unsigned line,
@@ -967,20 +1137,41 @@ static int config_parse_user(
                 void *data,
                 void *userdata) {
 
-        UnitFileInstallInfo *i = data;
-        char *printed;
+        UnitFileInstallInfo *info = userdata, *alsoinfo = NULL;
+        InstallContext *c = data;
         int r;
 
+        assert(unit);
         assert(filename);
         assert(lvalue);
         assert(rvalue);
 
-        r = install_full_printf(i, rvalue, &printed);
-        if (r < 0)
-                return r;
+        for (;;) {
+                _cleanup_free_ char *word = NULL, *printed = NULL;
+
+                r = extract_first_word(&rvalue, &word, NULL, 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                r = install_full_printf(info, word, &printed);
+                if (r < 0)
+                        return r;
+
+                if (!unit_name_is_valid(printed, UNIT_NAME_ANY))
+                        return -EINVAL;
+
+                r = install_info_add(c, printed, NULL, true, &alsoinfo);
+                if (r < 0)
+                        return r;
+
+                r = strv_push(&info->also, printed);
+                if (r < 0)
+                        return r;
 
-        free(i->user);
-        i->user = printed;
+                printed = NULL;
+        }
 
         return 0;
 }
@@ -998,26 +1189,30 @@ static int config_parse_default_instance(
                 void *userdata) {
 
         UnitFileInstallInfo *i = data;
-        char *printed;
+        _cleanup_free_ char *printed = NULL;
         int r;
 
+        assert(unit);
         assert(filename);
         assert(lvalue);
         assert(rvalue);
 
+        if (unit_name_is_valid(unit, UNIT_NAME_INSTANCE))
+                /* When enabling an instance, we might be using a template unit file,
+                 * but we should ignore DefaultInstance silently. */
+                return 0;
+        if (!unit_name_is_valid(unit, UNIT_NAME_TEMPLATE))
+                return log_syntax(unit, LOG_WARNING, filename, line, 0,
+                                  "DefaultInstance= only makes sense for template units, ignoring.");
+
         r = install_full_printf(i, rvalue, &printed);
         if (r < 0)
                 return r;
 
-        if (!unit_instance_is_valid(printed)) {
-                free(printed);
+        if (!unit_instance_is_valid(printed))
                 return -EINVAL;
-        }
 
-        free(i->default_instance);
-        i->default_instance = printed;
-
-        return 0;
+        return free_and_replace(i->default_instance, printed);
 }
 
 static int unit_file_load(
@@ -1025,54 +1220,100 @@ static int unit_file_load(
                 UnitFileInstallInfo *info,
                 const char *path,
                 const char *root_dir,
-                bool allow_symlink,
-                bool load,
-                bool *also) {
+                SearchFlags flags) {
 
         const ConfigTableItem items[] = {
-                { "Install", "Alias",           config_parse_strv,             0, &info->aliases           },
+                { "Install", "Alias",           config_parse_alias,            0, &info->aliases           },
                 { "Install", "WantedBy",        config_parse_strv,             0, &info->wanted_by         },
                 { "Install", "RequiredBy",      config_parse_strv,             0, &info->required_by       },
                 { "Install", "DefaultInstance", config_parse_default_instance, 0, info                     },
                 { "Install", "Also",            config_parse_also,             0, c                        },
-                { "Exec",    "User",            config_parse_user,             0, info                     },
                 {}
         };
 
+        UnitType type;
         _cleanup_fclose_ FILE *f = NULL;
-        int fd, r;
+        _cleanup_close_ int fd = -1;
+        struct stat st;
+        int r;
 
-        assert(c);
         assert(info);
         assert(path);
 
-        if (!isempty(root_dir))
-                path = strjoina(root_dir, "/", path);
+        if (!(flags & SEARCH_DROPIN)) {
+                /* Loading or checking for the main unit file… */
 
-        if (!load) {
-                r = access(path, F_OK) ? -errno : 0;
-                return r;
+                type = unit_name_to_type(info->name);
+                if (type < 0)
+                        return -EINVAL;
+                if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE|UNIT_NAME_INSTANCE) && !unit_type_may_template(type))
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "Unit type %s cannot be templated.", unit_type_to_string(type));
+
+                if (!(flags & SEARCH_LOAD)) {
+                        r = lstat(path, &st);
+                        if (r < 0)
+                                return -errno;
+
+                        if (null_or_empty(&st))
+                                info->type = UNIT_FILE_TYPE_MASKED;
+                        else if (S_ISREG(st.st_mode))
+                                info->type = UNIT_FILE_TYPE_REGULAR;
+                        else if (S_ISLNK(st.st_mode))
+                                return -ELOOP;
+                        else if (S_ISDIR(st.st_mode))
+                                return -EISDIR;
+                        else
+                                return -ENOTTY;
+
+                        return 0;
+                }
+
+                fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+                if (fd < 0)
+                        return -errno;
+        } else {
+                /* Operating on a drop-in file. If we aren't supposed to load the unit file drop-ins don't matter, let's hence shortcut this. */
+
+                if (!(flags & SEARCH_LOAD))
+                        return 0;
+
+                fd = chase_symlinks_and_open(path, root_dir, 0, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+                if (fd < 0)
+                        return fd;
         }
 
-        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|(allow_symlink ? 0 : O_NOFOLLOW));
-        if (fd < 0)
+        if (fstat(fd, &st) < 0)
                 return -errno;
 
-        f = fdopen(fd, "re");
-        if (!f) {
-                safe_close(fd);
-                return -ENOMEM;
+        if (null_or_empty(&st)) {
+                if ((flags & SEARCH_DROPIN) == 0)
+                        info->type = UNIT_FILE_TYPE_MASKED;
+
+                return 0;
         }
 
-        r = config_parse(NULL, path, f,
-                         NULL,
-                         config_item_table_lookup, items,
-                         true, true, false, info);
+        r = stat_verify_regular(&st);
         if (r < 0)
                 return r;
 
-        if (also)
-                *also = !strv_isempty(info->also);
+        f = fdopen(fd, "r");
+        if (!f)
+                return -errno;
+        fd = -1;
+
+        /* c is only needed if we actually load the file (it's referenced from items[] btw, in case you wonder.) */
+        assert(c);
+
+        r = config_parse(info->name, path, f,
+                         NULL,
+                         config_item_table_lookup, items,
+                         CONFIG_PARSE_RELAXED|CONFIG_PARSE_ALLOW_INCLUDE, info);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse %s: %m", info->name);
+
+        if ((flags & SEARCH_DROPIN) == 0)
+                info->type = UNIT_FILE_TYPE_REGULAR;
 
         return
                 (int) strv_length(info->aliases) +
@@ -1080,162 +1321,406 @@ static int unit_file_load(
                 (int) strv_length(info->required_by);
 }
 
+static int unit_file_load_or_readlink(
+                InstallContext *c,
+                UnitFileInstallInfo *info,
+                const char *path,
+                const char *root_dir,
+                SearchFlags flags) {
+
+        _cleanup_free_ char *target = NULL;
+        int r;
+
+        r = unit_file_load(c, info, path, root_dir, flags);
+        if (r != -ELOOP || (flags & SEARCH_DROPIN))
+                return r;
+
+        /* This is a symlink, let's read it. */
+
+        r = readlink_malloc(path, &target);
+        if (r < 0)
+                return r;
+
+        if (path_equal(target, "/dev/null"))
+                info->type = UNIT_FILE_TYPE_MASKED;
+        else {
+                const char *bn;
+                UnitType a, b;
+
+                bn = basename(target);
+
+                if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) {
+
+                        if (!unit_name_is_valid(bn, UNIT_NAME_PLAIN))
+                                return -EINVAL;
+
+                } else if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
+
+                        if (!unit_name_is_valid(bn, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
+                                return -EINVAL;
+
+                } else if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE)) {
+
+                        if (!unit_name_is_valid(bn, UNIT_NAME_TEMPLATE))
+                                return -EINVAL;
+                } else
+                        return -EINVAL;
+
+                /* Enforce that the symlink destination does not
+                 * change the unit file type. */
+
+                a = unit_name_to_type(info->name);
+                b = unit_name_to_type(bn);
+                if (a < 0 || b < 0 || a != b)
+                        return -EINVAL;
+
+                if (path_is_absolute(target))
+                        /* This is an absolute path, prefix the root so that we always deal with fully qualified paths */
+                        info->symlink_target = prefix_root(root_dir, target);
+                else
+                        /* This is a relative path, take it relative to the dir the symlink is located in. */
+                        info->symlink_target = file_in_same_dir(path, target);
+                if (!info->symlink_target)
+                        return -ENOMEM;
+
+                info->type = UNIT_FILE_TYPE_SYMLINK;
+        }
+
+        return 0;
+}
+
 static int unit_file_search(
                 InstallContext *c,
                 UnitFileInstallInfo *info,
                 const LookupPaths *paths,
-                const char *root_dir,
-                bool allow_symlink,
-                bool load,
-                bool *also) {
+                SearchFlags flags) {
 
+        const char *dropin_dir_name = NULL, *dropin_template_dir_name = NULL;
+        _cleanup_strv_free_ char **dirs = NULL, **files = NULL;
+        _cleanup_free_ char *template = NULL;
+        bool found_unit = false;
+        int r, result;
         char **p;
-        int r;
 
-        assert(c);
         assert(info);
         assert(paths);
 
+        /* Was this unit already loaded? */
+        if (info->type != _UNIT_FILE_TYPE_INVALID)
+                return 0;
+
         if (info->path)
-                return unit_file_load(c, info, info->path, root_dir, allow_symlink, load, also);
+                return unit_file_load_or_readlink(c, info, info->path, paths->root_dir, flags);
 
         assert(info->name);
 
-        STRV_FOREACH(p, paths->unit_path) {
+        if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
+                r = unit_name_template(info->name, &template);
+                if (r < 0)
+                        return r;
+        }
+
+        STRV_FOREACH(p, paths->search_path) {
                 _cleanup_free_ char *path = NULL;
 
-                path = strjoin(*p, "/", info->name, NULL);
+                path = strjoin(*p, "/", info->name);
                 if (!path)
                         return -ENOMEM;
 
-                r = unit_file_load(c, info, path, root_dir, allow_symlink, load, also);
+                r = unit_file_load_or_readlink(c, info, path, paths->root_dir, flags);
                 if (r >= 0) {
-                        info->path = path;
-                        path = NULL;
-                        return r;
-                }
-                if (r != -ENOENT && r != -ELOOP)
+                        info->path = TAKE_PTR(path);
+                        result = r;
+                        found_unit = true;
+                        break;
+                } else if (!IN_SET(r, -ENOENT, -ENOTDIR, -EACCES))
                         return r;
         }
 
-        if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
+        if (!found_unit && template) {
 
                 /* Unit file doesn't exist, however instance
                  * enablement was requested.  We will check if it is
                  * possible to load template unit file. */
 
-                _cleanup_free_ char *template = NULL;
+                STRV_FOREACH(p, paths->search_path) {
+                        _cleanup_free_ char *path = NULL;
 
-                r = unit_name_template(info->name, &template);
+                        path = strjoin(*p, "/", template);
+                        if (!path)
+                                return -ENOMEM;
+
+                        r = unit_file_load_or_readlink(c, info, path, paths->root_dir, flags);
+                        if (r >= 0) {
+                                info->path = TAKE_PTR(path);
+                                result = r;
+                                found_unit = true;
+                                break;
+                        } else if (!IN_SET(r, -ENOENT, -ENOTDIR, -EACCES))
+                                return r;
+                }
+        }
+
+        if (!found_unit)
+                return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
+                                       "Cannot find unit %s%s%s.",
+                                       info->name, template ? " or " : "", strempty(template));
+
+        if (info->type == UNIT_FILE_TYPE_MASKED)
+                return result;
+
+        /* Search for drop-in directories */
+
+        dropin_dir_name = strjoina(info->name, ".d");
+        STRV_FOREACH(p, paths->search_path) {
+                char *path;
+
+                path = path_join(*p, dropin_dir_name);
+                if (!path)
+                        return -ENOMEM;
+
+                r = strv_consume(&dirs, path);
                 if (r < 0)
                         return r;
+        }
 
-                STRV_FOREACH(p, paths->unit_path) {
-                        _cleanup_free_ char *path = NULL;
+        if (template) {
+                dropin_template_dir_name = strjoina(template, ".d");
+                STRV_FOREACH(p, paths->search_path) {
+                        char *path;
 
-                        path = strjoin(*p, "/", template, NULL);
+                        path = path_join(*p, dropin_template_dir_name);
                         if (!path)
                                 return -ENOMEM;
 
-                        r = unit_file_load(c, info, path, root_dir, allow_symlink, load, also);
-                        if (r >= 0) {
-                                info->path = path;
-                                path = NULL;
-                                return r;
-                        }
-                        if (r != -ENOENT && r != -ELOOP)
+                        r = strv_consume(&dirs, path);
+                        if (r < 0)
                                 return r;
                 }
         }
 
-        return -ENOENT;
+        /* Load drop-in conf files */
+
+        r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) dirs);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to get list of conf files: %m");
+
+        STRV_FOREACH(p, files) {
+                r = unit_file_load_or_readlink(c, info, *p, paths->root_dir, flags | SEARCH_DROPIN);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to load conf file %s: %m", *p);
+        }
+
+        return result;
 }
 
-static int unit_file_can_install(
-                const LookupPaths *paths,
+static int install_info_follow(
+                InstallContext *c,
+                UnitFileInstallInfo *i,
                 const char *root_dir,
-                const char *name,
-                bool allow_symlink,
-                bool *also) {
+                SearchFlags flags,
+                bool ignore_different_name) {
+
+        assert(c);
+        assert(i);
+
+        if (i->type != UNIT_FILE_TYPE_SYMLINK)
+                return -EINVAL;
+        if (!i->symlink_target)
+                return -EINVAL;
+
+        /* If the basename doesn't match, the caller should add a
+         * complete new entry for this. */
+
+        if (!ignore_different_name && !streq(basename(i->symlink_target), i->name))
+                return -EXDEV;
+
+        free_and_replace(i->path, i->symlink_target);
+        i->type = _UNIT_FILE_TYPE_INVALID;
+
+        return unit_file_load_or_readlink(c, i, i->path, root_dir, flags);
+}
+
+/**
+ * Search for the unit file. If the unit name is a symlink, follow the symlink to the
+ * target, maybe more than once. Propagate the instance name if present.
+ */
+static int install_info_traverse(
+                UnitFileScope scope,
+                InstallContext *c,
+                const LookupPaths *paths,
+                UnitFileInstallInfo *start,
+                SearchFlags flags,
+                UnitFileInstallInfo **ret) {
 
-        _cleanup_(install_context_done) InstallContext c = {};
         UnitFileInstallInfo *i;
+        unsigned k = 0;
         int r;
 
         assert(paths);
-        assert(name);
+        assert(start);
+        assert(c);
 
-        r = install_info_add_auto(&c, name);
+        r = unit_file_search(c, start, paths, flags);
         if (r < 0)
                 return r;
 
-        assert_se(i = ordered_hashmap_first(c.will_install));
+        i = start;
+        while (i->type == UNIT_FILE_TYPE_SYMLINK) {
+                /* Follow the symlink */
 
-        r = unit_file_search(&c, i, paths, root_dir, allow_symlink, true, also);
+                if (++k > UNIT_FILE_FOLLOW_SYMLINK_MAX)
+                        return -ELOOP;
 
-        if (r >= 0)
-                r =
-                        (int) strv_length(i->aliases) +
-                        (int) strv_length(i->wanted_by) +
-                        (int) strv_length(i->required_by);
+                if (!(flags & SEARCH_FOLLOW_CONFIG_SYMLINKS)) {
+                        r = path_is_config(paths, i->path, true);
+                        if (r < 0)
+                                return r;
+                        if (r > 0)
+                                return -ELOOP;
+                }
 
-        return r;
-}
+                r = install_info_follow(c, i, paths->root_dir, flags, false);
+                if (r == -EXDEV) {
+                        _cleanup_free_ char *buffer = NULL;
+                        const char *bn;
 
-static int create_symlink(
-                const char *old_path,
-                const char *new_path,
-                bool force,
-                UnitFileChange **changes,
-                unsigned *n_changes) {
+                        /* Target has a different name, create a new
+                         * install info object for that, and continue
+                         * with that. */
 
-        _cleanup_free_ char *dest = NULL;
-        int r;
+                        bn = basename(i->symlink_target);
 
-        assert(old_path);
-        assert(new_path);
+                        if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE) &&
+                            unit_name_is_valid(bn, UNIT_NAME_TEMPLATE)) {
 
-        mkdir_parents_label(new_path, 0755);
+                                _cleanup_free_ char *instance = NULL;
 
-        if (symlink(old_path, new_path) >= 0) {
-                unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
-                return 0;
+                                r = unit_name_to_instance(i->name, &instance);
+                                if (r < 0)
+                                        return r;
+
+                                r = unit_name_replace_instance(bn, instance, &buffer);
+                                if (r < 0)
+                                        return r;
+
+                                if (streq(buffer, i->name)) {
+
+                                        /* We filled in the instance, and the target stayed the same? If so, then let's
+                                         * honour the link as it is. */
+
+                                        r = install_info_follow(c, i, paths->root_dir, flags, true);
+                                        if (r < 0)
+                                                return r;
+
+                                        continue;
+                                }
+
+                                bn = buffer;
+                        }
+
+                        r = install_info_add(c, bn, NULL, false, &i);
+                        if (r < 0)
+                                return r;
+
+                        /* Try again, with the new target we found. */
+                        r = unit_file_search(c, i, paths, flags);
+                        if (r == -ENOENT)
+                                /* Translate error code to highlight this specific case */
+                                return -ENOLINK;
+                }
+
+                if (r < 0)
+                        return r;
         }
 
-        if (errno != EEXIST)
-                return -errno;
+        if (ret)
+                *ret = i;
+
+        return 0;
+}
+
+/**
+ * Call install_info_add() with name_or_path as the path (if name_or_path starts with "/")
+ * or the name (otherwise). root_dir is prepended to the path.
+ */
+static int install_info_add_auto(
+                InstallContext *c,
+                const LookupPaths *paths,
+                const char *name_or_path,
+                UnitFileInstallInfo **ret) {
+
+        assert(c);
+        assert(name_or_path);
+
+        if (path_is_absolute(name_or_path)) {
+                const char *pp;
+
+                pp = prefix_roota(paths->root_dir, name_or_path);
+
+                return install_info_add(c, NULL, pp, false, ret);
+        } else
+                return install_info_add(c, name_or_path, NULL, false, ret);
+}
+
+static int install_info_discover(
+                UnitFileScope scope,
+                InstallContext *c,
+                const LookupPaths *paths,
+                const char *name,
+                SearchFlags flags,
+                UnitFileInstallInfo **ret,
+                UnitFileChange **changes,
+                size_t *n_changes) {
+
+        UnitFileInstallInfo *i;
+        int r;
+
+        assert(c);
+        assert(paths);
+        assert(name);
+
+        r = install_info_add_auto(c, paths, name, &i);
+        if (r >= 0)
+                r = install_info_traverse(scope, c, paths, i, flags, ret);
 
-        r = readlink_and_make_absolute(new_path, &dest);
         if (r < 0)
-                return r;
+                unit_file_changes_add(changes, n_changes, r, name, NULL);
+        return r;
+}
 
-        if (path_equal(dest, old_path))
-                return 0;
+static int install_info_discover_and_check(
+                        UnitFileScope scope,
+                        InstallContext *c,
+                        const LookupPaths *paths,
+                        const char *name,
+                        SearchFlags flags,
+                        UnitFileInstallInfo **ret,
+                        UnitFileChange **changes,
+                        size_t *n_changes) {
 
-        if (!force)
-                return -EEXIST;
+        int r;
 
-        r = symlink_atomic(old_path, new_path);
+        r = install_info_discover(scope, c, paths, name, flags, ret, changes, n_changes);
         if (r < 0)
                 return r;
 
-        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL);
-        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
-
-        return 0;
+        return install_info_may_process(ret ? *ret : NULL, paths, changes, n_changes);
 }
 
 static int install_info_symlink_alias(
                 UnitFileInstallInfo *i,
+                const LookupPaths *paths,
                 const char *config_path,
                 bool force,
                 UnitFileChange **changes,
-                unsigned *n_changes) {
+                size_t *n_changes) {
 
         char **s;
         int r = 0, q;
 
         assert(i);
+        assert(paths);
         assert(config_path);
 
         STRV_FOREACH(s, i->aliases) {
@@ -1249,7 +1734,7 @@ static int install_info_symlink_alias(
                 if (!alias_path)
                         return -ENOMEM;
 
-                q = create_symlink(i->path, alias_path, force, changes, n_changes);
+                q = create_symlink(paths, i->path, alias_path, force, changes, n_changes);
                 if (r == 0)
                         r = q;
         }
@@ -1259,12 +1744,12 @@ static int install_info_symlink_alias(
 
 static int install_info_symlink_wants(
                 UnitFileInstallInfo *i,
+                const LookupPaths *paths,
                 const char *config_path,
                 char **list,
                 const char *suffix,
-                bool force,
                 UnitFileChange **changes,
-                unsigned *n_changes) {
+                size_t *n_changes) {
 
         _cleanup_free_ char *buf = NULL;
         const char *n;
@@ -1272,20 +1757,38 @@ static int install_info_symlink_wants(
         int r = 0, q;
 
         assert(i);
+        assert(paths);
         assert(config_path);
 
-        if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) {
+        if (strv_isempty(list))
+                return 0;
 
-                /* Don't install any symlink if there's no default
-                 * instance configured */
+        if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) {
+                UnitFileInstallInfo instance = {
+                        .type = _UNIT_FILE_TYPE_INVALID,
+                };
+                _cleanup_free_ char *path = NULL;
 
+                /* If this is a template, and we have no instance, don't do anything */
                 if (!i->default_instance)
-                        return 0;
+                        return 1;
 
                 r = unit_name_replace_instance(i->name, i->default_instance, &buf);
                 if (r < 0)
                         return r;
 
+                instance.name = buf;
+                r = unit_file_search(NULL, &instance, paths, SEARCH_FOLLOW_CONFIG_SYMLINKS);
+                if (r < 0)
+                        return r;
+
+                path = TAKE_PTR(instance.path);
+
+                if (instance.type == UNIT_FILE_TYPE_MASKED) {
+                        unit_file_changes_add(changes, n_changes, -ERFKILL, path, NULL);
+                        return -ERFKILL;
+                }
+
                 n = buf;
         } else
                 n = i->name;
@@ -1302,11 +1805,11 @@ static int install_info_symlink_wants(
                         continue;
                 }
 
-                path = strjoin(config_path, "/", dst, suffix, n, NULL);
+                path = strjoin(config_path, "/", dst, suffix, n);
                 if (!path)
                         return -ENOMEM;
 
-                q = create_symlink(i->path, path, force, changes, n_changes);
+                q = create_symlink(paths, i->path, path, true, changes, n_changes);
                 if (r == 0)
                         r = q;
         }
@@ -1318,10 +1821,9 @@ static int install_info_symlink_link(
                 UnitFileInstallInfo *i,
                 const LookupPaths *paths,
                 const char *config_path,
-                const char *root_dir,
                 bool force,
                 UnitFileChange **changes,
-                unsigned *n_changes) {
+                size_t *n_changes) {
 
         _cleanup_free_ char *path = NULL;
         int r;
@@ -1331,25 +1833,26 @@ static int install_info_symlink_link(
         assert(config_path);
         assert(i->path);
 
-        r = in_search_path(i->path, paths->unit_path);
-        if (r != 0)
+        r = in_search_path(paths, i->path);
+        if (r < 0)
                 return r;
+        if (r > 0)
+                return 0;
 
-        path = strjoin(config_path, "/", i->name, NULL);
+        path = strjoin(config_path, "/", i->name);
         if (!path)
                 return -ENOMEM;
 
-        return create_symlink(i->path, path, force, changes, n_changes);
+        return create_symlink(paths, i->path, path, force, changes, n_changes);
 }
 
 static int install_info_apply(
                 UnitFileInstallInfo *i,
                 const LookupPaths *paths,
                 const char *config_path,
-                const char *root_dir,
                 bool force,
                 UnitFileChange **changes,
-                unsigned *n_changes) {
+                size_t *n_changes) {
 
         int r, q;
 
@@ -1357,385 +1860,765 @@ static int install_info_apply(
         assert(paths);
         assert(config_path);
 
-        r = install_info_symlink_alias(i, config_path, force, changes, n_changes);
+        if (i->type != UNIT_FILE_TYPE_REGULAR)
+                return 0;
+
+        r = install_info_symlink_alias(i, paths, config_path, force, changes, n_changes);
 
-        q = install_info_symlink_wants(i, config_path, i->wanted_by, ".wants/", force, changes, n_changes);
+        q = install_info_symlink_wants(i, paths, config_path, i->wanted_by, ".wants/", changes, n_changes);
         if (r == 0)
                 r = q;
 
-        q = install_info_symlink_wants(i, config_path, i->required_by, ".requires/", force, changes, n_changes);
+        q = install_info_symlink_wants(i, paths, config_path, i->required_by, ".requires/", changes, n_changes);
         if (r == 0)
                 r = q;
 
-        q = install_info_symlink_link(i, paths, config_path, root_dir, force, changes, n_changes);
-        if (r == 0)
-                r = q;
+        q = install_info_symlink_link(i, paths, config_path, force, changes, n_changes);
+        /* Do not count links to the unit file towards the "carries_install_info" count */
+        if (r == 0 && q < 0)
+                r = q;
+
+        return r;
+}
+
+static int install_context_apply(
+                UnitFileScope scope,
+                InstallContext *c,
+                const LookupPaths *paths,
+                const char *config_path,
+                bool force,
+                SearchFlags flags,
+                UnitFileChange **changes,
+                size_t *n_changes) {
+
+        UnitFileInstallInfo *i;
+        int r;
+
+        assert(c);
+        assert(paths);
+        assert(config_path);
+
+        if (ordered_hashmap_isempty(c->will_process))
+                return 0;
+
+        r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = 0;
+        while ((i = ordered_hashmap_first(c->will_process))) {
+                int q;
+
+                q = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name);
+                if (q < 0)
+                        return q;
+
+                q = install_info_traverse(scope, c, paths, i, flags, NULL);
+                if (q < 0) {
+                        unit_file_changes_add(changes, n_changes, r, i->name, NULL);
+                        return q;
+                }
+
+                /* We can attempt to process a masked unit when a different unit
+                 * that we were processing specifies it in Also=. */
+                if (i->type == UNIT_FILE_TYPE_MASKED) {
+                        unit_file_changes_add(changes, n_changes, UNIT_FILE_IS_MASKED, i->path, NULL);
+                        if (r >= 0)
+                                /* Assume that something *could* have been enabled here,
+                                 * avoid "empty [Install] section" warning. */
+                                r += 1;
+                        continue;
+                }
+
+                if (i->type != UNIT_FILE_TYPE_REGULAR)
+                        continue;
+
+                q = install_info_apply(i, paths, config_path, force, changes, n_changes);
+                if (r >= 0) {
+                        if (q < 0)
+                                r = q;
+                        else
+                                r += q;
+                }
+        }
+
+        return r;
+}
+
+static int install_context_mark_for_removal(
+                UnitFileScope scope,
+                InstallContext *c,
+                const LookupPaths *paths,
+                Set **remove_symlinks_to,
+                const char *config_path,
+                UnitFileChange **changes,
+                size_t *n_changes) {
+
+        UnitFileInstallInfo *i;
+        int r;
+
+        assert(c);
+        assert(paths);
+        assert(config_path);
+
+        /* Marks all items for removal */
+
+        if (ordered_hashmap_isempty(c->will_process))
+                return 0;
+
+        r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops);
+        if (r < 0)
+                return r;
+
+        while ((i = ordered_hashmap_first(c->will_process))) {
+
+                r = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name);
+                if (r < 0)
+                        return r;
+
+                r = install_info_traverse(scope, c, paths, i, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL);
+                if (r == -ENOLINK) {
+                        log_debug_errno(r, "Name %s leads to a dangling symlink, removing name.", i->name);
+                        unit_file_changes_add(changes, n_changes, UNIT_FILE_IS_DANGLING, i->path ?: i->name, NULL);
+                } else if (r == -ENOENT) {
+
+                        if (i->auxiliary)  /* some unit specified in Also= or similar is missing */
+                                log_debug_errno(r, "Auxiliary unit of %s not found, removing name.", i->name);
+                        else {
+                                log_debug_errno(r, "Unit %s not found, removing name.", i->name);
+                                unit_file_changes_add(changes, n_changes, r, i->path ?: i->name, NULL);
+                        }
+
+                } else if (r < 0) {
+                        log_debug_errno(r, "Failed to find unit %s, removing name: %m", i->name);
+                        unit_file_changes_add(changes, n_changes, r, i->path ?: i->name, NULL);
+                } else if (i->type == UNIT_FILE_TYPE_MASKED) {
+                        log_debug("Unit file %s is masked, ignoring.", i->name);
+                        unit_file_changes_add(changes, n_changes, UNIT_FILE_IS_MASKED, i->path ?: i->name, NULL);
+                        continue;
+                } else if (i->type != UNIT_FILE_TYPE_REGULAR) {
+                        log_debug("Unit %s has type %s, ignoring.", i->name, unit_file_type_to_string(i->type) ?: "invalid");
+                        continue;
+                }
+
+                r = mark_symlink_for_removal(remove_symlinks_to, i->name);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int unit_file_mask(
+                UnitFileScope scope,
+                UnitFileFlags flags,
+                const char *root_dir,
+                char **files,
+                UnitFileChange **changes,
+                size_t *n_changes) {
+
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
+        const char *config_path;
+        char **i;
+        int r;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
+        if (r < 0)
+                return r;
+
+        config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config;
+        if (!config_path)
+                return -ENXIO;
+
+        STRV_FOREACH(i, files) {
+                _cleanup_free_ char *path = NULL;
+                int q;
+
+                if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) {
+                        if (r == 0)
+                                r = -EINVAL;
+                        continue;
+                }
+
+                path = path_make_absolute(*i, config_path);
+                if (!path)
+                        return -ENOMEM;
+
+                q = create_symlink(&paths, "/dev/null", path, !!(flags & UNIT_FILE_FORCE), changes, n_changes);
+                if (q < 0 && r >= 0)
+                        r = q;
+        }
+
+        return r;
+}
+
+int unit_file_unmask(
+                UnitFileScope scope,
+                UnitFileFlags flags,
+                const char *root_dir,
+                char **files,
+                UnitFileChange **changes,
+                size_t *n_changes) {
+
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
+        _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
+        _cleanup_strv_free_ char **todo = NULL;
+        size_t n_todo = 0, n_allocated = 0;
+        const char *config_path;
+        char **i;
+        bool dry_run;
+        int r, q;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
+        if (r < 0)
+                return r;
+
+        config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config;
+        if (!config_path)
+                return -ENXIO;
+
+        dry_run = !!(flags & UNIT_FILE_DRY_RUN);
+
+        STRV_FOREACH(i, files) {
+                _cleanup_free_ char *path = NULL;
+
+                if (!unit_name_is_valid(*i, UNIT_NAME_ANY))
+                        return -EINVAL;
+
+                path = path_make_absolute(*i, config_path);
+                if (!path)
+                        return -ENOMEM;
+
+                r = null_or_empty_path(path);
+                if (r == -ENOENT)
+                        continue;
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        continue;
+
+                if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2))
+                        return -ENOMEM;
+
+                todo[n_todo] = strdup(*i);
+                if (!todo[n_todo])
+                        return -ENOMEM;
+
+                n_todo++;
+        }
+
+        strv_uniq(todo);
+
+        r = 0;
+        STRV_FOREACH(i, todo) {
+                _cleanup_free_ char *path = NULL;
+                const char *rp;
+
+                path = path_make_absolute(*i, config_path);
+                if (!path)
+                        return -ENOMEM;
+
+                if (!dry_run && unlink(path) < 0) {
+                        if (errno != ENOENT) {
+                                if (r >= 0)
+                                        r = -errno;
+                                unit_file_changes_add(changes, n_changes, -errno, path, NULL);
+                        }
+
+                        continue;
+                }
+
+                unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
+
+                rp = skip_root(&paths, path);
+                q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: path);
+                if (q < 0)
+                        return q;
+        }
+
+        q = remove_marked_symlinks(remove_symlinks_to, config_path, &paths, dry_run, changes, n_changes);
+        if (r >= 0)
+                r = q;
+
+        return r;
+}
+
+int unit_file_link(
+                UnitFileScope scope,
+                UnitFileFlags flags,
+                const char *root_dir,
+                char **files,
+                UnitFileChange **changes,
+                size_t *n_changes) {
+
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
+        _cleanup_strv_free_ char **todo = NULL;
+        size_t n_todo = 0, n_allocated = 0;
+        const char *config_path;
+        char **i;
+        int r, q;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
+        if (r < 0)
+                return r;
+
+        config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config;
+        if (!config_path)
+                return -ENXIO;
+
+        STRV_FOREACH(i, files) {
+                _cleanup_free_ char *full = NULL;
+                struct stat st;
+                char *fn;
+
+                if (!path_is_absolute(*i))
+                        return -EINVAL;
+
+                fn = basename(*i);
+                if (!unit_name_is_valid(fn, UNIT_NAME_ANY))
+                        return -EINVAL;
+
+                full = prefix_root(paths.root_dir, *i);
+                if (!full)
+                        return -ENOMEM;
+
+                if (lstat(full, &st) < 0)
+                        return -errno;
+                r = stat_verify_regular(&st);
+                if (r < 0)
+                        return r;
+
+                q = in_search_path(&paths, *i);
+                if (q < 0)
+                        return q;
+                if (q > 0)
+                        continue;
+
+                if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2))
+                        return -ENOMEM;
+
+                todo[n_todo] = strdup(*i);
+                if (!todo[n_todo])
+                        return -ENOMEM;
+
+                n_todo++;
+        }
+
+        strv_uniq(todo);
+
+        r = 0;
+        STRV_FOREACH(i, todo) {
+                _cleanup_free_ char *new_path = NULL;
+
+                new_path = path_make_absolute(basename(*i), config_path);
+                if (!new_path)
+                        return -ENOMEM;
+
+                q = create_symlink(&paths, *i, new_path, !!(flags & UNIT_FILE_FORCE), changes, n_changes);
+                if (q < 0 && r >= 0)
+                        r = q;
+        }
+
+        return r;
+}
+
+static int path_shall_revert(const LookupPaths *paths, const char *path) {
+        int r;
+
+        assert(paths);
+        assert(path);
+
+        /* Checks whether the path is one where the drop-in directories shall be removed. */
+
+        r = path_is_config(paths, path, true);
+        if (r != 0)
+                return r;
+
+        r = path_is_control(paths, path);
+        if (r != 0)
+                return r;
 
-        return r;
+        return path_is_transient(paths, path);
 }
 
-static int install_context_apply(
-                InstallContext *c,
-                const LookupPaths *paths,
-                const char *config_path,
+int unit_file_revert(
+                UnitFileScope scope,
                 const char *root_dir,
-                bool force,
+                char **files,
                 UnitFileChange **changes,
-                unsigned *n_changes) {
+                size_t *n_changes) {
 
-        UnitFileInstallInfo *i;
+        _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
+        _cleanup_strv_free_ char **todo = NULL;
+        size_t n_todo = 0, n_allocated = 0;
+        char **i;
         int r, q;
 
-        assert(c);
-        assert(paths);
-        assert(config_path);
+        /* Puts a unit file back into vendor state. This means:
+         *
+         * a) we remove all drop-in snippets added by the user ("config"), add to transient units ("transient"), and
+         *    added via "systemctl set-property" ("control"), but not if the drop-in is generated ("generated").
+         *
+         * c) if there's a vendor unit file (i.e. one in /usr) we remove any configured overriding unit files (i.e. in
+         *    "config", but not in "transient" or "control" or even "generated").
+         *
+         * We remove all that in both the runtime and the persistent directories, if that applies.
+         */
+
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
+        if (r < 0)
+                return r;
 
-        if (!ordered_hashmap_isempty(c->will_install)) {
-                r = ordered_hashmap_ensure_allocated(&c->have_installed, &string_hash_ops);
-                if (r < 0)
-                        return r;
+        STRV_FOREACH(i, files) {
+                bool has_vendor = false;
+                char **p;
 
-                r = ordered_hashmap_reserve(c->have_installed, ordered_hashmap_size(c->will_install));
-                if (r < 0)
-                        return r;
-        }
+                if (!unit_name_is_valid(*i, UNIT_NAME_ANY))
+                        return -EINVAL;
 
-        r = 0;
-        while ((i = ordered_hashmap_first(c->will_install))) {
-                assert_se(ordered_hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);
+                STRV_FOREACH(p, paths.search_path) {
+                        _cleanup_free_ char *path = NULL, *dropin = NULL;
+                        struct stat st;
 
-                q = unit_file_search(c, i, paths, root_dir, false, true, NULL);
-                if (q < 0) {
-                        if (r >= 0)
-                                r = q;
+                        path = path_make_absolute(*i, *p);
+                        if (!path)
+                                return -ENOMEM;
 
-                        return r;
-                } else if (r >= 0)
-                        r += q;
+                        r = lstat(path, &st);
+                        if (r < 0) {
+                                if (errno != ENOENT)
+                                        return -errno;
+                        } else if (S_ISREG(st.st_mode)) {
+                                /* Check if there's a vendor version */
+                                r = path_is_vendor(&paths, path);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0)
+                                        has_vendor = true;
+                        }
 
-                q = install_info_apply(i, paths, config_path, root_dir, force, changes, n_changes);
-                if (r >= 0 && q < 0)
-                        r = q;
-        }
+                        dropin = strappend(path, ".d");
+                        if (!dropin)
+                                return -ENOMEM;
 
-        return r;
-}
+                        r = lstat(dropin, &st);
+                        if (r < 0) {
+                                if (errno != ENOENT)
+                                        return -errno;
+                        } else if (S_ISDIR(st.st_mode)) {
+                                /* Remove the drop-ins */
+                                r = path_shall_revert(&paths, dropin);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0) {
+                                        if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2))
+                                                return -ENOMEM;
 
-static int install_context_mark_for_removal(
-                InstallContext *c,
-                const LookupPaths *paths,
-                Set **remove_symlinks_to,
-                const char *config_path,
-                const char *root_dir) {
+                                        todo[n_todo++] = TAKE_PTR(dropin);
+                                }
+                        }
+                }
 
-        UnitFileInstallInfo *i;
-        int r, q;
+                if (!has_vendor)
+                        continue;
 
-        assert(c);
-        assert(paths);
-        assert(config_path);
+                /* OK, there's a vendor version, hence drop all configuration versions */
+                STRV_FOREACH(p, paths.search_path) {
+                        _cleanup_free_ char *path = NULL;
+                        struct stat st;
 
-        /* Marks all items for removal */
+                        path = path_make_absolute(*i, *p);
+                        if (!path)
+                                return -ENOMEM;
 
-        if (!ordered_hashmap_isempty(c->will_install)) {
-                r = ordered_hashmap_ensure_allocated(&c->have_installed, &string_hash_ops);
-                if (r < 0)
-                        return r;
+                        r = lstat(path, &st);
+                        if (r < 0) {
+                                if (errno != ENOENT)
+                                        return -errno;
+                        } else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
+                                r = path_is_config(&paths, path, true);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0) {
+                                        if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2))
+                                                return -ENOMEM;
 
-                r = ordered_hashmap_reserve(c->have_installed, ordered_hashmap_size(c->will_install));
-                if (r < 0)
-                        return r;
+                                        todo[n_todo++] = TAKE_PTR(path);
+                                }
+                        }
+                }
         }
 
+        strv_uniq(todo);
+
         r = 0;
-        while ((i = ordered_hashmap_first(c->will_install))) {
-                assert_se(ordered_hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);
+        STRV_FOREACH(i, todo) {
+                _cleanup_strv_free_ char **fs = NULL;
+                const char *rp;
+                char **j;
 
-                q = unit_file_search(c, i, paths, root_dir, false, true, NULL);
-                if (q == -ENOENT) {
-                        /* do nothing */
-                } else if (q < 0) {
-                        if (r >= 0)
-                                r = q;
+                (void) get_files_in_directory(*i, &fs);
 
-                        return r;
-                } else if (r >= 0)
-                        r += q;
-
-                if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE)) {
-                        char *unit_file;
-
-                        if (i->path) {
-                                unit_file = basename(i->path);
-
-                                if (unit_name_is_valid(unit_file, UNIT_NAME_INSTANCE))
-                                        /* unit file named as instance exists, thus all symlinks
-                                         * pointing to it will be removed */
-                                        q = mark_symlink_for_removal(remove_symlinks_to, i->name);
-                                else
-                                        /* does not exist, thus we will mark for removal symlinks
-                                         * to template unit file */
-                                        q = mark_symlink_for_removal(remove_symlinks_to, unit_file);
-                        } else {
-                                /* If i->path is not set, it means that we didn't actually find
-                                 * the unit file. But we can still remove symlinks to the
-                                 * nonexistent template. */
-                                r = unit_name_template(i->name, &unit_file);
-                                if (r < 0)
-                                        return r;
+                q = rm_rf(*i, REMOVE_ROOT|REMOVE_PHYSICAL);
+                if (q < 0 && q != -ENOENT && r >= 0) {
+                        r = q;
+                        continue;
+                }
 
-                                q = mark_symlink_for_removal(remove_symlinks_to, unit_file);
-                                free(unit_file);
-                        }
-                } else
-                        q = mark_symlink_for_removal(remove_symlinks_to, i->name);
+                STRV_FOREACH(j, fs) {
+                        _cleanup_free_ char *t = NULL;
 
-                if (r >= 0 && q < 0)
-                        r = q;
+                        t = strjoin(*i, "/", *j);
+                        if (!t)
+                                return -ENOMEM;
+
+                        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, t, NULL);
+                }
+
+                unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, *i, NULL);
+
+                rp = skip_root(&paths, *i);
+                q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: *i);
+                if (q < 0)
+                        return q;
         }
 
+        q = remove_marked_symlinks(remove_symlinks_to, paths.runtime_config, &paths, false, changes, n_changes);
+        if (r >= 0)
+                r = q;
+
+        q = remove_marked_symlinks(remove_symlinks_to, paths.persistent_config, &paths, false, changes, n_changes);
+        if (r >= 0)
+                r = q;
+
         return r;
 }
 
 int unit_file_add_dependency(
                 UnitFileScope scope,
-                bool runtime,
+                UnitFileFlags flags,
                 const char *root_dir,
                 char **files,
-                char *target,
+                const char *target,
                 UnitDependency dep,
-                bool force,
                 UnitFileChange **changes,
-                unsigned *n_changes) {
+                size_t *n_changes) {
 
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
         _cleanup_(install_context_done) InstallContext c = {};
-        _cleanup_free_ char *config_path = NULL;
-        char **i;
+        UnitFileInstallInfo *i, *target_info;
+        const char *config_path;
+        char **f;
         int r;
-        UnitFileInstallInfo *info;
 
         assert(scope >= 0);
         assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(target);
 
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
-        if (r < 0)
-                return r;
+        if (!IN_SET(dep, UNIT_WANTS, UNIT_REQUIRES))
+                return -EINVAL;
+
+        if (!unit_name_is_valid(target, UNIT_NAME_ANY))
+                return -EINVAL;
 
-        r = get_config_path(scope, runtime, root_dir, &config_path);
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(i, files) {
-                UnitFileState state;
-
-                state = unit_file_get_state(scope, root_dir, *i);
-                if (state < 0)
-                        return log_error_errno(state, "Failed to get unit file state for %s: %m", *i);
+        config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config;
+        if (!config_path)
+                return -ENXIO;
 
-                if (state == UNIT_FILE_MASKED || state == UNIT_FILE_MASKED_RUNTIME) {
-                        log_error("Failed to enable unit: Unit %s is masked", *i);
-                        return -EOPNOTSUPP;
-                }
+        r = install_info_discover_and_check(scope, &c, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS,
+                                            &target_info, changes, n_changes);
+        if (r < 0)
+                return r;
 
-                r = install_info_add_auto(&c, *i);
-                if (r < 0)
-                        return r;
-        }
+        assert(target_info->type == UNIT_FILE_TYPE_REGULAR);
 
-        if (!ordered_hashmap_isempty(c.will_install)) {
-                r = ordered_hashmap_ensure_allocated(&c.have_installed, &string_hash_ops);
-                if (r < 0)
-                        return r;
+        STRV_FOREACH(f, files) {
+                char ***l;
 
-                r = ordered_hashmap_reserve(c.have_installed, ordered_hashmap_size(c.will_install));
+                r = install_info_discover_and_check(scope, &c, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS,
+                                                    &i, changes, n_changes);
                 if (r < 0)
                         return r;
-        }
 
-        while ((info = ordered_hashmap_first(c.will_install))) {
-                assert_se(ordered_hashmap_move_one(c.have_installed, c.will_install, info->name) == 0);
+                assert(i->type == UNIT_FILE_TYPE_REGULAR);
 
-                r = unit_file_search(&c, info, &paths, root_dir, false, false, NULL);
-                if (r < 0)
-                        return r;
+                /* We didn't actually load anything from the unit
+                 * file, but instead just add in our new symlink to
+                 * create. */
 
                 if (dep == UNIT_WANTS)
-                        r = strv_extend(&info->wanted_by, target);
-                else if (dep == UNIT_REQUIRES)
-                        r = strv_extend(&info->required_by, target);
+                        l = &i->wanted_by;
                 else
-                        r = -EINVAL;
-
-                if (r < 0)
-                        return r;
+                        l = &i->required_by;
 
-                r = install_info_apply(info, &paths, config_path, root_dir, force, changes, n_changes);
-                if (r < 0)
-                        return r;
+                strv_free(*l);
+                *l = strv_new(target_info->name);
+                if (!*l)
+                        return -ENOMEM;
         }
 
-        return 0;
+        return install_context_apply(scope, &c, &paths, config_path, !!(flags & UNIT_FILE_FORCE), SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes);
 }
 
 int unit_file_enable(
                 UnitFileScope scope,
-                bool runtime,
+                UnitFileFlags flags,
                 const char *root_dir,
                 char **files,
-                bool force,
                 UnitFileChange **changes,
-                unsigned *n_changes) {
+                size_t *n_changes) {
 
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
         _cleanup_(install_context_done) InstallContext c = {};
-        char **i;
-        _cleanup_free_ char *config_path = NULL;
+        const char *config_path;
+        UnitFileInstallInfo *i;
+        char **f;
         int r;
 
         assert(scope >= 0);
         assert(scope < _UNIT_FILE_SCOPE_MAX);
 
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
-        if (r < 0)
-                return r;
-
-        r = get_config_path(scope, runtime, root_dir, &config_path);
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(i, files) {
-                UnitFileState state;
-
-                /* We only want to know if this unit is masked, so we ignore
-                 * errors from unit_file_get_state, deferring other checks.
-                 * This allows templated units to be enabled on the fly. */
-                state = unit_file_get_state(scope, root_dir, *i);
-                if (state == UNIT_FILE_MASKED || state == UNIT_FILE_MASKED_RUNTIME) {
-                        log_error("Failed to enable unit: Unit %s is masked", *i);
-                        return -EOPNOTSUPP;
-                }
+        config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config;
+        if (!config_path)
+                return -ENXIO;
 
-                r = install_info_add_auto(&c, *i);
+        STRV_FOREACH(f, files) {
+                r = install_info_discover_and_check(scope, &c, &paths, *f, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
+                                                    &i, changes, n_changes);
                 if (r < 0)
                         return r;
+
+                assert(i->type == UNIT_FILE_TYPE_REGULAR);
         }
 
         /* This will return the number of symlink rules that were
-        supposed to be created, not the ones actually created. This is
-        useful to determine whether the passed files had any
-        installation data at all. */
+           supposed to be created, not the ones actually created. This
+           is useful to determine whether the passed files had any
+           installation data at all. */
 
-        return install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes);
+        return install_context_apply(scope, &c, &paths, config_path, !!(flags & UNIT_FILE_FORCE), SEARCH_LOAD, changes, n_changes);
 }
 
 int unit_file_disable(
                 UnitFileScope scope,
-                bool runtime,
+                UnitFileFlags flags,
                 const char *root_dir,
                 char **files,
                 UnitFileChange **changes,
-                unsigned *n_changes) {
+                size_t *n_changes) {
 
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
         _cleanup_(install_context_done) InstallContext c = {};
-        char **i;
-        _cleanup_free_ char *config_path = NULL;
         _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
-        int r, q;
+        const char *config_path;
+        char **i;
+        int r;
 
         assert(scope >= 0);
         assert(scope < _UNIT_FILE_SCOPE_MAX);
 
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
         if (r < 0)
                 return r;
 
-        r = get_config_path(scope, runtime, root_dir, &config_path);
-        if (r < 0)
-                return r;
+        config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config;
+        if (!config_path)
+                return -ENXIO;
 
         STRV_FOREACH(i, files) {
-                r = install_info_add_auto(&c, *i);
+                if (!unit_name_is_valid(*i, UNIT_NAME_ANY))
+                        return -EINVAL;
+
+                r = install_info_add(&c, *i, NULL, false, NULL);
                 if (r < 0)
                         return r;
         }
 
-        r = install_context_mark_for_removal(&c, &paths, &remove_symlinks_to, config_path, root_dir);
-
-        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
-        if (r >= 0)
-                r = q;
+        r = install_context_mark_for_removal(scope, &c, &paths, &remove_symlinks_to, config_path, changes, n_changes);
+        if (r < 0)
+                return r;
 
-        return r;
+        return remove_marked_symlinks(remove_symlinks_to, config_path, &paths, !!(flags & UNIT_FILE_DRY_RUN), changes, n_changes);
 }
 
 int unit_file_reenable(
                 UnitFileScope scope,
-                bool runtime,
+                UnitFileFlags flags,
                 const char *root_dir,
                 char **files,
-                bool force,
                 UnitFileChange **changes,
-                unsigned *n_changes) {
+                size_t *n_changes) {
+
+        char **n;
         int r;
+        size_t l, i;
 
-        r = unit_file_disable(scope, runtime, root_dir, files,
-                              changes, n_changes);
+        /* First, we invoke the disable command with only the basename... */
+        l = strv_length(files);
+        n = newa(char*, l+1);
+        for (i = 0; i < l; i++)
+                n[i] = basename(files[i]);
+        n[i] = NULL;
+
+        r = unit_file_disable(scope, flags, root_dir, n, changes, n_changes);
         if (r < 0)
                 return r;
 
-        return unit_file_enable(scope, runtime, root_dir, files, force,
-                                changes, n_changes);
+        /* But the enable command with the full name */
+        return unit_file_enable(scope, flags, root_dir, files, changes, n_changes);
 }
 
 int unit_file_set_default(
                 UnitFileScope scope,
+                UnitFileFlags flags,
                 const char *root_dir,
-                const char *file,
-                bool force,
+                const char *name,
                 UnitFileChange **changes,
-                unsigned *n_changes) {
+                size_t *n_changes) {
 
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
         _cleanup_(install_context_done) InstallContext c = {};
-        _cleanup_free_ char *config_path = NULL;
-        char *path;
+        UnitFileInstallInfo *i;
+        const char *new_path;
         int r;
-        UnitFileInstallInfo *i = NULL;
 
         assert(scope >= 0);
         assert(scope < _UNIT_FILE_SCOPE_MAX);
-        assert(file);
+        assert(name);
 
-        if (unit_name_to_type(file) != UNIT_TARGET)
+        if (unit_name_to_type(name) != UNIT_TARGET) /* this also validates the name */
+                return -EINVAL;
+        if (streq(name, SPECIAL_DEFAULT_TARGET))
                 return -EINVAL;
 
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
-        if (r < 0)
-                return r;
-
-        r = get_config_path(scope, false, root_dir, &config_path);
-        if (r < 0)
-                return r;
-
-        r = install_info_add_auto(&c, file);
-        if (r < 0)
-                return r;
-
-        assert_se(i = ordered_hashmap_first(c.will_install));
-
-        r = unit_file_search(&c, i, &paths, root_dir, false, true, NULL);
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
         if (r < 0)
                 return r;
 
-        path = strjoina(config_path, "/" SPECIAL_DEFAULT_TARGET);
-
-        r = create_symlink(i->path, path, force, changes, n_changes);
+        r = install_info_discover_and_check(scope, &c, &paths, name, 0, &i, changes, n_changes);
         if (r < 0)
                 return r;
 
-        return 0;
+        new_path = strjoina(paths.persistent_config, "/" SPECIAL_DEFAULT_TARGET);
+        return create_symlink(&paths, i->path, new_path, !!(flags & UNIT_FILE_FORCE), changes, n_changes);
 }
 
 int unit_file_get_default(
@@ -1743,177 +2626,244 @@ int unit_file_get_default(
                 const char *root_dir,
                 char **name) {
 
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
-        char **p;
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
+        _cleanup_(install_context_done) InstallContext c = {};
+        UnitFileInstallInfo *i;
+        char *n;
         int r;
 
         assert(scope >= 0);
         assert(scope < _UNIT_FILE_SCOPE_MAX);
         assert(name);
 
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(p, paths.unit_path) {
-                _cleanup_free_ char *path = NULL, *tmp = NULL;
-                char *n;
-
-                path = path_join(root_dir, *p, SPECIAL_DEFAULT_TARGET);
-                if (!path)
-                        return -ENOMEM;
-
-                r = readlink_malloc(path, &tmp);
-                if (r == -ENOENT)
-                        continue;
-                else if (r == -EINVAL)
-                        /* not a symlink */
-                        n = strdup(SPECIAL_DEFAULT_TARGET);
-                else if (r < 0)
-                        return r;
-                else
-                        n = strdup(basename(tmp));
-
-                if (!n)
-                        return -ENOMEM;
+        r = install_info_discover(scope, &c, &paths, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS,
+                                  &i, NULL, NULL);
+        if (r < 0)
+                return r;
+        r = install_info_may_process(i, &paths, NULL, 0);
+        if (r < 0)
+                return r;
 
-                *name = n;
-                return 0;
-        }
+        n = strdup(i->name);
+        if (!n)
+                return -ENOMEM;
 
-        return -ENOENT;
+        *name = n;
+        return 0;
 }
 
-UnitFileState unit_file_lookup_state(
+int unit_file_lookup_state(
                 UnitFileScope scope,
-                const char *root_dir,
                 const LookupPaths *paths,
-                const char *name) {
+                const char *name,
+                UnitFileState *ret) {
 
-        UnitFileState state = _UNIT_FILE_STATE_INVALID;
-        char **i;
-        _cleanup_free_ char *path = NULL;
-        int r = 0;
+        _cleanup_(install_context_done) InstallContext c = {};
+        UnitFileInstallInfo *i;
+        UnitFileState state;
+        int r;
 
         assert(paths);
+        assert(name);
 
         if (!unit_name_is_valid(name, UNIT_NAME_ANY))
                 return -EINVAL;
 
-        STRV_FOREACH(i, paths->unit_path) {
-                struct stat st;
-                char *partial;
-                bool also = false;
+        r = install_info_discover(scope, &c, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
+                                  &i, NULL, NULL);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to discover unit %s: %m", name);
 
-                free(path);
-                path = path_join(root_dir, *i, name);
-                if (!path)
-                        return -ENOMEM;
+        assert(IN_SET(i->type, UNIT_FILE_TYPE_REGULAR, UNIT_FILE_TYPE_MASKED));
+        log_debug("Found unit %s at %s (%s)", name, strna(i->path),
+                  i->type == UNIT_FILE_TYPE_REGULAR ? "regular file" : "mask");
 
-                if (root_dir)
-                        partial = path + strlen(root_dir);
-                else
-                        partial = path;
-
-                /*
-                 * Search for a unit file in our default paths, to
-                 * be sure, that there are no broken symlinks.
-                 */
-                if (lstat(path, &st) < 0) {
-                        r = -errno;
-                        if (errno != ENOENT)
-                                return r;
+        /* Shortcut things, if the caller just wants to know if this unit exists. */
+        if (!ret)
+                return 0;
 
-                        if (!unit_name_is_valid(name, UNIT_NAME_INSTANCE))
-                                continue;
-                } else {
-                        if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
-                                return -ENOENT;
+        switch (i->type) {
 
-                        r = null_or_empty_path(path);
-                        if (r < 0 && r != -ENOENT)
-                                return r;
-                        else if (r > 0) {
-                                state = path_startswith(*i, "/run") ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
-                                return state;
-                        }
+        case UNIT_FILE_TYPE_MASKED:
+                r = path_is_runtime(paths, i->path, true);
+                if (r < 0)
+                        return r;
+
+                state = r > 0 ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
+                break;
+
+        case UNIT_FILE_TYPE_REGULAR:
+                r = path_is_generator(paths, i->path);
+                if (r < 0)
+                        return r;
+                if (r > 0) {
+                        state = UNIT_FILE_GENERATED;
+                        break;
+                }
+
+                r = path_is_transient(paths, i->path);
+                if (r < 0)
+                        return r;
+                if (r > 0) {
+                        state = UNIT_FILE_TRANSIENT;
+                        break;
                 }
 
-                r = find_symlinks_in_scope(scope, root_dir, name, &state);
+                /* Check if any of the Alias= symlinks have been created.
+                 * We ignore other aliases, and only check those that would
+                 * be created by systemctl enable for this unit. */
+                r = find_symlinks_in_scope(scope, paths, i, true, &state);
                 if (r < 0)
                         return r;
-                else if (r > 0)
-                        return state;
+                if (r > 0)
+                        break;
 
-                r = unit_file_can_install(paths, root_dir, partial, true, &also);
-                if (r < 0 && errno != ENOENT)
+                /* Check if the file is known under other names. If it is,
+                 * it might be in use. Report that as UNIT_FILE_INDIRECT. */
+                r = find_symlinks_in_scope(scope, paths, i, false, &state);
+                if (r < 0)
                         return r;
-                else if (r > 0)
-                        return UNIT_FILE_DISABLED;
-                else if (r == 0) {
-                        if (also)
-                                return UNIT_FILE_INDIRECT;
-                        return UNIT_FILE_STATIC;
+                if (r > 0)
+                        state = UNIT_FILE_INDIRECT;
+                else {
+                        if (unit_file_install_info_has_rules(i))
+                                state = UNIT_FILE_DISABLED;
+                        else if (unit_file_install_info_has_also(i))
+                                state = UNIT_FILE_INDIRECT;
+                        else
+                                state = UNIT_FILE_STATIC;
                 }
+
+                break;
+
+        default:
+                assert_not_reached("Unexpect unit file type.");
         }
 
-        return r < 0 ? r : state;
+        *ret = state;
+        return 0;
 }
 
-UnitFileState unit_file_get_state(
+int unit_file_get_state(
                 UnitFileScope scope,
                 const char *root_dir,
-                const char *name) {
+                const char *name,
+                UnitFileState *ret) {
 
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
         int r;
 
         assert(scope >= 0);
         assert(scope < _UNIT_FILE_SCOPE_MAX);
         assert(name);
 
-        if (root_dir && scope != UNIT_FILE_SYSTEM)
-                return -EINVAL;
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
+        if (r < 0)
+                return r;
+
+        return unit_file_lookup_state(scope, &paths, name, ret);
+}
+
+int unit_file_exists(UnitFileScope scope, const LookupPaths *paths, const char *name) {
+        _cleanup_(install_context_done) InstallContext c = {};
+        int r;
+
+        assert(paths);
+        assert(name);
+
+        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
+                return -EINVAL;
+
+        r = install_info_discover(scope, &c, paths, name, 0, NULL, NULL, NULL);
+        if (r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+static int split_pattern_into_name_and_instances(const char *pattern, char **out_unit_name, char ***out_instances) {
+        _cleanup_strv_free_ char **instances = NULL;
+        _cleanup_free_ char *unit_name = NULL;
+        int r;
+
+        assert(pattern);
+        assert(out_instances);
+        assert(out_unit_name);
 
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+        r = extract_first_word(&pattern, &unit_name, NULL, 0);
         if (r < 0)
                 return r;
 
-        return unit_file_lookup_state(scope, root_dir, &paths, name);
+        /* We handle the instances logic when unit name is extracted */
+        if (pattern) {
+                /* We only create instances when a rule of templated unit
+                 * is seen. A rule like enable foo@.service a b c will
+                 * result in an array of (a, b, c) as instance names */
+                if (!unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE))
+                        return -EINVAL;
+
+                instances = strv_split(pattern, WHITESPACE);
+                if (!instances)
+                        return -ENOMEM;
+
+                *out_instances = TAKE_PTR(instances);
+        }
+
+        *out_unit_name = TAKE_PTR(unit_name);
+
+        return 0;
 }
 
-int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name) {
+static int read_presets(UnitFileScope scope, const char *root_dir, Presets *presets) {
+        _cleanup_(presets_freep) Presets ps = {};
+        size_t n_allocated = 0;
         _cleanup_strv_free_ char **files = NULL;
         char **p;
         int r;
 
         assert(scope >= 0);
         assert(scope < _UNIT_FILE_SCOPE_MAX);
-        assert(name);
+        assert(presets);
 
-        if (scope == UNIT_FILE_SYSTEM)
-                r = conf_files_list(&files, ".preset", root_dir,
+        switch (scope) {
+        case UNIT_FILE_SYSTEM:
+                r = conf_files_list(&files, ".preset", root_dir, 0,
                                     "/etc/systemd/system-preset",
+                                    "/run/systemd/system-preset",
                                     "/usr/local/lib/systemd/system-preset",
                                     "/usr/lib/systemd/system-preset",
-#ifdef HAVE_SPLIT_USR
+#if HAVE_SPLIT_USR
                                     "/lib/systemd/system-preset",
 #endif
                                     NULL);
-        else if (scope == UNIT_FILE_GLOBAL)
-                r = conf_files_list(&files, ".preset", root_dir,
+                break;
+
+        case UNIT_FILE_GLOBAL:
+        case UNIT_FILE_USER:
+                r = conf_files_list(&files, ".preset", root_dir, 0,
                                     "/etc/systemd/user-preset",
+                                    "/run/systemd/user-preset",
                                     "/usr/local/lib/systemd/user-preset",
                                     "/usr/lib/systemd/user-preset",
                                     NULL);
-        else
-                return 1;
+                break;
+
+        default:
+                assert_not_reached("Invalid unit file scope");
+        }
 
         if (r < 0)
                 return r;
 
         STRV_FOREACH(p, files) {
                 _cleanup_fclose_ FILE *f;
+                int n = 0;
 
                 f = fopen(*p, "re");
                 if (!f) {
@@ -1924,151 +2874,352 @@ int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char
                 }
 
                 for (;;) {
-                        char line[LINE_MAX], *l;
+                        _cleanup_free_ char *line = NULL;
+                        PresetRule rule = {};
+                        const char *parameter;
+                        char *l;
 
-                        if (!fgets(line, sizeof(line), f))
+                        r = read_line(f, LONG_LINE_MAX, &line);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
                                 break;
 
                         l = strstrip(line);
-                        if (!*l)
-                                continue;
+                        n++;
 
-                        if (strchr(COMMENTS "\n", *l))
+                        if (isempty(l))
+                                continue;
+                        if (strchr(COMMENTS, *l))
                                 continue;
 
-                        if (first_word(l, "enable")) {
-                                l += 6;
-                                l += strspn(l, WHITESPACE);
+                        parameter = first_word(l, "enable");
+                        if (parameter) {
+                                char *unit_name;
+                                char **instances = NULL;
 
-                                if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
-                                        log_debug("Preset file says enable %s.", name);
-                                        return 1;
+                                /* Unit_name will remain the same as parameter when no instances are specified */
+                                r = split_pattern_into_name_and_instances(parameter, &unit_name, &instances);
+                                if (r < 0) {
+                                        log_syntax(NULL, LOG_WARNING, *p, n, r, "Couldn't parse line '%s'. Ignoring.", line);
+                                        continue;
                                 }
 
-                        } else if (first_word(l, "disable")) {
-                                l += 7;
-                                l += strspn(l, WHITESPACE);
+                                rule = (PresetRule) {
+                                        .pattern = unit_name,
+                                        .action = PRESET_ENABLE,
+                                        .instances = instances,
+                                };
+                        }
 
-                                if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
-                                        log_debug("Preset file says disable %s.", name);
-                                        return 0;
-                                }
+                        parameter = first_word(l, "disable");
+                        if (parameter) {
+                                char *pattern;
+
+                                pattern = strdup(parameter);
+                                if (!pattern)
+                                        return -ENOMEM;
+
+                                rule = (PresetRule) {
+                                        .pattern = pattern,
+                                        .action = PRESET_DISABLE,
+                                };
+                        }
+
+                        if (rule.action) {
+                                if (!GREEDY_REALLOC(ps.rules, n_allocated, ps.n_rules + 1))
+                                        return -ENOMEM;
 
-                        } else
-                                log_debug("Couldn't parse line '%s'", l);
+                                ps.rules[ps.n_rules++] = rule;
+                                continue;
+                        }
+
+                        log_syntax(NULL, LOG_WARNING, *p, n, 0, "Couldn't parse line '%s'. Ignoring.", line);
                 }
         }
 
-        /* Default is "enable" */
-        log_debug("Preset file doesn't say anything about %s, enabling.", name);
-        return 1;
+        *presets = ps;
+        ps = (Presets){};
+
+        return 0;
 }
 
-int unit_file_preset(
-                UnitFileScope scope,
-                bool runtime,
-                const char *root_dir,
-                char **files,
-                UnitFilePresetMode mode,
-                bool force,
-                UnitFileChange **changes,
-                unsigned *n_changes) {
+static int pattern_match_multiple_instances(
+                        const PresetRule rule,
+                        const char *unit_name,
+                        char ***ret) {
 
-        _cleanup_(install_context_done) InstallContext plus = {}, minus = {};
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
-        _cleanup_free_ char *config_path = NULL;
-        char **i;
-        int r, q;
+        _cleanup_free_ char *templated_name = NULL;
+        int r;
 
-        assert(scope >= 0);
-        assert(scope < _UNIT_FILE_SCOPE_MAX);
-        assert(mode < _UNIT_FILE_PRESET_MAX);
+        /* If no ret is needed or the rule itself does not have instances
+         * initalized, we return not matching */
+        if (!ret || !rule.instances)
+                return 0;
 
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+        r = unit_name_template(unit_name, &templated_name);
         if (r < 0)
                 return r;
+        if (!streq(rule.pattern, templated_name))
+                return 0;
 
-        r = get_config_path(scope, runtime, root_dir, &config_path);
-        if (r < 0)
-                return r;
+        /* Compose a list of specified instances when unit name is a template  */
+        if (unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
+                _cleanup_free_ char *prefix = NULL;
+                _cleanup_strv_free_ char **out_strv = NULL;
+                char **iter;
 
-        STRV_FOREACH(i, files) {
+                r = unit_name_to_prefix(unit_name, &prefix);
+                if (r < 0)
+                        return r;
 
-                if (!unit_name_is_valid(*i, UNIT_NAME_ANY))
-                        return -EINVAL;
+                STRV_FOREACH(iter, rule.instances) {
+                        _cleanup_free_ char *name = NULL;
+                        r = unit_name_build(prefix, *iter, ".service", &name);
+                        if (r < 0)
+                                return r;
+                        r = strv_extend(&out_strv, name);
+                        if (r < 0)
+                                return r;
+                }
+
+                *ret = TAKE_PTR(out_strv);
+                return 1;
+        } else {
+                /* We now know the input unit name is an instance name */
+                _cleanup_free_ char *instance_name = NULL;
 
-                r = unit_file_query_preset(scope, root_dir, *i);
+                r = unit_name_to_instance(unit_name, &instance_name);
                 if (r < 0)
                         return r;
 
-                if (r && mode != UNIT_FILE_PRESET_DISABLE_ONLY)
-                        r = install_info_add_auto(&plus, *i);
-                else if (!r && mode != UNIT_FILE_PRESET_ENABLE_ONLY)
-                        r = install_info_add_auto(&minus, *i);
+                if (strv_find(rule.instances, instance_name))
+                        return 1;
+        }
+        return 0;
+}
+
+static int query_presets(const char *name, const Presets presets, char ***instance_name_list) {
+        PresetAction action = PRESET_UNKNOWN;
+        size_t i;
+        char **s;
+        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
+                return -EINVAL;
+
+        for (i = 0; i < presets.n_rules; i++)
+                if (pattern_match_multiple_instances(presets.rules[i], name, instance_name_list) > 0 ||
+                    fnmatch(presets.rules[i].pattern, name, FNM_NOESCAPE) == 0) {
+                        action = presets.rules[i].action;
+                        break;
+                }
+
+        switch (action) {
+        case PRESET_UNKNOWN:
+                log_debug("Preset files don't specify rule for %s. Enabling.", name);
+                return 1;
+        case PRESET_ENABLE:
+                if (instance_name_list && *instance_name_list)
+                        STRV_FOREACH(s, *instance_name_list)
+                                log_debug("Preset files say enable %s.", *s);
                 else
-                        r = 0;
-                if (r < 0)
-                        return r;
+                        log_debug("Preset files say enable %s.", name);
+                return 1;
+        case PRESET_DISABLE:
+                log_debug("Preset files say disable %s.", name);
+                return 0;
+        default:
+                assert_not_reached("invalid preset action");
         }
+}
 
-        r = 0;
+int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name) {
+        _cleanup_(presets_freep) Presets presets = {};
+        int r;
+
+        r = read_presets(scope, root_dir, &presets);
+        if (r < 0)
+                return r;
+
+        return query_presets(name, presets, NULL);
+}
+
+static int execute_preset(
+                UnitFileScope scope,
+                InstallContext *plus,
+                InstallContext *minus,
+                const LookupPaths *paths,
+                const char *config_path,
+                char **files,
+                UnitFilePresetMode mode,
+                bool force,
+                UnitFileChange **changes,
+                size_t *n_changes) {
+
+        int r;
+
+        assert(plus);
+        assert(minus);
+        assert(paths);
+        assert(config_path);
 
         if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) {
                 _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
 
-                r = install_context_mark_for_removal(&minus, &paths, &remove_symlinks_to, config_path, root_dir);
+                r = install_context_mark_for_removal(scope, minus, paths, &remove_symlinks_to, config_path, changes, n_changes);
+                if (r < 0)
+                        return r;
 
-                q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
-                if (r == 0)
-                        r = q;
-        }
+                r = remove_marked_symlinks(remove_symlinks_to, config_path, paths, false, changes, n_changes);
+        } else
+                r = 0;
 
         if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) {
+                int q;
+
                 /* Returns number of symlinks that where supposed to be installed. */
-                q = install_context_apply(&plus, &paths, config_path, root_dir, force, changes, n_changes);
-                if (r == 0)
-                        r = q;
+                q = install_context_apply(scope, plus, paths, config_path, force, SEARCH_LOAD, changes, n_changes);
+                if (r >= 0) {
+                        if (q < 0)
+                                r = q;
+                        else
+                                r += q;
+                }
         }
 
         return r;
 }
 
+static int preset_prepare_one(
+                UnitFileScope scope,
+                InstallContext *plus,
+                InstallContext *minus,
+                LookupPaths *paths,
+                const char *name,
+                Presets presets,
+                UnitFileChange **changes,
+                size_t *n_changes) {
+
+        _cleanup_(install_context_done) InstallContext tmp = {};
+        _cleanup_strv_free_ char **instance_name_list = NULL;
+        UnitFileInstallInfo *i;
+        int r;
+
+        if (install_info_find(plus, name) || install_info_find(minus, name))
+                return 0;
+
+        r = install_info_discover(scope, &tmp, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS,
+                                  &i, changes, n_changes);
+        if (r < 0)
+                return r;
+        if (!streq(name, i->name)) {
+                log_debug("Skipping %s because it is an alias for %s.", name, i->name);
+                return 0;
+        }
+
+        r = query_presets(name, presets, &instance_name_list);
+        if (r < 0)
+                return r;
+
+        if (r > 0) {
+                if (instance_name_list) {
+                        char **s;
+                        STRV_FOREACH(s, instance_name_list) {
+                                r = install_info_discover_and_check(scope, plus, paths, *s, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
+                                                                    &i, changes, n_changes);
+                                if (r < 0)
+                                        return r;
+                        }
+                } else {
+                        r = install_info_discover_and_check(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
+                                                            &i, changes, n_changes);
+                        if (r < 0)
+                                return r;
+                }
+
+        } else
+                r = install_info_discover(scope, minus, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS,
+                                          &i, changes, n_changes);
+
+        return r;
+}
+
+int unit_file_preset(
+                UnitFileScope scope,
+                UnitFileFlags flags,
+                const char *root_dir,
+                char **files,
+                UnitFilePresetMode mode,
+                UnitFileChange **changes,
+                size_t *n_changes) {
+
+        _cleanup_(install_context_done) InstallContext plus = {}, minus = {};
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
+        _cleanup_(presets_freep) Presets presets = {};
+        const char *config_path;
+        char **i;
+        int r;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(mode < _UNIT_FILE_PRESET_MAX);
+
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
+        if (r < 0)
+                return r;
+
+        config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config;
+        if (!config_path)
+                return -ENXIO;
+
+        r = read_presets(scope, root_dir, &presets);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, files) {
+                r = preset_prepare_one(scope, &plus, &minus, &paths, *i, presets, changes, n_changes);
+                if (r < 0)
+                        return r;
+        }
+
+        return execute_preset(scope, &plus, &minus, &paths, config_path, files, mode, !!(flags & UNIT_FILE_FORCE), changes, n_changes);
+}
+
 int unit_file_preset_all(
                 UnitFileScope scope,
-                bool runtime,
+                UnitFileFlags flags,
                 const char *root_dir,
                 UnitFilePresetMode mode,
-                bool force,
                 UnitFileChange **changes,
-                unsigned *n_changes) {
+                size_t *n_changes) {
 
         _cleanup_(install_context_done) InstallContext plus = {}, minus = {};
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
-        _cleanup_free_ char *config_path = NULL;
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
+        _cleanup_(presets_freep) Presets presets = {};
+        const char *config_path = NULL;
         char **i;
-        int r, q;
+        int r;
 
         assert(scope >= 0);
         assert(scope < _UNIT_FILE_SCOPE_MAX);
         assert(mode < _UNIT_FILE_PRESET_MAX);
 
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
         if (r < 0)
                 return r;
 
-        r = get_config_path(scope, runtime, root_dir, &config_path);
+        config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config;
+        if (!config_path)
+                return -ENXIO;
+
+        r = read_presets(scope, root_dir, &presets);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(i, paths.unit_path) {
+        STRV_FOREACH(i, paths.search_path) {
                 _cleanup_closedir_ DIR *d = NULL;
-                _cleanup_free_ char *units_dir;
-
-                units_dir = path_join(root_dir, *i, NULL);
-                if (!units_dir)
-                        return -ENOMEM;
+                struct dirent *de;
 
-                d = opendir(units_dir);
+                d = opendir(*i);
                 if (!d) {
                         if (errno == ENOENT)
                                 continue;
@@ -2076,62 +3227,32 @@ int unit_file_preset_all(
                         return -errno;
                 }
 
-                for (;;) {
-                        struct dirent *de;
-
-                        errno = 0;
-                        de = readdir(d);
-                        if (!de && errno != 0)
-                                return -errno;
-
-                        if (!de)
-                                break;
-
-                        if (hidden_file(de->d_name))
-                                continue;
+                FOREACH_DIRENT(de, d, return -errno) {
 
                         if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
                                 continue;
 
                         dirent_ensure_type(d, de);
 
-                        if (de->d_type != DT_REG)
+                        if (!IN_SET(de->d_type, DT_LNK, DT_REG))
                                 continue;
 
-                        r = unit_file_query_preset(scope, root_dir, de->d_name);
-                        if (r < 0)
-                                return r;
-
-                        if (r && mode != UNIT_FILE_PRESET_DISABLE_ONLY)
-                                r = install_info_add_auto(&plus, de->d_name);
-                        else if (!r && mode != UNIT_FILE_PRESET_ENABLE_ONLY)
-                                r = install_info_add_auto(&minus, de->d_name);
-                        else
-                                r = 0;
+                        /* we don't pass changes[] in, because we want to handle errors on our own */
+                        r = preset_prepare_one(scope, &plus, &minus, &paths, de->d_name, presets, NULL, 0);
+                        if (r == -ERFKILL)
+                                r = unit_file_changes_add(changes, n_changes,
+                                                          UNIT_FILE_IS_MASKED, de->d_name, NULL);
+                        else if (r == -ENOLINK)
+                                r = unit_file_changes_add(changes, n_changes,
+                                                          UNIT_FILE_IS_DANGLING, de->d_name, NULL);
+                        else if (r == -EADDRNOTAVAIL) /* Ignore generated/transient units when applying preset */
+                                continue;
                         if (r < 0)
                                 return r;
                 }
         }
 
-        r = 0;
-
-        if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) {
-                _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
-
-                r = install_context_mark_for_removal(&minus, &paths, &remove_symlinks_to, config_path, root_dir);
-
-                q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, NULL);
-                if (r == 0)
-                        r = q;
-        }
-
-        if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) {
-                q = install_context_apply(&plus, &paths, config_path, root_dir, force, changes, n_changes);
-                if (r == 0)
-                        r = q;
-        }
-
-        return r;
+        return execute_preset(scope, &plus, &minus, &paths, config_path, NULL, mode, !!(flags & UNIT_FILE_FORCE), changes, n_changes);
 }
 
 static void unit_file_list_free_one(UnitFileList *f) {
@@ -2142,14 +3263,20 @@ static void unit_file_list_free_one(UnitFileList *f) {
         free(f);
 }
 
+Hashmap* unit_file_list_free(Hashmap *h) {
+        return hashmap_free_with_destructor(h, unit_file_list_free_one);
+}
+
 DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free_one);
 
 int unit_file_get_list(
                 UnitFileScope scope,
                 const char *root_dir,
-                Hashmap *h) {
+                Hashmap *h,
+                char **states,
+                char **patterns) {
 
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
+        _cleanup_(lookup_paths_free) LookupPaths paths = {};
         char **i;
         int r;
 
@@ -2157,53 +3284,33 @@ int unit_file_get_list(
         assert(scope < _UNIT_FILE_SCOPE_MAX);
         assert(h);
 
-        if (root_dir && scope != UNIT_FILE_SYSTEM)
-                return -EINVAL;
-
-        if (root_dir) {
-                r = access(root_dir, F_OK);
-                if (r < 0)
-                        return -errno;
-        }
-
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(i, paths.unit_path) {
+        STRV_FOREACH(i, paths.search_path) {
                 _cleanup_closedir_ DIR *d = NULL;
-                _cleanup_free_ char *units_dir;
-
-                units_dir = path_join(root_dir, *i, NULL);
-                if (!units_dir)
-                        return -ENOMEM;
+                struct dirent *de;
 
-                d = opendir(units_dir);
+                d = opendir(*i);
                 if (!d) {
                         if (errno == ENOENT)
                                 continue;
+                        if (IN_SET(errno, ENOTDIR, EACCES)) {
+                                log_debug_errno(errno, "Failed to open \"%s\": %m", *i);
+                                continue;
+                        }
 
                         return -errno;
                 }
 
-                for (;;) {
+                FOREACH_DIRENT(de, d, return -errno) {
                         _cleanup_(unit_file_list_free_onep) UnitFileList *f = NULL;
-                        struct dirent *de;
-                        _cleanup_free_ char *path = NULL;
-                        bool also = false;
-
-                        errno = 0;
-                        de = readdir(d);
-                        if (!de && errno != 0)
-                                return -errno;
 
-                        if (!de)
-                                break;
-
-                        if (hidden_file(de->d_name))
+                        if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
                                 continue;
 
-                        if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
+                        if (!strv_fnmatch_or_empty(patterns, de->d_name, FNM_NOESCAPE))
                                 continue;
 
                         if (hashmap_get(h, de->d_name))
@@ -2218,48 +3325,22 @@ int unit_file_get_list(
                         if (!f)
                                 return -ENOMEM;
 
-                        f->path = path_make_absolute(de->d_name, units_dir);
+                        f->path = path_make_absolute(de->d_name, *i);
                         if (!f->path)
                                 return -ENOMEM;
 
-                        r = null_or_empty_path(f->path);
-                        if (r < 0 && r != -ENOENT)
-                                return r;
-                        else if (r > 0) {
-                                f->state =
-                                        path_startswith(*i, "/run") ?
-                                        UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
-                                goto found;
-                        }
-
-                        r = find_symlinks_in_scope(scope, root_dir, de->d_name, &f->state);
+                        r = unit_file_lookup_state(scope, &paths, de->d_name, &f->state);
                         if (r < 0)
-                                return r;
-                        else if (r > 0) {
-                                f->state = UNIT_FILE_ENABLED;
-                                goto found;
-                        }
-
-                        path = path_make_absolute(de->d_name, *i);
-                        if (!path)
-                                return -ENOMEM;
+                                f->state = UNIT_FILE_BAD;
 
-                        r = unit_file_can_install(&paths, root_dir, path, true, &also);
-                        if (r == -EINVAL ||  /* Invalid setting? */
-                            r == -EBADMSG || /* Invalid format? */
-                            r == -ENOENT     /* Included file not found? */)
-                                f->state = UNIT_FILE_INVALID;
-                        else if (r < 0)
-                                return r;
-                        else if (r > 0)
-                                f->state = UNIT_FILE_DISABLED;
-                        else
-                                f->state = also ? UNIT_FILE_INDIRECT : UNIT_FILE_STATIC;
+                        if (!strv_isempty(states) &&
+                            !strv_contains(states, unit_file_state_to_string(f->state)))
+                                continue;
 
-                found:
                         r = hashmap_put(h, basename(f->path), f);
                         if (r < 0)
                                 return r;
+
                         f = NULL; /* prevent cleanup */
                 }
         }
@@ -2277,7 +3358,9 @@ static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = {
         [UNIT_FILE_STATIC] = "static",
         [UNIT_FILE_DISABLED] = "disabled",
         [UNIT_FILE_INDIRECT] = "indirect",
-        [UNIT_FILE_INVALID] = "invalid",
+        [UNIT_FILE_GENERATED] = "generated",
+        [UNIT_FILE_TRANSIENT] = "transient",
+        [UNIT_FILE_BAD] = "bad",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState);
@@ -2285,6 +3368,8 @@ DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState);
 static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = {
         [UNIT_FILE_SYMLINK] = "symlink",
         [UNIT_FILE_UNLINK] = "unlink",
+        [UNIT_FILE_IS_MASKED] = "masked",
+        [UNIT_FILE_IS_DANGLING] = "dangling",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType);