chiark / gitweb /
journal: store XOR combination of entry data object hashes to identify hash lines
[elogind.git] / src / install.c
index 751d52b6480ade3b7d5f90b7dad105a34d9cda17..cfbd50ead954c93e518f19d05be6ef7f6402f5fe 100644 (file)
@@ -1,9 +1,9 @@
-/*-*- Mode: C; c-basic-offset: 8 -*-*/
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
 
 /***
   This file is part of systemd.
 
 
 /***
   This file is part of systemd.
 
-  Copyright 2010 Lennart Poettering
+  Copyright 2011 Lennart Poettering
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
@@ -11,7 +11,7 @@
   (at your option) any later version.
 
   systemd is distributed in the hope that it will be useful, but
   (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
+  WITHOUT ANY WARRANTY; without even the implied warranty <of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   General Public License for more details.
 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   General Public License for more details.
 
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include <sys/stat.h>
-#include <stdio.h>
-#include <getopt.h>
 #include <errno.h>
 #include <errno.h>
-#include <unistd.h>
 #include <fcntl.h>
 #include <fcntl.h>
-#include <dirent.h>
+#include <unistd.h>
+#include <string.h>
+#include <fnmatch.h>
 
 
-#include "log.h"
-#include "path-lookup.h"
 #include "util.h"
 #include "util.h"
-#include "macro.h"
+#include "hashmap.h"
+#include "set.h"
+#include "path-lookup.h"
 #include "strv.h"
 #include "strv.h"
+#include "unit-name.h"
+#include "install.h"
 #include "conf-parser.h"
 #include "conf-parser.h"
-#include "dbus-common.h"
-#include "sd-daemon.h"
-
-static bool arg_force = false;
-static bool arg_all = false;
-static bool arg_verbose = false;
-
-static enum {
-        WHERE_SYSTEM,
-        WHERE_SESSION,
-        WHERE_GLOBAL,
-} arg_where = WHERE_SYSTEM;
-
-static enum {
-        ACTION_INVALID,
-        ACTION_ENABLE,
-        ACTION_DISABLE,
-        ACTION_REALIZE,
-        ACTION_TEST
-} arg_action = ACTION_INVALID;
-
-static enum {
-        REALIZE_NO,        /* Don't reload/start/stop or anything */
-        REALIZE_RELOAD,    /* Only reload daemon config, don't stop/start */
-        REALIZE_MINIMAL,   /* Only shutdown/restart if running. */
-        REALIZE_MAYBE,     /* Start if WantedBy= suggests */
-        REALIZE_YES        /* Start unconditionally */
-} arg_realize = REALIZE_NO;
 
 typedef struct {
         char *name;
 
 typedef struct {
         char *name;
@@ -70,615 +42,251 @@ typedef struct {
         char **wanted_by;
 } InstallInfo;
 
         char **wanted_by;
 } InstallInfo;
 
-static Hashmap *will_install = NULL, *have_installed = NULL;
-static Set *remove_symlinks_to = NULL;
-
-static int help(void) {
-
-        printf("%s [OPTIONS...] {COMMAND} ...\n\n"
-               "Install init system units.\n\n"
-               "  -h --help           Show this help\n"
-               "     --force          Override existing links\n"
-               "     --verbose        Show what is being done as it is done\n"
-               "     --system         Install into system\n"
-               "     --session        Install into session\n"
-               "     --global         Install into all sessions\n"
-               "     --all            When disabling, remove all symlinks, not\n"
-               "                      just those listed in the [Install] section\n"
-               "     --realize[=MODE] Start/stop/restart unit after installation\n"
-               "                      Takes 'no', 'minimal', 'maybe' or 'yes'\n\n"
-               "Commands:\n"
-               "  enable [NAME...]    Enable one or more units\n"
-               "  disable [NAME...]   Disable one or more units\n"
-               "  realize [NAME...]   Test whether any of the specified units are enabled\n"
-               "                      and the start/stop/restart units accordingly\n"
-               "  test [NAME...]      Test whether any of the specified units are enabled\n",
-               program_invocation_short_name);
-
-        return 0;
-}
-
-static int parse_argv(int argc, char *argv[]) {
-
-        enum {
-                ARG_SESSION = 0x100,
-                ARG_SYSTEM,
-                ARG_GLOBAL,
-                ARG_FORCE,
-                ARG_REALIZE,
-                ARG_ALL
-        };
-
-        static const struct option options[] = {
-                { "help",      no_argument,       NULL, 'h'         },
-                { "session",   no_argument,       NULL, ARG_SESSION },
-                { "system",    no_argument,       NULL, ARG_SYSTEM  },
-                { "global",    no_argument,       NULL, ARG_GLOBAL  },
-                { "force",     no_argument,       NULL, ARG_FORCE   },
-                { "realize",   optional_argument, NULL, ARG_REALIZE },
-                { "all",       no_argument,       NULL, ARG_ALL     },
-                { "verbose",   no_argument,       NULL, 'v'         },
-                { NULL,        0,                 NULL, 0           }
-        };
-
-        int c;
-        bool realize_switch = false;
-
-        assert(argc >= 1);
-        assert(argv);
-
-        while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0) {
-
-                switch (c) {
-
-                case 'h':
-                        help();
-                        return 0;
-
-                case ARG_SESSION:
-                        arg_where = WHERE_SESSION;
-                        break;
-
-                case ARG_SYSTEM:
-                        arg_where = WHERE_SYSTEM;
-                        break;
-
-                case ARG_GLOBAL:
-                        arg_where = WHERE_GLOBAL;
-                        break;
-
-                case ARG_FORCE:
-                        arg_force = true;
-                        break;
-
-                case ARG_REALIZE:
-
-                        realize_switch = true;
-
-                        if (!optarg)
-                                arg_realize = REALIZE_MAYBE;
-                        else if (streq(optarg, "no"))
-                                arg_realize = REALIZE_NO;
-                        else if (streq(optarg, "minimal"))
-                                arg_realize = REALIZE_MINIMAL;
-                        else if (streq(optarg, "maybe"))
-                                arg_realize = REALIZE_MAYBE;
-                        else if (streq(optarg, "yes"))
-                                arg_realize = REALIZE_YES;
-                        else if (streq(optarg, "reload"))
-                                arg_realize = REALIZE_RELOAD;
-                        else {
-                                log_error("Invalid --realize argument %s", optarg);
-                                return -EINVAL;
-                        }
-
-                        break;
-
-                case ARG_ALL:
-                        arg_all = true;
-                        break;
-
-                case 'v':
-                        arg_verbose = true;
-                        break;
-
-                case '?':
-                        return -EINVAL;
-
-                default:
-                        log_error("Unknown option code %c", c);
-                        return -EINVAL;
-                }
-        }
-
-        if (optind >= argc) {
-                help();
-                return -EINVAL;
-        }
-
-        if (streq(argv[optind], "enable"))
-                arg_action = ACTION_ENABLE;
-        else if (streq(argv[optind], "disable"))
-                arg_action = ACTION_DISABLE;
-        else if (streq(argv[optind], "test"))
-                arg_action = ACTION_TEST;
-        else if (streq(argv[optind], "realize")) {
-                arg_action = ACTION_REALIZE;
-
-                if (!realize_switch)
-                        arg_realize = REALIZE_MAYBE;
-        } else {
-                log_error("Unknown verb %s.", argv[optind]);
-                return -EINVAL;
-        }
-
-        optind++;
+typedef struct {
+        Hashmap *will_install;
+        Hashmap *have_installed;
+} InstallContext;
 
 
-        if (optind >= argc) {
-                log_error("Missing unit name.");
-                return -EINVAL;
-        }
+static int lookup_paths_init_from_scope(LookupPaths *paths, UnitFileScope scope) {
+        assert(paths);
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
 
 
+        zero(*paths);
 
 
-        return 1;
+        return lookup_paths_init(paths,
+                                 scope == UNIT_FILE_SYSTEM ? MANAGER_SYSTEM : MANAGER_USER,
+                                 scope == UNIT_FILE_USER);
 }
 
 }
 
-static void install_info_free(InstallInfo *i) {
-        assert(i);
+static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) {
+        char *p = NULL;
+        int r;
 
 
-        free(i->name);
-        free(i->path);
-        strv_free(i->aliases);
-        strv_free(i->wanted_by);
-        free(i);
-}
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(ret);
 
 
-static void install_info_hashmap_free(Hashmap *m) {
-        InstallInfo *i;
+        switch (scope) {
 
 
-        while ((i = hashmap_steal_first(m)))
-                install_info_free(i);
+        case UNIT_FILE_SYSTEM:
 
 
-        hashmap_free(m);
-}
+                if (root_dir && runtime)
+                        return -EINVAL;
 
 
-static bool unit_name_valid(const char *name) {
+                if (runtime)
+                        p = strdup("/run/systemd/system");
+                else if (root_dir)
+                        asprintf(&p, "%s/%s", root_dir, SYSTEM_CONFIG_UNIT_PATH);
+                else
+                        p = strdup(SYSTEM_CONFIG_UNIT_PATH);
 
 
-        /* This is a minimal version of unit_name_valid() from
-         * unit-name.c */
+                break;
 
 
-        if (!*name)
-                return false;
+        case UNIT_FILE_GLOBAL:
 
 
-        if (ignore_file(name))
-                return false;
+                if (root_dir)
+                        return -EINVAL;
 
 
-        return true;
-}
+                if (runtime)
+                        p = strdup("/run/systemd/user");
+                else
+                        p = strdup(USER_CONFIG_UNIT_PATH);
+                break;
 
 
-static int install_info_add(const char *name) {
-        InstallInfo *i;
-        int r;
+        case UNIT_FILE_USER:
 
 
-        if (!unit_name_valid(name))
-                return -EINVAL;
+                if (root_dir || runtime)
+                        return -EINVAL;
 
 
-        if (hashmap_get(have_installed, name) ||
-            hashmap_get(will_install, name))
-                return 0;
+                r = user_config_home(&p);
+                if (r <= 0)
+                        return r < 0 ? r : -ENOENT;
 
 
-        if (!(i = new0(InstallInfo, 1))) {
-                r = -ENOMEM;
-                goto fail;
-        }
+                break;
 
 
-        if (!(i->name = strdup(name))) {
-                r = -ENOMEM;
-                goto fail;
+        default:
+                assert_not_reached("Bad scope");
         }
 
         }
 
-        if ((r = hashmap_put(will_install, i->name, i)) < 0)
-                goto fail;
+        if (!p)
+                return -ENOMEM;
 
 
+        *ret = p;
         return 0;
         return 0;
-
-fail:
-        if (i)
-                install_info_free(i);
-
-        return r;
 }
 
 }
 
-static int daemon_reload(DBusConnection *bus) {
-        DBusMessage *m = NULL, *reply = NULL;
-        DBusError error;
-        int r;
+static int add_file_change(
+                UnitFileChange **changes,
+                unsigned *n_changes,
+                UnitFileChangeType type,
+                const char *path,
+                const char *source) {
 
 
-        assert(bus);
+        UnitFileChange *c;
+        unsigned i;
 
 
-        dbus_error_init(&error);
+        assert(path);
+        assert(!changes == !n_changes);
 
 
-        if (arg_verbose)
-                log_info("Reloading daemon configuration.");
+        if (!changes)
+                return 0;
 
 
-        if (!(m = dbus_message_new_method_call(
-                              "org.freedesktop.systemd1",
-                              "/org/freedesktop/systemd1",
-                              "org.freedesktop.systemd1.Manager",
-                              "Reload"))) {
-                log_error("Could not allocate message.");
+        c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange));
+        if (!c)
                 return -ENOMEM;
                 return -ENOMEM;
-        }
-
-        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
-                log_error("Failed to reload configuration: %s", error.message);
-                r = -EIO;
-                goto finish;
-        }
-
-        r = 0;
-
-finish:
-        if (m)
-                dbus_message_unref(m);
-
-        if (reply)
-                dbus_message_unref(reply);
-
-        dbus_error_free(&error);
-
-        return r;
-}
-
-static int install_info_run(DBusConnection *bus, InstallInfo *i, bool enabled) {
-        DBusMessage *m = NULL, *reply = NULL;
-        DBusError error;
-        int r;
-        const char *mode = "replace";
-
-        assert(bus);
-        assert(i);
-
-        dbus_error_init(&error);
-
-        if (arg_action == ACTION_ENABLE ||
-            (arg_action == ACTION_REALIZE && enabled)) {
-
-                if (arg_realize == REALIZE_MAYBE) {
-                        char **k;
-                        bool yes_please = false;
-
-                        STRV_FOREACH(k, i->wanted_by) {
-                                DBusMessageIter sub, iter;
-
-                                const char *path, *state;
-                                const char *interface = "org.freedesktop.systemd1.Unit";
-                                const char *property = "ActiveState";
-
-                                if (!(m = dbus_message_new_method_call(
-                                                      "org.freedesktop.systemd1",
-                                                      "/org/freedesktop/systemd1",
-                                                      "org.freedesktop.systemd1.Manager",
-                                                      "GetUnit"))) {
-                                        log_error("Could not allocate message.");
-                                        r = -ENOMEM;
-                                        goto finish;
-                                }
-
-                                if (!dbus_message_append_args(m,
-                                                              DBUS_TYPE_STRING, k,
-                                                              DBUS_TYPE_INVALID)) {
-                                        log_error("Could not append arguments to message.");
-                                        r = -ENOMEM;
-                                        goto finish;
-                                }
-
-                                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
-                                        /* Hmm, this unit doesn't exist, let's try the next one */
-                                        dbus_message_unref(m);
-                                        m = NULL;
-                                        continue;
-                                }
-
-                                if (!dbus_message_get_args(reply, &error,
-                                                           DBUS_TYPE_OBJECT_PATH, &path,
-                                                           DBUS_TYPE_INVALID)) {
-                                        log_error("Failed to parse reply: %s", error.message);
-                                        r = -EIO;
-                                        goto finish;
-                                }
-
-                                dbus_message_unref(m);
-                                if (!(m = dbus_message_new_method_call(
-                                                      "org.freedesktop.systemd1",
-                                                      path,
-                                                      "org.freedesktop.DBus.Properties",
-                                                      "Get"))) {
-                                        log_error("Could not allocate message.");
-                                        r = -ENOMEM;
-                                        goto finish;
-                                }
 
 
-                                if (!dbus_message_append_args(m,
-                                                              DBUS_TYPE_STRING, &interface,
-                                                              DBUS_TYPE_STRING, &property,
-                                                              DBUS_TYPE_INVALID)) {
-                                        log_error("Could not append arguments to message.");
-                                        r = -ENOMEM;
-                                        goto finish;
-                                }
-
-                                dbus_message_unref(reply);
-                                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
-                                        log_error("Failed to issue method call: %s", error.message);
-                                        r = -EIO;
-                                        goto finish;
-                                }
-
-                                if (!dbus_message_iter_init(reply, &iter) ||
-                                    dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
-                                        log_error("Failed to parse reply.");
-                                        r = -EIO;
-                                        goto finish;
-                                }
-
-                                dbus_message_iter_recurse(&iter, &sub);
-
-                                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)  {
-                                        log_error("Failed to parse reply.");
-                                        r = -EIO;
-                                        goto finish;
-                                }
-
-                                dbus_message_iter_get_basic(&sub, &state);
-
-                                dbus_message_unref(m);
-                                dbus_message_unref(reply);
-                                m = reply = NULL;
-
-                                if (streq(state, "active") ||
-                                    startswith(state, "reloading") ||
-                                    startswith(state, "activating")) {
-                                        yes_please = true;
-                                        break;
-                                }
-                        }
-
-                        if (!yes_please) {
-                                r = 0;
-                                goto finish;
-                        }
-                }
-
-                if (arg_verbose)
-                        log_info("Restarting %s.", i->name);
-
-                if (!(m = dbus_message_new_method_call(
-                                      "org.freedesktop.systemd1",
-                                      "/org/freedesktop/systemd1",
-                                      "org.freedesktop.systemd1.Manager",
-                                      arg_realize == REALIZE_MINIMAL ? "TryRestartUnit" : "RestartUnit"))) {
-                        log_error("Could not allocate message.");
-                        r = -ENOMEM;
-                        goto finish;
-                }
-
-                if (!dbus_message_append_args(m,
-                                              DBUS_TYPE_STRING, &i->name,
-                                              DBUS_TYPE_STRING, &mode,
-                                              DBUS_TYPE_INVALID)) {
-                        log_error("Could not append arguments to message.");
-                        r = -ENOMEM;
-                        goto finish;
-                }
+        *changes = c;
+        i = *n_changes;
 
 
+        c[i].type = type;
+        c[i].path = strdup(path);
+        if (!c[i].path)
+                return -ENOMEM;
 
 
-        } else if (arg_action == ACTION_DISABLE ||
-                   (arg_action == ACTION_REALIZE && !enabled)) {
-
-                if (arg_verbose)
-                        log_info("Stopping %s.", i->name);
-
-                if (!(m = dbus_message_new_method_call(
-                                      "org.freedesktop.systemd1",
-                                      "/org/freedesktop/systemd1",
-                                      "org.freedesktop.systemd1.Manager",
-                                      "StopUnit"))) {
-                        log_error("Could not allocate message.");
-                        r = -ENOMEM;
-                        goto finish;
-                }
-
-                if (!dbus_message_append_args(m,
-                                              DBUS_TYPE_STRING, &i->name,
-                                              DBUS_TYPE_STRING, &mode,
-                                              DBUS_TYPE_INVALID)) {
-                        log_error("Could not append arguments to message.");
-                        r = -ENOMEM;
-                        goto finish;
+        if (source) {
+                c[i].source = strdup(source);
+                if (!c[i].source) {
+                        free(c[i].path);
+                        return -ENOMEM;
                 }
         } else
                 }
         } else
-                assert_not_reached("install_info_run() called but nothing to do?");
-
-        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
-                log_error("Failed to realize unit: %s", error.message);
-                r = -EIO;
-                goto finish;
-        }
-
-        r = 0;
-
-finish:
-        if (m)
-                dbus_message_unref(m);
-
-        if (reply)
-                dbus_message_unref(reply);
-
-        dbus_error_free(&error);
-
-        return r;
-}
-
-static int config_parse_also(
-                const char *filename,
-                unsigned line,
-                const char *section,
-                const char *lvalue,
-                const char *rvalue,
-                void *data,
-                void *userdata) {
-
-        char *w;
-        size_t l;
-        char *state;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-
-        FOREACH_WORD_QUOTED(w, l, rvalue, state) {
-                char *n;
-                int r;
-
-                if (!(n = strndup(w, l)))
-                        return -ENOMEM;
-
-                r = install_info_add(n);
-                free(n);
-
-                if (r < 0)
-                        return r;
-        }
+                c[i].source = NULL;
 
 
+        *n_changes = i+1;
         return 0;
 }
 
         return 0;
 }
 
-static int mark_symlink_for_removal(const char *p) {
+static int mark_symlink_for_removal(
+                Set **remove_symlinks_to,
+                const char *p) {
+
         char *n;
         int r;
 
         assert(p);
         char *n;
         int r;
 
         assert(p);
-        assert(path_is_absolute(p));
 
 
-        if (!remove_symlinks_to)
-                return 0;
+        r = set_ensure_allocated(remove_symlinks_to, string_hash_func, string_compare_func);
+        if (r < 0)
+                return r;
 
 
-        if (!(n = canonicalize_file_name(p)))
-                if (!(n = strdup(p)))
-                        return -ENOMEM;
+        n = strdup(p);
+        if (!n)
+                return -ENOMEM;
 
         path_kill_slashes(n);
 
 
         path_kill_slashes(n);
 
-        if ((r = set_put(remove_symlinks_to, n)) < 0) {
+        r = set_put(*remove_symlinks_to, n);
+        if (r < 0) {
                 free(n);
                 free(n);
-                return r;
+                return r == -EEXIST ? 0 : r;
         }
 
         return 0;
 }
 
         }
 
         return 0;
 }
 
-static int remove_marked_symlinks_fd(int fd, const char *config_path, const char *root, bool *deleted) {
+static int remove_marked_symlinks_fd(
+                Set *remove_symlinks_to,
+                int fd,
+                const char *path,
+                const char *config_path,
+                bool *deleted,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
         int r = 0;
         DIR *d;
         int r = 0;
         DIR *d;
-        struct dirent *de;
+        struct dirent buffer, *de;
 
 
+        assert(remove_symlinks_to);
         assert(fd >= 0);
         assert(fd >= 0);
-        assert(root);
+        assert(path);
+        assert(config_path);
         assert(deleted);
 
         assert(deleted);
 
-        if (!(d = fdopendir(fd)))
+        d = fdopendir(fd);
+        if (!d) {
+                close_nointr_nofail(fd);
                 return -errno;
                 return -errno;
+        }
 
         rewinddir(d);
 
 
         rewinddir(d);
 
-        while ((de = readdir(d))) {
-                bool is_dir = false, is_link = false;
-
-                if (ignore_file(de->d_name))
-                        continue;
+        for (;;) {
+                int k;
 
 
-                if (de->d_type == DT_LNK)
-                        is_link = true;
-                else if (de->d_type == DT_DIR)
-                        is_dir = true;
-                else if (de->d_type == DT_UNKNOWN) {
-                        struct stat st;
-
-                        if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
-                                log_error("Failed to stat %s/%s: %m", root, de->d_name);
+                k = readdir_r(d, &buffer, &de);
+                if (k != 0) {
+                        r = -errno;
+                        break;
+                }
 
 
-                                if (r == 0)
-                                        r = -errno;
-                                continue;
-                        }
+                if (!de)
+                        break;
 
 
-                        is_link = S_ISLNK(st.st_mode);
-                        is_dir = S_ISDIR(st.st_mode);
-                } else
+                if (ignore_file(de->d_name))
                         continue;
 
                         continue;
 
-                if (is_dir) {
+                dirent_ensure_type(d, de);
+
+                if (de->d_type == DT_DIR) {
                         int nfd, q;
                         char *p;
 
                         int nfd, q;
                         char *p;
 
-                        if ((nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW)) < 0) {
-                                log_error("Failed to open %s/%s: %m", root, de->d_name);
+                        nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+                        if (nfd < 0) {
+                                if (errno == ENOENT)
+                                        continue;
 
                                 if (r == 0)
                                         r = -errno;
                                 continue;
                         }
 
 
                                 if (r == 0)
                                         r = -errno;
                                 continue;
                         }
 
-                        if (asprintf(&p, "%s/%s", root, de->d_name) < 0) {
-                                log_error("Failed to allocate directory string.");
+                        p = path_make_absolute(de->d_name, path);
+                        if (!p) {
                                 close_nointr_nofail(nfd);
                                 r = -ENOMEM;
                                 break;
                         }
 
                                 close_nointr_nofail(nfd);
                                 r = -ENOMEM;
                                 break;
                         }
 
-                        q = remove_marked_symlinks_fd(nfd, config_path, p, deleted);
+                        /* 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);
                         free(p);
                         free(p);
-                        close_nointr_nofail(nfd);
 
                         if (r == 0)
 
                         if (r == 0)
-                                q = r;
+                                r = q;
 
 
-                } else if (is_link) {
-                        char *p, *dest, *c;
+                } else if (de->d_type == DT_LNK) {
+                        char *p, *dest;
                         int q;
                         int q;
+                        bool found;
 
 
-                        if (asprintf(&p, "%s/%s", root, de->d_name) < 0) {
-                                log_error("Failed to allocate symlink string.");
+                        p = path_make_absolute(de->d_name, path);
+                        if (!p) {
                                 r = -ENOMEM;
                                 break;
                         }
 
                                 r = -ENOMEM;
                                 break;
                         }
 
-                        if ((q = readlink_and_make_absolute(p, &dest)) < 0) {
-                                log_error("Cannot read symlink %s: %s", p, strerror(-q));
+                        q = readlink_and_canonicalize(p, &dest);
+                        if (q < 0) {
                                 free(p);
 
                                 free(p);
 
+                                if (q == -ENOENT)
+                                        continue;
+
                                 if (r == 0)
                                         r = q;
                                 continue;
                         }
 
                                 if (r == 0)
                                         r = q;
                                 continue;
                         }
 
-                        if ((c = canonicalize_file_name(dest))) {
-                                /* This might fail if the destination
-                                 * is already removed */
-
-                                free(dest);
-                                dest = c;
-                        }
-
-                        path_kill_slashes(dest);
-                        if (set_get(remove_symlinks_to, dest)) {
+                        found =
+                                set_get(remove_symlinks_to, dest) ||
+                                set_get(remove_symlinks_to, file_name_from_path(dest));
 
 
-                                if (arg_verbose)
-                                        log_info("rm '%s'", p);
+                        if (found) {
 
 
-                                if (unlink(p) < 0) {
-                                        log_error("Cannot unlink symlink %s: %m", p);
+                                if (unlink(p) < 0 && errno != ENOENT) {
 
                                         if (r == 0)
                                                 r = -errno;
 
                                         if (r == 0)
                                                 r = -errno;
@@ -686,9 +294,12 @@ static int remove_marked_symlinks_fd(int fd, const char *config_path, const char
                                         rmdir_parents(p, config_path);
                                         path_kill_slashes(p);
 
                                         rmdir_parents(p, config_path);
                                         path_kill_slashes(p);
 
+                                        add_file_change(changes, n_changes, UNIT_FILE_UNLINK, p, NULL);
+
                                         if (!set_get(remove_symlinks_to, p)) {
 
                                         if (!set_get(remove_symlinks_to, p)) {
 
-                                                if ((r = mark_symlink_for_removal(p)) < 0) {
+                                                q = mark_symlink_for_removal(&remove_symlinks_to, p);
+                                                if (q < 0) {
                                                         if (r == 0)
                                                                 r = q;
                                                 } else
                                                         if (r == 0)
                                                                 r = q;
                                                 } else
@@ -702,10 +313,17 @@ static int remove_marked_symlinks_fd(int fd, const char *config_path, const char
                 }
         }
 
                 }
         }
 
+        closedir(d);
+
         return r;
 }
 
         return r;
 }
 
-static int remove_marked_symlinks(const char *config_path) {
+static int remove_marked_symlinks(
+                Set *remove_symlinks_to,
+                const char *config_path,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
         int fd, r = 0;
         bool deleted;
 
         int fd, r = 0;
         bool deleted;
 
@@ -714,17 +332,24 @@ static int remove_marked_symlinks(const char *config_path) {
         if (set_size(remove_symlinks_to) <= 0)
                 return 0;
 
         if (set_size(remove_symlinks_to) <= 0)
                 return 0;
 
-        if ((fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW)) < 0)
+        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+        if (fd < 0)
                 return -errno;
 
         do {
                 return -errno;
 
         do {
-                int q;
+                int q, cfd;
                 deleted = false;
 
                 deleted = false;
 
-                if ((q = remove_marked_symlinks_fd(fd, config_path, config_path, &deleted)) < 0) {
-                        if (r == 0)
-                                r = q;
+                cfd = dup(fd);
+                if (cfd < 0) {
+                        r = -errno;
+                        break;
                 }
                 }
+
+                /* 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);
+                if (r == 0)
+                        r = q;
         } while (deleted);
 
         close_nointr_nofail(fd);
         } while (deleted);
 
         close_nointr_nofail(fd);
@@ -732,434 +357,1598 @@ static int remove_marked_symlinks(const char *config_path) {
         return r;
 }
 
         return r;
 }
 
-static int create_symlink(const char *old_path, const char *new_path) {
-        int r;
+static int find_symlinks_fd(
+                const char *name,
+                int fd,
+                const char *path,
+                const char *config_path,
+                bool *same_name_link) {
 
 
-        assert(old_path);
-        assert(new_path);
+        int r = 0;
+        DIR *d;
+        struct dirent buffer, *de;
 
 
-        if (arg_action == ACTION_ENABLE) {
-                char *dest;
+        assert(name);
+        assert(fd >= 0);
+        assert(path);
+        assert(config_path);
+        assert(same_name_link);
 
 
-                mkdir_parents(new_path, 0755);
+        d = fdopendir(fd);
+        if (!d) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
 
 
-                if (symlink(old_path, new_path) >= 0)
-                        return 0;
+        for (;;) {
+                int k;
 
 
-                if (errno != EEXIST) {
-                        log_error("Cannot link %s to %s: %m", old_path, new_path);
-                        return -errno;
+                k = readdir_r(d, &buffer, &de);
+                if (k != 0) {
+                        r = -errno;
+                        break;
                 }
 
                 }
 
-                if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
+                if (!de)
+                        break;
+
+                if (ignore_file(de->d_name))
+                        continue;
+
+                dirent_ensure_type(d, de);
+
+                if (de->d_type == DT_DIR) {
+                        int nfd, q;
+                        char *p;
+
+                        nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+                        if (nfd < 0) {
+                                if (errno == ENOENT)
+                                        continue;
+
+                                if (r == 0)
+                                        r = -errno;
+                                continue;
+                        }
+
+                        p = path_make_absolute(de->d_name, path);
+                        if (!p) {
+                                close_nointr_nofail(nfd);
+                                r = -ENOMEM;
+                                break;
+                        }
+
+                        /* This will close nfd, regardless whether it succeeds or not */
+                        q = find_symlinks_fd(name, nfd, p, config_path, same_name_link);
+                        free(p);
 
 
-                        if (errno == EINVAL) {
-                                log_error("Cannot link %s to %s, file exists already and is not a symlink.", old_path, new_path);
-                                return -EEXIST;
+                        if (q > 0) {
+                                r = 1;
+                                break;
                         }
 
                         }
 
-                        log_error("readlink() failed: %s", strerror(-r));
+                        if (r == 0)
+                                r = q;
+
+                } else if (de->d_type == DT_LNK) {
+                        char *p, *dest;
+                        bool found_path, found_dest, b = false;
+                        int q;
+
+                        /* Acquire symlink name */
+                        p = path_make_absolute(de->d_name, path);
+                        if (!p) {
+                                r = -ENOMEM;
+                                break;
+                        }
+
+                        /* Acquire symlink destination */
+                        q = readlink_and_canonicalize(p, &dest);
+                        if (q < 0) {
+                                free(p);
+
+                                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);
+
+                        /* 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(file_name_from_path(dest), name);
+
+                        free(dest);
+
+                        if (found_path && found_dest) {
+                                char *t;
+
+                                /* Filter out same name links in the main
+                                 * config path */
+                                t = path_make_absolute(name, config_path);
+                                if (!t) {
+                                        free(p);
+                                        r = -ENOMEM;
+                                        break;
+                                }
+
+                                b = path_equal(t, p);
+                                free(t);
+                        }
+
+                        free(p);
+
+                        if (b)
+                                *same_name_link = true;
+                        else if (found_path || found_dest) {
+                                r = 1;
+                                break;
+                        }
+                }
+        }
+
+        closedir(d);
+
+        return r;
+}
+
+static int find_symlinks(
+                const char *name,
+                const char *config_path,
+                bool *same_name_link) {
+
+        int fd;
+
+        assert(name);
+        assert(config_path);
+        assert(same_name_link);
+
+        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+        if (fd < 0)
+                return -errno;
+
+        /* This takes possession of fd and closes it */
+        return find_symlinks_fd(name, fd, config_path, config_path, same_name_link);
+}
+
+static int find_symlinks_in_scope(
+                UnitFileScope scope,
+                const char *root_dir,
+                const char *name,
+                UnitFileState *state) {
+
+        int r;
+        char *path;
+        bool same_name_link_runtime = false, same_name_link = false;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(name);
+
+        if (scope == UNIT_FILE_SYSTEM || scope == UNIT_FILE_GLOBAL) {
+
+                /* First look in runtime config path */
+                r = get_config_path(scope, true, root_dir, &path);
+                if (r < 0)
+                        return r;
+
+                r = find_symlinks(name, path, &same_name_link_runtime);
+                free(path);
+
+                if (r < 0)
+                        return r;
+                else if (r > 0) {
+                        *state = UNIT_FILE_ENABLED_RUNTIME;
                         return r;
                 }
                         return r;
                 }
+        }
 
 
-                if (streq(dest, old_path)) {
-                        free(dest);
-                        return 0;
+        /* Then look in the normal config path */
+        r = get_config_path(scope, false, root_dir, &path);
+        if (r < 0)
+                return r;
+
+        r = find_symlinks(name, path, &same_name_link);
+        free(path);
+
+        if (r < 0)
+                return r;
+        else if (r > 0) {
+                *state = UNIT_FILE_ENABLED;
+                return r;
+        }
+
+        /* Hmm, we didn't find it, but maybe we found the same name
+         * link? */
+        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, *prefix;
+        int r;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        r = get_config_path(scope, runtime, root_dir, &prefix);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, files) {
+                char *path;
+
+                if (!unit_name_is_valid_no_type(*i, true)) {
+                        if (r == 0)
+                                r = -EINVAL;
+                        continue;
+                }
+
+                path = path_make_absolute(*i, prefix);
+                if (!path) {
+                        r = -ENOMEM;
+                        break;
+                }
+
+                if (symlink("/dev/null", path) >= 0) {
+                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");
+
+                        free(path);
+                        continue;
+                }
+
+                if (errno == EEXIST) {
+
+                        if (null_or_empty_path(path) > 0) {
+                                free(path);
+                                continue;
+                        }
+
+                        if (force) {
+                                unlink(path);
+
+                                if (symlink("/dev/null", path) >= 0) {
+
+                                        add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
+                                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");
+
+                                        free(path);
+                                        continue;
+                                }
+                        }
+
+                        if (r == 0)
+                                r = -EEXIST;
+                } else {
+                        if (r == 0)
+                                r = -errno;
+                }
+
+                free(path);
+        }
+
+        free(prefix);
+
+        return r;
+}
+
+int unit_file_unmask(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        char **i, *config_path = NULL;
+        int r, q;
+        Set *remove_symlinks_to = NULL;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
+                goto finish;
+
+        STRV_FOREACH(i, files) {
+                char *path;
+
+                if (!unit_name_is_valid_no_type(*i, true)) {
+                        if (r == 0)
+                                r = -EINVAL;
+                        continue;
                 }
 
                 }
 
-                if (!arg_force) {
-                        log_error("Cannot link %s to %s, symlink exists already and points to %s.", old_path, new_path, dest);
+                path = path_make_absolute(*i, config_path);
+                if (!path) {
+                        r = -ENOMEM;
+                        break;
+                }
+
+                q = null_or_empty_path(path);
+                if (q > 0) {
+                        if (unlink(path) >= 0) {
+                                mark_symlink_for_removal(&remove_symlinks_to, path);
+                                add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
+
+                                free(path);
+                                continue;
+                        }
+
+                        q = -errno;
+                }
+
+                if (q != -ENOENT && r == 0)
+                        r = q;
+
+                free(path);
+        }
+
+
+finish:
+        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
+        if (r == 0)
+                r = q;
+
+        set_free_free(remove_symlinks_to);
+        free(config_path);
+
+        return r;
+}
+
+int unit_file_link(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        LookupPaths paths;
+        char **i, *config_path = NULL;
+        int r, q;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        zero(paths);
+
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
+
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
+                goto finish;
+
+        STRV_FOREACH(i, files) {
+                char *path, *fn;
+                struct stat st;
+
+                fn = file_name_from_path(*i);
+
+                if (!path_is_absolute(*i) ||
+                    !unit_name_is_valid_no_type(fn, true)) {
+                        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) {
+                        r = q;
+                        break;
+                }
+
+                if (q > 0)
+                        continue;
+
+                path = path_make_absolute(fn, config_path);
+                if (!path) {
+                        r = -ENOMEM;
+                        break;
+                }
+
+                if (symlink(*i, path) >= 0) {
+                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);
+
+                        free(path);
+                        continue;
+                }
+
+                if (errno == EEXIST) {
+                        char *dest = NULL;
+
+                        q = readlink_and_make_absolute(path, &dest);
+
+                        if (q < 0 && errno != ENOENT) {
+                                free(path);
+
+                                if (r == 0)
+                                        r = q;
+
+                                continue;
+                        }
+
+                        if (q >= 0 && path_equal(dest, *i)) {
+                                free(dest);
+                                free(path);
+                                continue;
+                        }
+
                         free(dest);
                         free(dest);
-                        return -EEXIST;
+
+                        if (force) {
+                                unlink(path);
+
+                                if (symlink(*i, path) >= 0) {
+
+                                        add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
+                                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);
+
+                                        free(path);
+                                        continue;
+                                }
+                        }
+
+                        if (r == 0)
+                                r = -EEXIST;
+                } else {
+                        if (r == 0)
+                                r = -errno;
+                }
+
+                free(path);
+        }
+
+                finish:
+        lookup_paths_free(&paths);
+        free(config_path);
+
+        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);
+}
+
+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(InstallInfo *i) {
+        assert(i);
+
+        free(i->name);
+        free(i->path);
+        strv_free(i->aliases);
+        strv_free(i->wanted_by);
+        free(i);
+}
+
+static void install_info_hashmap_free(Hashmap *m) {
+        InstallInfo *i;
+
+        if (!m)
+                return;
+
+        while ((i = hashmap_steal_first(m)))
+                install_info_free(i);
+
+        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) {
+        InstallInfo *i = NULL;
+        int r;
+
+        assert(c);
+        assert(name || path);
+
+        if (!name)
+                name = file_name_from_path(path);
+
+        if (!unit_name_is_valid_no_type(name, true))
+                return -EINVAL;
+
+        if (hashmap_get(c->have_installed, name) ||
+            hashmap_get(c->will_install, name))
+                return 0;
+
+        r = hashmap_ensure_allocated(&c->will_install, string_hash_func, string_compare_func);
+        if (r < 0)
+                return r;
+
+        i = new0(InstallInfo, 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 = 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(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        char *w;
+        size_t l;
+        char *state;
+        InstallContext *c = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+                char *n;
+                int r;
+
+                n = strndup(w, l);
+                if (!n)
+                        return -ENOMEM;
+
+                r = install_info_add(c, n, NULL);
+                if (r < 0) {
+                        free(n);
+                        return r;
+                }
+
+                free(n);
+        }
+
+        return 0;
+}
+
+static int unit_file_load(
+                InstallContext *c,
+                InstallInfo *info,
+                const char *path,
+                bool allow_symlink) {
+
+        const ConfigTableItem items[] = {
+                { "Install", "Alias",    config_parse_strv, 0, &info->aliases   },
+                { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by },
+                { "Install", "Also",     config_parse_also, 0, c                },
+                { NULL, NULL, NULL, 0, NULL }
+        };
+
+        int fd;
+        FILE *f;
+        int r;
+
+        assert(c);
+        assert(info);
+        assert(path);
+
+        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|(allow_symlink ? 0 : O_NOFOLLOW));
+        if (fd < 0)
+                return -errno;
+
+        f = fdopen(fd, "re");
+        if (!f) {
+                close_nointr_nofail(fd);
+                return -ENOMEM;
+        }
+
+        r = config_parse(path, f, NULL, config_item_table_lookup, (void*) items, true, info);
+        fclose(f);
+        if (r < 0)
+                return r;
+
+        return strv_length(info->aliases) + strv_length(info->wanted_by);
+}
+
+static int unit_file_search(
+                InstallContext *c,
+                InstallInfo *info,
+                LookupPaths *paths,
+                const char *root_dir,
+                bool allow_symlink) {
+
+        char **p;
+        int r;
+
+        assert(c);
+        assert(info);
+        assert(paths);
+
+        if (info->path)
+                return unit_file_load(c, info, info->path, allow_symlink);
+
+        assert(info->name);
+
+        STRV_FOREACH(p, paths->unit_path) {
+                char *path = NULL;
+
+                if (isempty(root_dir))
+                        asprintf(&path, "%s/%s", *p, info->name);
+                else
+                        asprintf(&path, "%s/%s/%s", root_dir, *p, info->name);
+
+                if (!path)
+                        return -ENOMEM;
+
+                r = unit_file_load(c, info, path, allow_symlink);
+
+                if (r >= 0)
+                        info->path = path;
+                else
+                        free(path);
+
+                if (r != -ENOENT && r != -ELOOP)
+                        return r;
+        }
+
+        return -ENOENT;
+}
+
+static int unit_file_can_install(
+                LookupPaths *paths,
+                const char *root_dir,
+                const char *name,
+                bool allow_symlink) {
+
+        InstallContext c;
+        InstallInfo *i;
+        int r;
+
+        assert(paths);
+        assert(name);
+
+        zero(c);
+
+        r = install_info_add_auto(&c, name);
+        if (r < 0)
+                return r;
+
+        assert_se(i = hashmap_first(c.will_install));
+
+        r = unit_file_search(&c, i, paths, root_dir, allow_symlink);
+
+        if (r >= 0)
+                r = strv_length(i->aliases) + strv_length(i->wanted_by);
+
+        install_context_done(&c);
+
+        return r;
+}
+
+static int create_symlink(
+                const char *old_path,
+                const char *new_path,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        char *dest;
+        int r;
+
+        assert(old_path);
+        assert(new_path);
+
+        mkdir_parents(new_path, 0755);
+
+        if (symlink(old_path, new_path) >= 0) {
+                add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
+                return 0;
+        }
+
+        if (errno != EEXIST)
+                return -errno;
+
+        r = readlink_and_make_absolute(new_path, &dest);
+        if (r < 0)
+                return r;
+
+        if (path_equal(dest, old_path)) {
+                free(dest);
+                return 0;
+        }
+
+        free(dest);
+
+        if (force)
+                return -EEXIST;
+
+        unlink(new_path);
+
+        if (symlink(old_path, new_path) >= 0) {
+                add_file_change(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL);
+                add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
+                return 0;
+        }
+
+        return -errno;
+}
+
+static int install_info_symlink_alias(
+                InstallInfo *i,
+                const char *config_path,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        char **s;
+        int r = 0, q;
+
+        assert(i);
+        assert(config_path);
+
+        STRV_FOREACH(s, i->aliases) {
+                char *alias_path;
+
+                alias_path = path_make_absolute(*s, config_path);
+
+                if (!alias_path)
+                        return -ENOMEM;
+
+                q = create_symlink(i->path, alias_path, force, changes, n_changes);
+                free(alias_path);
+
+                if (r == 0)
+                        r = q;
+        }
+
+        return r;
+}
+
+static int install_info_symlink_wants(
+                InstallInfo *i,
+                const char *config_path,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        char **s;
+        int r = 0, q;
+
+        assert(i);
+        assert(config_path);
+
+        STRV_FOREACH(s, i->wanted_by) {
+                char *path;
+
+                if (!unit_name_is_valid_no_type(*s, true)) {
+                        r = -EINVAL;
+                        continue;
                 }
 
                 }
 
-                free(dest);
-                unlink(new_path);
+                if (asprintf(&path, "%s/%s.wants/%s", config_path, *s, i->name) < 0)
+                        return -ENOMEM;
+
+                q = create_symlink(i->path, path, force, changes, n_changes);
+                free(path);
+
+                if (r == 0)
+                        r = q;
+        }
+
+        return r;
+}
+
+static int install_info_symlink_link(
+                InstallInfo *i,
+                LookupPaths *paths,
+                const char *config_path,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        int r;
+        char *path;
+
+        assert(i);
+        assert(paths);
+        assert(config_path);
+        assert(i->path);
+
+        r = in_search_path(i->path, paths->unit_path);
+        if (r != 0)
+                return r;
+
+        if (asprintf(&path, "%s/%s", config_path, i->name) < 0)
+                return -ENOMEM;
+
+        r = create_symlink(i->path, path, force, changes, n_changes);
+        free(path);
+
+        return r;
+}
+
+static int install_info_apply(
+                InstallInfo *i,
+                LookupPaths *paths,
+                const char *config_path,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        int r, q;
+
+        assert(i);
+        assert(paths);
+        assert(config_path);
+
+        r = install_info_symlink_alias(i, config_path, force, changes, n_changes);
+
+        q = install_info_symlink_wants(i, config_path, force, changes, n_changes);
+        if (r == 0)
+                r = q;
+
+        q = install_info_symlink_link(i, paths, config_path, force, changes, n_changes);
+        if (r == 0)
+                r = q;
+
+        return r;
+}
+
+static int install_context_apply(
+                InstallContext *c,
+                LookupPaths *paths,
+                const char *config_path,
+                const char *root_dir,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        InstallInfo *i;
+        int r = 0, q;
+
+        assert(c);
+        assert(paths);
+        assert(config_path);
+
+        while ((i = hashmap_first(c->will_install))) {
+
+                q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func);
+                if (q < 0)
+                        return q;
+
+                assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);
+
+                q = unit_file_search(c, i, paths, root_dir, false);
+                if (q < 0) {
+                        if (r >= 0)
+                                r = q;
 
 
-                if (arg_verbose)
-                        log_info("ln -s '%s' '%s'", old_path, new_path);
+                        return r;
+                } else if (r >= 0)
+                        r += q;
 
 
-                if (symlink(old_path, new_path) >= 0)
-                        return 0;
+                q = install_info_apply(i, paths, config_path, force, changes, n_changes);
+                if (r >= 0 && q < 0)
+                        r = q;
+        }
 
 
-                log_error("Cannot link %s to %s: %m", old_path, new_path);
-                return -errno;
+        return r;
+}
 
 
-        } else if (arg_action == ACTION_DISABLE) {
-                char *dest;
+static int install_context_mark_for_removal(
+                InstallContext *c,
+                LookupPaths *paths,
+                Set **remove_symlinks_to,
+                const char *config_path,
+                const char *root_dir) {
 
 
-                if ((r = mark_symlink_for_removal(old_path)) < 0)
-                        return r;
+        InstallInfo *i;
+        int r = 0, q;
 
 
-                if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
-                        if (errno == ENOENT)
-                                return 0;
+        assert(c);
+        assert(paths);
+        assert(config_path);
 
 
-                        if (errno == EINVAL) {
-                                log_warning("File %s not a symlink, ignoring.", old_path);
-                                return 0;
-                        }
+        /* Marks all items for removal */
 
 
-                        log_error("readlink() failed: %s", strerror(-r));
-                        return r;
-                }
+        while ((i = hashmap_first(c->will_install))) {
 
 
-                if (!streq(dest, old_path)) {
-                        log_warning("File %s not a symlink to %s but points to %s, ignoring.", new_path, old_path, dest);
-                        free(dest);
-                        return 0;
-                }
+                q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func);
+                if (q < 0)
+                        return q;
 
 
+                assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);
 
 
-                free(dest);
+                q = unit_file_search(c, i, paths, root_dir, false);
+                if (q < 0) {
+                        if (r >= 0)
+                                r = q;
 
 
-                if ((r = mark_symlink_for_removal(new_path)) < 0)
                         return r;
                         return r;
+                } else if (r >= 0)
+                        r += q;
 
 
-                if (arg_verbose)
-                        log_info("rm '%s'", new_path);
+                q = mark_symlink_for_removal(remove_symlinks_to, i->name);
+                if (r >= 0 && q < 0)
+                        r = q;
+        }
 
 
-                if (unlink(new_path) >= 0)
-                        return 0;
+        return r;
+}
 
 
-                log_error("Cannot unlink %s: %m", new_path);
-                return -errno;
+int unit_file_enable(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
 
 
-        } else if (arg_action == ACTION_TEST || arg_action == ACTION_REALIZE) {
-                char *dest;
+        LookupPaths paths;
+        InstallContext c;
+        char **i, *config_path = NULL;
+        int r;
 
 
-                if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
 
 
-                        if (errno == ENOENT || errno == EINVAL)
-                                return 0;
+        zero(paths);
+        zero(c);
 
 
-                        log_error("readlink() failed: %s", strerror(-r));
-                        return r;
-                }
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
 
 
-                if (streq(dest, old_path)) {
-                        free(dest);
-                        return 1;
-                }
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
+                goto finish;
 
 
-                return 0;
+        STRV_FOREACH(i, files) {
+                r = install_info_add_auto(&c, *i);
+                if (r < 0)
+                        goto finish;
         }
 
         }
 
-        assert_not_reached("Unknown action.");
+        /* 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 hat any
+        installation data at all. */
+        r = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes);
+
+finish:
+        install_context_done(&c);
+        lookup_paths_free(&paths);
+        free(config_path);
+
+        return r;
 }
 
 }
 
-static int install_info_symlink_alias(InstallInfo *i, const char *config_path) {
-        char **s;
-        char *alias_path = NULL;
-        int r;
+int unit_file_disable(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                UnitFileChange **changes,
+                unsigned *n_changes) {
 
 
-        assert(i);
+        LookupPaths paths;
+        InstallContext c;
+        char **i, *config_path = NULL;
+        Set *remove_symlinks_to = NULL;
+        int r, q;
 
 
-        STRV_FOREACH(s, i->aliases) {
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
 
 
-                if (!unit_name_valid(*s)) {
-                        log_error("Invalid name %s.", *s);
-                        r = -EINVAL;
-                        goto finish;
-                }
+        zero(paths);
+        zero(c);
 
 
-                free(alias_path);
-                if (!(alias_path = path_make_absolute(*s, config_path))) {
-                        log_error("Out of memory");
-                        r = -ENOMEM;
-                        goto finish;
-                }
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
 
 
-                if ((r = create_symlink(i->path, alias_path)) != 0)
-                        goto finish;
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
+                goto finish;
 
 
-                if (arg_action == ACTION_DISABLE)
-                        rmdir_parents(alias_path, config_path);
+        STRV_FOREACH(i, files) {
+                r = install_info_add_auto(&c, *i);
+                if (r < 0)
+                        goto finish;
         }
 
         }
 
-        r = 0;
+        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);
+        if (r == 0)
+                r = q;
 
 finish:
 
 finish:
-        free(alias_path);
+        install_context_done(&c);
+        lookup_paths_free(&paths);
+        set_free_free(remove_symlinks_to);
+        free(config_path);
 
         return r;
 }
 
 
         return r;
 }
 
-static int install_info_symlink_wants(InstallInfo *i, const char *config_path) {
-        char **s;
-        char *alias_path = NULL;
-        int r;
+int unit_file_reenable(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
 
 
-        assert(i);
+        LookupPaths paths;
+        InstallContext c;
+        char **i, *config_path = NULL;
+        Set *remove_symlinks_to = NULL;
+        int r, q;
 
 
-        STRV_FOREACH(s, i->wanted_by) {
-                if (!unit_name_valid(*s)) {
-                        log_error("Invalid name %s.", *s);
-                        r = -EINVAL;
-                        goto finish;
-                }
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
 
 
-                free(alias_path);
-                alias_path = NULL;
+        zero(paths);
+        zero(c);
 
 
-                if (asprintf(&alias_path, "%s/%s.wants/%s", config_path, *s, i->name) < 0) {
-                        log_error("Out of memory");
-                        r = -ENOMEM;
-                        goto finish;
-                }
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
+
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
+                goto finish;
 
 
-                if ((r = create_symlink(i->path, alias_path)) != 0)
+        STRV_FOREACH(i, files) {
+                r = mark_symlink_for_removal(&remove_symlinks_to, *i);
+                if (r < 0)
                         goto finish;
 
                         goto finish;
 
-                if (arg_action == ACTION_DISABLE)
-                        rmdir_parents(alias_path, config_path);
+                r = install_info_add_auto(&c, *i);
+                if (r < 0)
+                        goto finish;
         }
 
         }
 
-        r = 0;
+        r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
+
+        /* Returns number of symlinks that where supposed to be installed. */
+        q = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes);
+        if (r == 0)
+                r = q;
 
 finish:
 
 finish:
-        free(alias_path);
+        lookup_paths_free(&paths);
+        install_context_done(&c);
+        set_free_free(remove_symlinks_to);
+        free(config_path);
 
         return r;
 }
 
 
         return r;
 }
 
-static int install_info_apply(LookupPaths *paths, InstallInfo *i, const char *config_path) {
+UnitFileState unit_file_get_state(
+                UnitFileScope scope,
+                const char *root_dir,
+                const char *name) {
 
 
-        const ConfigItem items[] = {
-                { "Alias",    config_parse_strv, &i->aliases,   "Install" },
-                { "WantedBy", config_parse_strv, &i->wanted_by, "Install" },
-                { "Also",     config_parse_also, NULL,          "Install" },
+        LookupPaths paths;
+        UnitFileState state = _UNIT_FILE_STATE_INVALID;
+        char **i, *path = NULL;
+        int r;
 
 
-                { NULL, NULL, NULL, NULL }
-        };
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(name);
 
 
-        char **p;
-        char *filename = NULL;
-        FILE *f = NULL;
-        int r;
+        zero(paths);
 
 
-        assert(paths);
-        assert(i);
+        if (root_dir && scope != UNIT_FILE_SYSTEM)
+                return -EINVAL;
 
 
-        STRV_FOREACH(p, paths->unit_path) {
-                int fd;
+        if (!unit_name_is_valid_no_type(name, true))
+                return -EINVAL;
 
 
-                if (!(filename = path_make_absolute(i->name, *p))) {
-                        log_error("Out of memory");
-                        return -ENOMEM;
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, paths.unit_path) {
+                struct stat st;
+
+                free(path);
+                path = NULL;
+
+                if (root_dir)
+                        asprintf(&path, "%s/%s/%s", root_dir, *i, name);
+                else
+                        asprintf(&path, "%s/%s", *i, name);
+
+                if (!path) {
+                        r = -ENOMEM;
+                        goto finish;
                 }
 
                 }
 
-                /* Ensure that we don't follow symlinks */
-                if ((fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOCTTY)) >= 0)
-                        if ((f = fdopen(fd, "re")))
-                                break;
+                if (lstat(path, &st) < 0) {
+                        if (errno == ENOENT)
+                                continue;
 
 
-                if (errno == ELOOP) {
-                        log_error("Refusing to operate on symlinks, please pass unit names or absolute paths to unit files.");
-                        free(filename);
-                        return -errno;
+                        r = -errno;
+                        goto finish;
                 }
 
                 }
 
-                if (errno != ENOENT) {
-                        log_error("Failed to open %s: %m", filename);
-                        free(filename);
-                        return -errno;
+                if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
+                        r = -ENOENT;
+                        goto finish;
                 }
 
                 }
 
-                free(filename);
-                filename = NULL;
-        }
+                r = null_or_empty_path(path);
+                if (r < 0 && r != -ENOENT)
+                        goto finish;
+                else if (r > 0) {
+                        state = path_startswith(*i, "/run") ?
+                                UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
+                        r = 0;
+                        goto finish;
+                }
 
 
-        if (!f) {
-                log_error("Couldn't find %s.", i->name);
-                return -ENOENT;
+                r = find_symlinks_in_scope(scope, root_dir, name, &state);
+                if (r < 0) {
+                        goto finish;
+                } else if (r > 0) {
+                        r = 0;
+                        goto finish;
+                }
+
+                r = unit_file_can_install(&paths, root_dir, path, true);
+                if (r < 0 && errno != -ENOENT)
+                        goto finish;
+                else if (r > 0) {
+                        state = UNIT_FILE_DISABLED;
+                        r = 0;
+                        goto finish;
+                } else if (r == 0) {
+                        state = UNIT_FILE_STATIC;
+                        r = 0;
+                        goto finish;
+                }
         }
 
         }
 
-        i->path = filename;
+finish:
+        lookup_paths_free(&paths);
+        free(path);
 
 
-        if ((r = config_parse(filename, f, NULL, items, true, i)) < 0) {
-                fclose(f);
-                return r;
-        }
+        return r < 0 ? r : state;
+}
 
 
-        fclose(f);
+int unit_file_query_preset(UnitFileScope scope, const char *name) {
+        char **files, **i;
+        int r;
 
 
-        if ((r = install_info_symlink_alias(i, config_path)) != 0)
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(name);
+
+        if (scope == UNIT_FILE_SYSTEM)
+                r = conf_files_list(&files, ".preset",
+                                    "/etc/systemd/system.preset",
+                                    "/usr/local/lib/systemd/system.preset",
+                                    "/usr/lib/systemd/system.preset",
+                                    "/lib/systemd/system.preset",
+                                    NULL);
+        else if (scope == UNIT_FILE_GLOBAL)
+                r = conf_files_list(&files, ".preset",
+                                    "/etc/systemd/user.preset",
+                                    "/usr/local/lib/systemd/user.preset",
+                                    "/usr/lib/systemd/user.preset",
+                                    NULL);
+        else
+                return 1;
+
+        if (r < 0)
                 return r;
 
                 return r;
 
-        if ((r = install_info_symlink_wants(i, config_path)) != 0)
-                return r;
+        STRV_FOREACH(i, files) {
+                FILE *f;
 
 
-        if ((r = mark_symlink_for_removal(filename)) < 0)
-                return r;
+                f = fopen(*i, "re");
+                if (!f) {
+                        if (errno == ENOENT)
+                                continue;
 
 
-        if ((r = remove_marked_symlinks(config_path)) < 0)
-                return r;
+                        r = -errno;
+                        goto finish;
+                }
 
 
-        return 0;
-}
+                for (;;) {
+                        char line[LINE_MAX], *l;
 
 
-static char *get_config_path(void) {
+                        if (!fgets(line, sizeof(line), f))
+                                break;
 
 
-        switch (arg_where) {
+                        l = strstrip(line);
+                        if (!*l)
+                                continue;
 
 
-        case WHERE_SYSTEM:
-                return strdup(SYSTEM_CONFIG_UNIT_PATH);
+                        if (strchr(COMMENTS, *l))
+                                continue;
 
 
-        case WHERE_GLOBAL:
-                return strdup(SESSION_CONFIG_UNIT_PATH);
+                        if (first_word(l, "enable")) {
+                                l += 6;
+                                l += strspn(l, WHITESPACE);
 
 
-        case WHERE_SESSION: {
-                char *p;
+                                if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
+                                        r = 1;
+                                        fclose(f);
+                                        goto finish;
+                                }
+                        } else if (first_word(l, "disable")) {
+                                l += 7;
+                                l += strspn(l, WHITESPACE);
 
 
-                if (session_config_home(&p) < 0)
-                        return NULL;
+                                if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
+                                        r = 0;
+                                        fclose(f);
+                                        goto finish;
+                                }
+                        } else
+                                log_debug("Couldn't parse line '%s'", l);
+                }
 
 
-                return p;
+                fclose(f);
         }
 
         }
 
-        default:
-                assert_not_reached("Unknown config path.");
-        }
-}
+        /* Default is "enable" */
+        r = 1;
 
 
-static int do_realize(bool enabled) {
-        DBusConnection *bus = NULL;
-        DBusError error;
-        int r, q;
-        Iterator i;
-        InstallInfo *j;
+finish:
+        strv_free(files);
 
 
-        dbus_error_init(&error);
+        return r;
+}
 
 
-        if (arg_realize == REALIZE_NO)
-                return 0;
+int unit_file_preset(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
 
 
-        if (arg_where == WHERE_GLOBAL) {
-                log_warning("Warning: --realize has no effect with --global.");
-                return 0;
-        }
+        LookupPaths paths;
+        InstallContext plus, minus;
+        char **i, *config_path = NULL;
+        Set *remove_symlinks_to = NULL;
+        int r, q;
 
 
-        if (arg_action == ACTION_TEST) {
-                log_warning("Warning: --realize has no effect with test.");
-                return 0;
-        }
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
 
 
-        if (arg_where == WHERE_SYSTEM && sd_booted() <= 0) {
-                log_info("systemd is not running, --realize has no effect.");
-                return 0;
-        }
+        zero(paths);
+        zero(plus);
+        zero(minus);
 
 
-        if (arg_where == WHERE_SYSTEM && running_in_chroot() > 0) {
-                log_info("Running in a chroot() environment, --realize has no effect.");
-                return 0;
-        }
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
 
 
-        if ((r = bus_connect(arg_where == WHERE_SESSION ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, NULL, &error)) < 0) {
-                log_error("Failed to get D-Bus connection: %s", error.message);
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
                 goto finish;
                 goto finish;
-        }
 
 
-        r = 0;
+        STRV_FOREACH(i, files) {
+
+                if (!unit_name_is_valid_no_type(*i, true)) {
+                        r = -EINVAL;
+                        goto finish;
+                }
 
 
-        if (arg_action == ACTION_ENABLE || arg_action == ACTION_REALIZE)
-                if ((r = daemon_reload(bus)) < 0)
+                r = unit_file_query_preset(scope, *i);
+                if (r < 0)
                         goto finish;
 
                         goto finish;
 
-        if (arg_realize != REALIZE_RELOAD) {
-                HASHMAP_FOREACH(j, have_installed, i)
-                        if ((q = install_info_run(bus, j, enabled)) < 0)
-                                r = q;
+                if (r)
+                        r = install_info_add_auto(&plus, *i);
+                else
+                        r = install_info_add_auto(&minus, *i);
+
+                if (r < 0)
+                        goto finish;
         }
 
         }
 
-        if (arg_action == ACTION_DISABLE)
-                if ((q = daemon_reload(bus)) < 0)
-                        r = q;
+        r = install_context_mark_for_removal(&minus, &paths, &remove_symlinks_to, config_path, root_dir);
 
 
-finish:
-        if (bus) {
-                dbus_connection_close(bus);
-                dbus_connection_unref(bus);
-        }
+        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
+        if (r == 0)
+                r = q;
 
 
-        dbus_error_free(&error);
+        /* 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;
+
+finish:
+        lookup_paths_free(&paths);
+        install_context_done(&plus);
+        install_context_done(&minus);
+        set_free_free(remove_symlinks_to);
+        free(config_path);
 
 
-        dbus_shutdown();
         return r;
 }
 
         return r;
 }
 
-int main(int argc, char *argv[]) {
-        int r, retval = 1, j;
+int unit_file_get_list(
+                UnitFileScope scope,
+                const char *root_dir,
+                Hashmap *h) {
+
         LookupPaths paths;
         LookupPaths paths;
-        InstallInfo *i;
-        char *config_path = NULL;
+        char **i, *buf = NULL;
+        DIR *d = NULL;
+        int r;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(h);
 
         zero(paths);
 
 
         zero(paths);
 
-        log_parse_environment();
+        if (root_dir && scope != UNIT_FILE_SYSTEM)
+                return -EINVAL;
 
 
-        if ((r = parse_argv(argc, argv)) < 0)
-                goto finish;
-        else if (r == 0) {
-                retval = 0;
-                goto finish;
-        }
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
 
 
-        if ((r = lookup_paths_init(&paths, arg_where == WHERE_SYSTEM ? MANAGER_SYSTEM : MANAGER_SESSION)) < 0) {
-                log_error("Failed to determine lookup paths: %s", strerror(-r));
-                goto finish;
-        }
+        STRV_FOREACH(i, paths.unit_path) {
+                struct dirent buffer, *de;
+                const char *units_dir;
 
 
-        if (!(config_path = get_config_path())) {
-                log_error("Failed to determine config path");
-                goto finish;
-        }
+                free(buf);
+                buf = NULL;
+
+                if (root_dir) {
+                        if (asprintf(&buf, "%s/%s", root_dir, *i) < 0) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                        units_dir = buf;
+                } else
+                        units_dir = *i;
 
 
-        will_install = hashmap_new(string_hash_func, string_compare_func);
-        have_installed = hashmap_new(string_hash_func, string_compare_func);
+                if (d)
+                        closedir(d);
 
 
-        if (!will_install || !have_installed) {
-                log_error("Failed to allocate unit sets.");
-                goto finish;
-        }
+                d = opendir(units_dir);
+                if (!d) {
+                        if (errno == ENOENT)
+                                continue;
 
 
-        if (arg_all)
-                if (!(remove_symlinks_to = set_new(string_hash_func, string_compare_func))) {
-                        log_error("Failed to allocate symlink sets.");
+                        r = -errno;
                         goto finish;
                 }
 
                         goto finish;
                 }
 
-        for (j = optind; j < argc; j++)
-                if ((r = install_info_add(argv[j])) < 0)
-                        goto finish;
+                for (;;) {
+                        UnitFileList *f;
+
+                        r = readdir_r(d, &buffer, &de);
+                        if (r != 0) {
+                                r = -r;
+                                goto finish;
+                        }
+
+                        if (!de)
+                                break;
+
+                        if (ignore_file(de->d_name))
+                                continue;
+
+                        if (!unit_name_is_valid_no_type(de->d_name, true))
+                                continue;
+
+                        if (hashmap_get(h, de->d_name))
+                                continue;
 
 
+                        r = dirent_ensure_type(d, de);
+                        if (r < 0) {
+                                if (errno == ENOENT)
+                                        continue;
 
 
-        while ((i = hashmap_first(will_install))) {
-                assert_se(hashmap_move_one(have_installed, will_install, i->name) == 0);
+                                goto finish;
+                        }
 
 
-                if ((r = install_info_apply(&paths, i, config_path)) != 0) {
+                        if (de->d_type != DT_LNK && de->d_type != DT_REG)
+                                continue;
 
 
-                        if (r < 0)
+                        f = new0(UnitFileList, 1);
+                        if (!f) {
+                                r = -ENOMEM;
                                 goto finish;
                                 goto finish;
+                        }
 
 
-                        /* In test mode and found something */
-                        retval = 0;
-                        break;
-                }
-        }
+                        f->path = path_make_absolute(de->d_name, units_dir);
+                        if (!f->path) {
+                                free(f);
+                                r = -ENOMEM;
+                                goto finish;
+                        }
 
 
-        if (do_realize(!retval) < 0)
-                goto finish;
+                        r = null_or_empty_path(f->path);
+                        if (r < 0 && r != -ENOENT) {
+                                free(f->path);
+                                free(f);
+                                goto finish;
+                        } else if (r > 0) {
+                                f->state =
+                                        path_startswith(*i, "/run") ?
+                                        UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
+                                goto found;
+                        }
 
 
-finish:
-        install_info_hashmap_free(will_install);
-        install_info_hashmap_free(have_installed);
+                        r = find_symlinks_in_scope(scope, root_dir, de->d_name, &f->state);
+                        if (r < 0) {
+                                free(f->path);
+                                free(f);
+                                goto finish;
+                        } else if (r > 0)
+                                goto found;
 
 
-        set_free_free(remove_symlinks_to);
+                        r = unit_file_can_install(&paths, root_dir, f->path, true);
+                        if (r < 0) {
+                                free(f->path);
+                                free(f);
+                                goto finish;
+                        } else if (r > 0) {
+                                f->state = UNIT_FILE_DISABLED;
+                                goto found;
+                        } else {
+                                f->state = UNIT_FILE_STATIC;
+                                goto found;
+                        }
+
+                        free(f->path);
+                        free(f);
+                        continue;
+
+                found:
+                        r = hashmap_put(h, file_name_from_path(f->path), f);
+                        if (r < 0) {
+                                free(f->path);
+                                free(f);
+                                goto finish;
+                        }
+                }
+        }
 
 
+finish:
         lookup_paths_free(&paths);
         lookup_paths_free(&paths);
+        free(buf);
 
 
-        free(config_path);
+        if (d)
+                closedir(d);
 
 
-        return retval;
+        return r;
 }
 }
+
+static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = {
+        [UNIT_FILE_ENABLED] = "enabled",
+        [UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtie",
+        [UNIT_FILE_LINKED] = "linked",
+        [UNIT_FILE_LINKED_RUNTIME] = "linked-runtime",
+        [UNIT_FILE_MASKED] = "masked",
+        [UNIT_FILE_MASKED_RUNTIME] = "masked-runtime",
+        [UNIT_FILE_STATIC] = "static",
+        [UNIT_FILE_DISABLED] = "disabled"
+};
+
+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",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType);