803fb7
From 272378080241106862a9cca2e4e84857fe1aaf5c Mon Sep 17 00:00:00 2001
803fb7
From: Lennart Poettering <lennart@poettering.net>
803fb7
Date: Thu, 8 Oct 2015 22:31:56 +0200
803fb7
Subject: [PATCH] install: follow unit file symlinks in /usr, but not /etc when
803fb7
 looking for [Install] data
803fb7
803fb7
Some distributions use alias unit files via symlinks in /usr to cover
803fb7
for legacy service names. With this change we'll allow "systemctl
803fb7
enable" on such aliases.
803fb7
803fb7
Previously, our rule was that symlinks are user configuration that
803fb7
"systemctl enable" + "systemctl disable" creates and removes, while unit
803fb7
files is where the instructions to do so are store. As a result of the
803fb7
rule we'd never read install information through symlinks, since that
803fb7
would mix enablement state with installation instructions.
803fb7
803fb7
Now, the new rule is that only symlinks inside of /etc are
803fb7
configuration. Unit files, and symlinks in /usr are now valid for
803fb7
installation instructions.
803fb7
803fb7
This patch is quite a rework of the whole install logic, and makes the
803fb7
following addional changes:
803fb7
803fb7
- Adds a complete test "test-instal-root" that tests the install logic
803fb7
  pretty comprehensively.
803fb7
803fb7
- Never uses canonicalize_file_name(), because that's incompatible with
803fb7
  operation relative to a specific root directory.
803fb7
803fb7
- unit_file_get_state() is reworked to return a proper error, and
803fb7
  returns the state in a call-by-ref parameter. This cleans up confusion
803fb7
  between the enum type and errno-like errors.
803fb7
803fb7
- The new logic puts a limit on how long to follow unit file symlinks:
803fb7
  it will do so only for 64 steps at max.
803fb7
803fb7
- The InstallContext object's fields are renamed to will_process and
803fb7
  has_processed (will_install and has_installed) since they are also
803fb7
  used for deinstallation and all kinds of other operations.
803fb7
803fb7
- The root directory is always verified before use.
803fb7
803fb7
- install.c is reordered to place the exported functions together.
803fb7
803fb7
- Stricter rules are followed when traversing symlinks: the unit suffix
803fb7
  must say identical, and it's not allowed to link between regular units
803fb7
  and templated units.
803fb7
803fb7
- Various modernizations
803fb7
803fb7
- The "invalid" unit file state has been renamed to "bad", in order to
803fb7
  avoid confusion between UNIT_FILE_INVALID and
803fb7
  _UNIT_FILE_STATE_INVALID. Given that the state should normally not be
803fb7
  seen and is not documented this should not be a problematic change.
803fb7
  The new name is now documented however.
803fb7
803fb7
Fixes #1375, #1718, #1706
803fb7
803fb7
Cherry-picked from: 79413b673b45adc98dfeaec882bbdda2343cb2f9
803fb7
Resolves: #1159308
803fb7
---
803fb7
 Makefile.am                           |   12 +-
803fb7
 man/systemctl.xml                     |   14 +-
803fb7
 src/core/dbus-manager.c               |    6 +-
803fb7
 src/core/dbus-unit.c                  |    4 +-
803fb7
 src/core/load-fragment.c              |    2 +-
803fb7
 src/core/manager.c                    |    2 +-
803fb7
 src/core/snapshot.c                   |    2 +-
803fb7
 src/core/unit.c                       |   19 +-
803fb7
 src/dbus1-generator/dbus1-generator.c |    2 +-
803fb7
 src/firstboot/firstboot.c             |   18 +-
803fb7
 src/shared/cgroup-util.c              |    6 +-
803fb7
 src/shared/install.c                  | 1909 +++++++++++++++++++--------------
803fb7
 src/shared/install.h                  |   46 +-
803fb7
 src/shared/path-util.c                |   34 +
803fb7
 src/shared/path-util.h                |   27 +
803fb7
 src/shared/unit-name.c                |   32 +-
803fb7
 src/shared/unit-name.h                |   14 +-
803fb7
 src/shared/util.c                     |   21 +
803fb7
 src/shared/util.h                     |    1 +
803fb7
 src/systemctl/systemctl.c             |    6 +-
803fb7
 src/sysv-generator/sysv-generator.c   |   10 +-
803fb7
 src/test/test-install-root.c          |  663 ++++++++++++
803fb7
 src/test/test-install.c               |   71 +-
803fb7
 23 files changed, 1998 insertions(+), 923 deletions(-)
803fb7
 create mode 100644 src/test/test-install-root.c
803fb7
803fb7
diff --git a/Makefile.am b/Makefile.am
803fb7
index 3af720bda..3a09e0a62 100644
803fb7
--- a/Makefile.am
803fb7
+++ b/Makefile.am
803fb7
@@ -1427,7 +1427,8 @@ tests += \
803fb7
 	test-copy \
803fb7
 	test-cap-list \
803fb7
 	test-sigbus \
803fb7
-	test-verbs
803fb7
+	test-verbs \
803fb7
+	test-install-root
803fb7
 
803fb7
 EXTRA_DIST += \
803fb7
 	test/a.service \
803fb7
@@ -1721,6 +1722,15 @@ test_verbs_SOURCES = \
803fb7
 test_verbs_LDADD = \
803fb7
 	libsystemd-shared.la
803fb7
 
803fb7
+test_install_root_SOURCES = \
803fb7
+	src/test/test-install-root.c
803fb7
+
803fb7
+test_install_root_LDADD = \
803fb7
+	libsystemd-units.la \
803fb7
+	libsystemd-label.la \
803fb7
+	libsystemd-internal.la \
803fb7
+	libsystemd-shared.la
803fb7
+
803fb7
 test_namespace_LDADD = \
803fb7
 	libsystemd-core.la
803fb7
 
803fb7
diff --git a/man/systemctl.xml b/man/systemctl.xml
803fb7
index c6f584278..2d0678d18 100644
803fb7
--- a/man/systemctl.xml
803fb7
+++ b/man/systemctl.xml
803fb7
@@ -905,10 +905,11 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service
803fb7
           <term><command>list-unit-files <optional><replaceable>PATTERN...</replaceable></optional></command></term>
803fb7
 
803fb7
           <listitem>
803fb7
-            <para>List installed unit files. If one or more
803fb7
-            <replaceable>PATTERN</replaceable>s are specified, only
803fb7
-            units whose filename (just the last component of the path)
803fb7
-            matches one of them are shown.</para>
803fb7
+            <para>List installed unit files and their enablement state
803fb7
+            (as reported by <command>is-enabled</command>). If one or
803fb7
+            more <replaceable>PATTERN</replaceable>s are specified,
803fb7
+            only units whose filename (just the last component of the
803fb7
+            path) matches one of them are shown.</para>
803fb7
           </listitem>
803fb7
         </varlistentry>
803fb7
 
803fb7
@@ -1108,6 +1109,11 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service
803fb7
                     <entry>Unit file is not enabled.</entry>
803fb7
                     <entry>1</entry>
803fb7
                   </row>
803fb7
+                  <row>
803fb7
+                    <entry><literal>bad</literal></entry>
803fb7
+                    <entry>Unit file is invalid or another error occured. Note that <command>is-enabled</command> wil not actually return this state, but print an error message instead. However the unit file listing printed by <command>list-unit-files</command> might show it.</entry>
803fb7
+                    <entry>> 0</entry>
803fb7
+                  </row>
803fb7
                 
803fb7
               </tgroup>
803fb7
             
803fb7
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
803fb7
index 1ec350e03..faa124d92 100644
803fb7
--- a/src/core/dbus-manager.c
803fb7
+++ b/src/core/dbus-manager.c
803fb7
@@ -1530,9 +1530,9 @@ static int method_get_unit_file_state(sd_bus *bus, sd_bus_message *message, void
803fb7
 
803fb7
         scope = m->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER;
803fb7
 
803fb7
-        state = unit_file_get_state(scope, NULL, name);
803fb7
-        if (state < 0)
803fb7
-                return state;
803fb7
+        r = unit_file_get_state(scope, NULL, name, &state);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
 
803fb7
         return sd_bus_reply_method_return(message, "s", unit_file_state_to_string(state));
803fb7
 }
803fb7
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
803fb7
index 625d21ab8..227915efc 100644
803fb7
--- a/src/core/dbus-unit.c
803fb7
+++ b/src/core/dbus-unit.c
803fb7
@@ -919,7 +919,7 @@ static int bus_unit_set_transient_property(
803fb7
                 if (r < 0)
803fb7
                         return r;
803fb7
 
803fb7
-                if (!unit_name_is_valid(s, TEMPLATE_INVALID) || !endswith(s, ".slice"))
803fb7
+                if (!unit_name_is_valid(s, UNIT_NAME_PLAIN) || !endswith(s, ".slice"))
803fb7
                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid slice name %s", s);
803fb7
 
803fb7
                 if (isempty(s)) {
803fb7
@@ -967,7 +967,7 @@ static int bus_unit_set_transient_property(
803fb7
                         return r;
803fb7
 
803fb7
                 while ((r = sd_bus_message_read(message, "s", &other)) > 0) {
803fb7
-                        if (!unit_name_is_valid(other, TEMPLATE_INVALID))
803fb7
+                        if (!unit_name_is_valid(other, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
803fb7
                                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name %s", other);
803fb7
 
803fb7
                         if (mode != UNIT_CHECK) {
803fb7
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
803fb7
index ec4cf4eef..70c09188a 100644
803fb7
--- a/src/core/load-fragment.c
803fb7
+++ b/src/core/load-fragment.c
803fb7
@@ -3409,7 +3409,7 @@ static int open_follow(char **filename, FILE **_f, Set *names, char **_final) {
803fb7
                  * unit name. */
803fb7
                 name = basename(*filename);
803fb7
 
803fb7
-                if (unit_name_is_valid(name, TEMPLATE_VALID)) {
803fb7
+                if (unit_name_is_valid(name, UNIT_NAME_ANY)) {
803fb7
 
803fb7
                         id = set_get(names, name);
803fb7
                         if (!id) {
803fb7
diff --git a/src/core/manager.c b/src/core/manager.c
803fb7
index 7483a96ec..bde17ce0b 100644
803fb7
--- a/src/core/manager.c
803fb7
+++ b/src/core/manager.c
803fb7
@@ -1328,7 +1328,7 @@ int manager_load_unit_prepare(
803fb7
 
803fb7
         t = unit_name_to_type(name);
803fb7
 
803fb7
-        if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid(name, TEMPLATE_INVALID))
803fb7
+        if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid(name, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
803fb7
                 return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is not valid.", name);
803fb7
 
803fb7
         ret = manager_get_unit(m, name);
803fb7
diff --git a/src/core/snapshot.c b/src/core/snapshot.c
803fb7
index b1d844877..f222ec232 100644
803fb7
--- a/src/core/snapshot.c
803fb7
+++ b/src/core/snapshot.c
803fb7
@@ -201,7 +201,7 @@ int snapshot_create(Manager *m, const char *name, bool cleanup, sd_bus_error *e,
803fb7
         assert(_s);
803fb7
 
803fb7
         if (name) {
803fb7
-                if (!unit_name_is_valid(name, TEMPLATE_INVALID))
803fb7
+                if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
803fb7
                         return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is not valid.", name);
803fb7
 
803fb7
                 if (unit_name_to_type(name) != UNIT_SNAPSHOT)
803fb7
diff --git a/src/core/unit.c b/src/core/unit.c
803fb7
index 4fb2fd300..db5aa987e 100644
803fb7
--- a/src/core/unit.c
803fb7
+++ b/src/core/unit.c
803fb7
@@ -158,7 +158,7 @@ int unit_add_name(Unit *u, const char *text) {
803fb7
         if (!s)
803fb7
                 return -ENOMEM;
803fb7
 
803fb7
-        if (!unit_name_is_valid(s, TEMPLATE_INVALID))
803fb7
+        if (!unit_name_is_valid(s, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
803fb7
                 return -EINVAL;
803fb7
 
803fb7
         assert_se((t = unit_name_to_type(s)) >= 0);
803fb7
@@ -3119,12 +3119,18 @@ int unit_following_set(Unit *u, Set **s) {
803fb7
 }
803fb7
 
803fb7
 UnitFileState unit_get_unit_file_state(Unit *u) {
803fb7
+        int r;
803fb7
+
803fb7
         assert(u);
803fb7
 
803fb7
-        if (u->unit_file_state < 0 && u->fragment_path)
803fb7
-                u->unit_file_state = unit_file_get_state(
803fb7
-                                u->manager->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER,
803fb7
-                                NULL, basename(u->fragment_path));
803fb7
+        if (u->unit_file_state < 0 && u->fragment_path) {
803fb7
+                r = unit_file_get_state(u->manager->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER,
803fb7
+                                        NULL,
803fb7
+                                        basename(u->fragment_path),
803fb7
+                                        &u->unit_file_state);
803fb7
+                if (r < 0)
803fb7
+                        u->unit_file_state = UNIT_FILE_BAD;
803fb7
+        }
803fb7
 
803fb7
         return u->unit_file_state;
803fb7
 }
803fb7
@@ -3135,7 +3141,8 @@ int unit_get_unit_file_preset(Unit *u) {
803fb7
         if (u->unit_file_preset < 0 && u->fragment_path)
803fb7
                 u->unit_file_preset = unit_file_query_preset(
803fb7
                                 u->manager->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER,
803fb7
-                                NULL, basename(u->fragment_path));
803fb7
+                                NULL,
803fb7
+                                basename(u->fragment_path));
803fb7
 
803fb7
         return u->unit_file_preset;
803fb7
 }
803fb7
diff --git a/src/dbus1-generator/dbus1-generator.c b/src/dbus1-generator/dbus1-generator.c
803fb7
index 2e08af2df..c909a4b1d 100644
803fb7
--- a/src/dbus1-generator/dbus1-generator.c
803fb7
+++ b/src/dbus1-generator/dbus1-generator.c
803fb7
@@ -188,7 +188,7 @@ static int add_dbus(const char *path, const char *fname, const char *type) {
803fb7
         }
803fb7
 
803fb7
         if (service) {
803fb7
-                if (!unit_name_is_valid(service, TEMPLATE_INVALID)) {
803fb7
+                if (!unit_name_is_valid(service, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) {
803fb7
                         log_warning("Unit name %s is not valid, ignoring.", service);
803fb7
                         return 0;
803fb7
                 }
803fb7
diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c
803fb7
index a37ca170f..b77c1d887 100644
803fb7
--- a/src/firstboot/firstboot.c
803fb7
+++ b/src/firstboot/firstboot.c
803fb7
@@ -50,8 +50,6 @@ static bool arg_copy_locale = false;
803fb7
 static bool arg_copy_timezone = false;
803fb7
 static bool arg_copy_root_password = false;
803fb7
 
803fb7
-#define prefix_roota(p) (arg_root ? (const char*) strjoina(arg_root, p) : (const char*) p)
803fb7
-
803fb7
 static void clear_string(char *x) {
803fb7
 
803fb7
         if (!x)
803fb7
@@ -85,13 +83,13 @@ static void print_welcome(void) {
803fb7
         if (done)
803fb7
                 return;
803fb7
 
803fb7
-        os_release = prefix_roota("/etc/os-release");
803fb7
+        os_release = prefix_roota(arg_root, "/etc/os-release");
803fb7
         r = parse_env_file(os_release, NEWLINE,
803fb7
                            "PRETTY_NAME", &pretty_name,
803fb7
                            NULL);
803fb7
         if (r == -ENOENT) {
803fb7
 
803fb7
-                os_release = prefix_roota("/usr/lib/os-release");
803fb7
+                os_release = prefix_roota(arg_root, "/usr/lib/os-release");
803fb7
                 r = parse_env_file(os_release, NEWLINE,
803fb7
                                    "PRETTY_NAME", &pretty_name,
803fb7
                                    NULL);
803fb7
@@ -249,7 +247,7 @@ static int process_locale(void) {
803fb7
         unsigned i = 0;
803fb7
         int r;
803fb7
 
803fb7
-        etc_localeconf = prefix_roota("/etc/locale.conf");
803fb7
+        etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
803fb7
         if (faccessat(AT_FDCWD, etc_localeconf, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
803fb7
                 return 0;
803fb7
 
803fb7
@@ -323,7 +321,7 @@ static int process_timezone(void) {
803fb7
         const char *etc_localtime, *e;
803fb7
         int r;
803fb7
 
803fb7
-        etc_localtime = prefix_roota("/etc/localtime");
803fb7
+        etc_localtime = prefix_roota(arg_root, "/etc/localtime");
803fb7
         if (faccessat(AT_FDCWD, etc_localtime, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
803fb7
                 return 0;
803fb7
 
803fb7
@@ -402,7 +400,7 @@ static int process_hostname(void) {
803fb7
         const char *etc_hostname;
803fb7
         int r;
803fb7
 
803fb7
-        etc_hostname = prefix_roota("/etc/hostname");
803fb7
+        etc_hostname = prefix_roota(arg_root, "/etc/hostname");
803fb7
         if (faccessat(AT_FDCWD, etc_hostname, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
803fb7
                 return 0;
803fb7
 
803fb7
@@ -427,7 +425,7 @@ static int process_machine_id(void) {
803fb7
         char id[SD_ID128_STRING_MAX];
803fb7
         int r;
803fb7
 
803fb7
-        etc_machine_id = prefix_roota("/etc/machine-id");
803fb7
+        etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
803fb7
         if (faccessat(AT_FDCWD, etc_machine_id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
803fb7
                 return 0;
803fb7
 
803fb7
@@ -453,7 +451,7 @@ static int prompt_root_password(void) {
803fb7
         if (!arg_prompt_root_password)
803fb7
                 return 0;
803fb7
 
803fb7
-        etc_shadow = prefix_roota("/etc/shadow");
803fb7
+        etc_shadow = prefix_roota(arg_root, "/etc/shadow");
803fb7
         if (faccessat(AT_FDCWD, etc_shadow, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
803fb7
                 return 0;
803fb7
 
803fb7
@@ -542,7 +540,7 @@ static int process_root_password(void) {
803fb7
         const char *etc_shadow;
803fb7
         int r;
803fb7
 
803fb7
-        etc_shadow = prefix_roota("/etc/shadow");
803fb7
+        etc_shadow = prefix_roota(arg_root, "/etc/shadow");
803fb7
         if (faccessat(AT_FDCWD, etc_shadow, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
803fb7
                 return 0;
803fb7
 
803fb7
diff --git a/src/shared/cgroup-util.c b/src/shared/cgroup-util.c
803fb7
index dfd8689b7..cf757d2b2 100644
803fb7
--- a/src/shared/cgroup-util.c
803fb7
+++ b/src/shared/cgroup-util.c
803fb7
@@ -1147,7 +1147,7 @@ int cg_path_decode_unit(const char *cgroup, char **unit){
803fb7
         c = strndupa(cgroup, e - cgroup);
803fb7
         c = cg_unescape(c);
803fb7
 
803fb7
-        if (!unit_name_is_valid(c, TEMPLATE_INVALID))
803fb7
+        if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
803fb7
                 return -EINVAL;
803fb7
 
803fb7
         s = strdup(c);
803fb7
@@ -1536,7 +1536,7 @@ int cg_slice_to_path(const char *unit, char **ret) {
803fb7
         assert(unit);
803fb7
         assert(ret);
803fb7
 
803fb7
-        if (!unit_name_is_valid(unit, TEMPLATE_INVALID))
803fb7
+        if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN))
803fb7
                 return -EINVAL;
803fb7
 
803fb7
         if (!endswith(unit, ".slice"))
803fb7
@@ -1553,7 +1553,7 @@ int cg_slice_to_path(const char *unit, char **ret) {
803fb7
 
803fb7
                 strcpy(stpncpy(n, p, dash - p), ".slice");
803fb7
 
803fb7
-                if (!unit_name_is_valid(n, TEMPLATE_INVALID))
803fb7
+                if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
803fb7
                         return -EINVAL;
803fb7
 
803fb7
                 escaped = cg_escape(n);
803fb7
diff --git a/src/shared/install.c b/src/shared/install.c
803fb7
index aa197e91b..5288bb450 100644
803fb7
--- a/src/shared/install.c
803fb7
+++ b/src/shared/install.c
803fb7
@@ -39,15 +39,24 @@
803fb7
 #include "specifier.h"
803fb7
 #include "install-printf.h"
803fb7
 #include "special.h"
803fb7
+#include "fileio.h"
803fb7
+
803fb7
+#define UNIT_FILE_FOLLOW_SYMLINK_MAX 64
803fb7
+
803fb7
+typedef enum SearchFlags {
803fb7
+        SEARCH_LOAD = 1,
803fb7
+        SEARCH_FOLLOW_CONFIG_SYMLINKS = 2,
803fb7
+} SearchFlags;
803fb7
 
803fb7
 typedef struct {
803fb7
-        OrderedHashmap *will_install;
803fb7
-        OrderedHashmap *have_installed;
803fb7
+        OrderedHashmap *will_process;
803fb7
+        OrderedHashmap *have_processed;
803fb7
 } InstallContext;
803fb7
 
803fb7
 static int in_search_path(const char *path, char **search) {
803fb7
         _cleanup_free_ char *parent = NULL;
803fb7
         int r;
803fb7
+        char **i;
803fb7
 
803fb7
         assert(path);
803fb7
 
803fb7
@@ -55,7 +64,11 @@ static int in_search_path(const char *path, char **search) {
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        return strv_contains(search, parent);
803fb7
+        STRV_FOREACH(i, search)
803fb7
+                if (path_equal(parent, *i))
803fb7
+                        return true;
803fb7
+
803fb7
+        return false;
803fb7
 }
803fb7
 
803fb7
 static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) {
803fb7
@@ -66,6 +79,9 @@ static int get_config_path(UnitFileScope scope, bool runtime, const char *root_d
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
         assert(ret);
803fb7
 
803fb7
+        /* This determines where we shall create or remove our
803fb7
+         * installation ("configuration") symlinks */
803fb7
+
803fb7
         switch (scope) {
803fb7
 
803fb7
         case UNIT_FILE_SYSTEM:
803fb7
@@ -113,6 +129,186 @@ static int get_config_path(UnitFileScope scope, bool runtime, const char *root_d
803fb7
         return 0;
803fb7
 }
803fb7
 
803fb7
+static bool is_config_path(UnitFileScope scope, const char *path) {
803fb7
+        int r;
803fb7
+
803fb7
+        assert(scope >= 0);
803fb7
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
+        assert(path);
803fb7
+
803fb7
+        /* Checks whether the specified path is intended for
803fb7
+         * configuration or is outside of it */
803fb7
+
803fb7
+        switch (scope) {
803fb7
+
803fb7
+        case UNIT_FILE_SYSTEM:
803fb7
+        case UNIT_FILE_GLOBAL:
803fb7
+                return path_startswith(path, "/etc") ||
803fb7
+                        path_startswith(path, SYSTEM_CONFIG_UNIT_PATH) ||
803fb7
+                        path_startswith(path, "/run");
803fb7
+
803fb7
+
803fb7
+        case UNIT_FILE_USER: {
803fb7
+                _cleanup_free_ char *p = NULL;
803fb7
+
803fb7
+                r = user_config_home(&p);
803fb7
+                if (r < 0)
803fb7
+                        return r;
803fb7
+                if (r > 0 && path_startswith(path, p))
803fb7
+                        return true;
803fb7
+
803fb7
+                free(p);
803fb7
+                p = NULL;
803fb7
+
803fb7
+                r = user_runtime_dir(&p);
803fb7
+                if (r < 0)
803fb7
+                        return r;
803fb7
+                if (r > 0 && path_startswith(path, p))
803fb7
+                        return true;
803fb7
+
803fb7
+                return false;
803fb7
+        }
803fb7
+
803fb7
+        default:
803fb7
+                assert_not_reached("Bad scope");
803fb7
+        }
803fb7
+}
803fb7
+
803fb7
+
803fb7
+static int verify_root_dir(UnitFileScope scope, const char **root_dir) {
803fb7
+        int r;
803fb7
+
803fb7
+        assert(root_dir);
803fb7
+
803fb7
+        /* Verifies that the specified root directory to operate on
803fb7
+         * makes sense. Reset it to NULL if it is the root directory
803fb7
+         * or set to empty */
803fb7
+
803fb7
+        if (isempty(*root_dir) || path_equal(*root_dir, "/")) {
803fb7
+                *root_dir = NULL;
803fb7
+                return 0;
803fb7
+        }
803fb7
+
803fb7
+        if (scope != UNIT_FILE_SYSTEM)
803fb7
+                return -EINVAL;
803fb7
+
803fb7
+        r = is_dir(*root_dir, true);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+        if (r == 0)
803fb7
+                return -ENOTDIR;
803fb7
+
803fb7
+        return 0;
803fb7
+}
803fb7
+
803fb7
+int unit_file_changes_add(
803fb7
+                UnitFileChange **changes,
803fb7
+                unsigned *n_changes,
803fb7
+                UnitFileChangeType type,
803fb7
+                const char *path,
803fb7
+                const char *source) {
803fb7
+
803fb7
+        UnitFileChange *c;
803fb7
+        unsigned i;
803fb7
+
803fb7
+        assert(path);
803fb7
+        assert(!changes == !n_changes);
803fb7
+
803fb7
+        if (!changes)
803fb7
+                return 0;
803fb7
+
803fb7
+        c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange));
803fb7
+        if (!c)
803fb7
+                return -ENOMEM;
803fb7
+
803fb7
+        *changes = c;
803fb7
+        i = *n_changes;
803fb7
+
803fb7
+        c[i].type = type;
803fb7
+        c[i].path = strdup(path);
803fb7
+        if (!c[i].path)
803fb7
+                return -ENOMEM;
803fb7
+
803fb7
+        path_kill_slashes(c[i].path);
803fb7
+
803fb7
+        if (source) {
803fb7
+                c[i].source = strdup(source);
803fb7
+                if (!c[i].source) {
803fb7
+                        free(c[i].path);
803fb7
+                        return -ENOMEM;
803fb7
+                }
803fb7
+
803fb7
+                path_kill_slashes(c[i].path);
803fb7
+        } else
803fb7
+                c[i].source = NULL;
803fb7
+
803fb7
+        *n_changes = i+1;
803fb7
+        return 0;
803fb7
+}
803fb7
+
803fb7
+void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) {
803fb7
+        unsigned i;
803fb7
+
803fb7
+        assert(changes || n_changes == 0);
803fb7
+
803fb7
+        if (!changes)
803fb7
+                return;
803fb7
+
803fb7
+        for (i = 0; i < n_changes; i++) {
803fb7
+                free(changes[i].path);
803fb7
+                free(changes[i].source);
803fb7
+        }
803fb7
+
803fb7
+        free(changes);
803fb7
+}
803fb7
+
803fb7
+static int create_symlink(
803fb7
+                const char *old_path,
803fb7
+                const char *new_path,
803fb7
+                bool force,
803fb7
+                UnitFileChange **changes,
803fb7
+                unsigned *n_changes) {
803fb7
+
803fb7
+        _cleanup_free_ char *dest = NULL;
803fb7
+        int r;
803fb7
+
803fb7
+        assert(old_path);
803fb7
+        assert(new_path);
803fb7
+
803fb7
+        /* Actually create a symlink, and remember that we did. Is
803fb7
+         * smart enough to check if there's already a valid symlink in
803fb7
+         * place. */
803fb7
+
803fb7
+        mkdir_parents_label(new_path, 0755);
803fb7
+
803fb7
+        if (symlink(old_path, new_path) >= 0) {
803fb7
+                unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
803fb7
+                return 0;
803fb7
+        }
803fb7
+
803fb7
+        if (errno != EEXIST)
803fb7
+                return -errno;
803fb7
+
803fb7
+        r = readlink_malloc(new_path, &dest);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        if (path_equal(dest, old_path))
803fb7
+                return 0;
803fb7
+
803fb7
+        if (!force)
803fb7
+                return -EEXIST;
803fb7
+
803fb7
+        r = symlink_atomic(old_path, new_path);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL);
803fb7
+        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
803fb7
+
803fb7
+        return 0;
803fb7
+}
803fb7
+
803fb7
 static int mark_symlink_for_removal(
803fb7
                 Set **remove_symlinks_to,
803fb7
                 const char *p) {
803fb7
@@ -136,7 +332,7 @@ static int mark_symlink_for_removal(
803fb7
         if (r < 0)
803fb7
                 return r == -EEXIST ? 0 : r;
803fb7
 
803fb7
-        return 0;
803fb7
+        return 1;
803fb7
 }
803fb7
 
803fb7
 static int remove_marked_symlinks_fd(
803fb7
@@ -144,10 +340,9 @@ static int remove_marked_symlinks_fd(
803fb7
                 int fd,
803fb7
                 const char *path,
803fb7
                 const char *config_path,
803fb7
-                bool *deleted,
803fb7
+                bool *restart,
803fb7
                 UnitFileChange **changes,
803fb7
-                unsigned *n_changes,
803fb7
-                char** instance_whitelist) {
803fb7
+                unsigned *n_changes) {
803fb7
 
803fb7
         _cleanup_closedir_ DIR *d = NULL;
803fb7
         int r = 0;
803fb7
@@ -156,7 +351,7 @@ static int remove_marked_symlinks_fd(
803fb7
         assert(fd >= 0);
803fb7
         assert(path);
803fb7
         assert(config_path);
803fb7
-        assert(deleted);
803fb7
+        assert(restart);
803fb7
 
803fb7
         d = fdopendir(fd);
803fb7
         if (!d) {
803fb7
@@ -205,42 +400,23 @@ static int remove_marked_symlinks_fd(
803fb7
                         }
803fb7
 
803fb7
                         /* This will close nfd, regardless whether it succeeds or not */
803fb7
-                        q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, deleted, changes, n_changes, instance_whitelist);
803fb7
+                        q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, restart, changes, n_changes);
803fb7
                         if (q < 0 && r == 0)
803fb7
                                 r = q;
803fb7
 
803fb7
                 } else if (de->d_type == DT_LNK) {
803fb7
                         _cleanup_free_ char *p = NULL, *dest = NULL;
803fb7
-                        int q;
803fb7
                         bool found;
803fb7
+                        int q;
803fb7
 
803fb7
-                        if (!unit_name_is_valid(de->d_name, TEMPLATE_VALID))
803fb7
+                        if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
803fb7
                                 continue;
803fb7
 
803fb7
-                        if (unit_name_is_instance(de->d_name) &&
803fb7
-                            instance_whitelist &&
803fb7
-                            !strv_contains(instance_whitelist, de->d_name)) {
803fb7
-
803fb7
-                                _cleanup_free_ char *w;
803fb7
-
803fb7
-                                /* OK, the file is not listed directly
803fb7
-                                 * in the whitelist, so let's check if
803fb7
-                                 * the template of it might be
803fb7
-                                 * listed. */
803fb7
-
803fb7
-                                w = unit_name_template(de->d_name);
803fb7
-                                if (!w)
803fb7
-                                        return -ENOMEM;
803fb7
-
803fb7
-                                if (!strv_contains(instance_whitelist, w))
803fb7
-                                        continue;
803fb7
-                        }
803fb7
-
803fb7
                         p = path_make_absolute(de->d_name, path);
803fb7
                         if (!p)
803fb7
                                 return -ENOMEM;
803fb7
 
803fb7
-                        q = readlink_and_canonicalize(p, &dest);
803fb7
+                        q = readlink_malloc(p, &dest);
803fb7
                         if (q < 0) {
803fb7
                                 if (q == -ENOENT)
803fb7
                                         continue;
803fb7
@@ -250,9 +426,15 @@ static int remove_marked_symlinks_fd(
803fb7
                                 continue;
803fb7
                         }
803fb7
 
803fb7
+                        /* We remove all links pointing to a file or
803fb7
+                         * path that is marked, as well as all files
803fb7
+                         * sharing the same name as a file that is
803fb7
+                         * marked. */
803fb7
+
803fb7
                         found =
803fb7
-                                set_get(remove_symlinks_to, dest) ||
803fb7
-                                set_get(remove_symlinks_to, basename(dest));
803fb7
+                                set_contains(remove_symlinks_to, dest) ||
803fb7
+                                set_contains(remove_symlinks_to, basename(dest)) ||
803fb7
+                                set_contains(remove_symlinks_to, de->d_name);
803fb7
 
803fb7
                         if (!found)
803fb7
                                 continue;
803fb7
@@ -264,18 +446,15 @@ static int remove_marked_symlinks_fd(
803fb7
                         }
803fb7
 
803fb7
                         path_kill_slashes(p);
803fb7
-                        rmdir_parents(p, config_path);
803fb7
-                        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, p, NULL);
803fb7
+                        (void) rmdir_parents(p, config_path);
803fb7
 
803fb7
-                        if (!set_get(remove_symlinks_to, p)) {
803fb7
+                        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, p, NULL);
803fb7
 
803fb7
-                                q = mark_symlink_for_removal(&remove_symlinks_to, p);
803fb7
-                                if (q < 0) {
803fb7
-                                        if (r == 0)
803fb7
-                                                r = q;
803fb7
-                                } else
803fb7
-                                        *deleted = true;
803fb7
-                        }
803fb7
+                        q = mark_symlink_for_removal(&remove_symlinks_to, p);
803fb7
+                        if (q < 0)
803fb7
+                                return q;
803fb7
+                        if (q > 0)
803fb7
+                                *restart = true;
803fb7
                 }
803fb7
         }
803fb7
 
803fb7
@@ -286,12 +465,11 @@ static int remove_marked_symlinks(
803fb7
                 Set *remove_symlinks_to,
803fb7
                 const char *config_path,
803fb7
                 UnitFileChange **changes,
803fb7
-                unsigned *n_changes,
803fb7
-                char** instance_whitelist) {
803fb7
+                unsigned *n_changes) {
803fb7
 
803fb7
         _cleanup_close_ int fd = -1;
803fb7
         int r = 0;
803fb7
-        bool deleted;
803fb7
+        bool restart;
803fb7
 
803fb7
         assert(config_path);
803fb7
 
803fb7
@@ -304,7 +482,7 @@ static int remove_marked_symlinks(
803fb7
 
803fb7
         do {
803fb7
                 int q, cfd;
803fb7
-                deleted = false;
803fb7
+                restart = false;
803fb7
 
803fb7
                 cfd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
803fb7
                 if (cfd < 0) {
803fb7
@@ -313,15 +491,16 @@ static int remove_marked_symlinks(
803fb7
                 }
803fb7
 
803fb7
                 /* This takes possession of cfd and closes it */
803fb7
-                q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &deleted, changes, n_changes, instance_whitelist);
803fb7
+                q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &restart, changes, n_changes);
803fb7
                 if (r == 0)
803fb7
                         r = q;
803fb7
-        } while (deleted);
803fb7
+        } while (restart);
803fb7
 
803fb7
         return r;
803fb7
 }
803fb7
 
803fb7
 static int find_symlinks_fd(
803fb7
+                const char *root_dir,
803fb7
                 const char *name,
803fb7
                 int fd,
803fb7
                 const char *path,
803fb7
@@ -380,7 +559,7 @@ static int find_symlinks_fd(
803fb7
                         }
803fb7
 
803fb7
                         /* This will close nfd, regardless whether it succeeds or not */
803fb7
-                        q = find_symlinks_fd(name, nfd, p, config_path, same_name_link);
803fb7
+                        q = find_symlinks_fd(root_dir, name, nfd, p, config_path, same_name_link);
803fb7
                         if (q > 0)
803fb7
                                 return 1;
803fb7
                         if (r == 0)
803fb7
@@ -397,7 +576,7 @@ static int find_symlinks_fd(
803fb7
                                 return -ENOMEM;
803fb7
 
803fb7
                         /* Acquire symlink destination */
803fb7
-                        q = readlink_and_canonicalize(p, &dest);
803fb7
+                        q = readlink_malloc(p, &dest);
803fb7
                         if (q < 0) {
803fb7
                                 if (q == -ENOENT)
803fb7
                                         continue;
803fb7
@@ -407,6 +586,18 @@ static int find_symlinks_fd(
803fb7
                                 continue;
803fb7
                         }
803fb7
 
803fb7
+                        /* Make absolute */
803fb7
+                        if (!path_is_absolute(dest)) {
803fb7
+                                char *x;
803fb7
+
803fb7
+                                x = prefix_root(root_dir, dest);
803fb7
+                                if (!x)
803fb7
+                                        return -ENOMEM;
803fb7
+
803fb7
+                                free(dest);
803fb7
+                                dest = x;
803fb7
+                        }
803fb7
+
803fb7
                         /* Check if the symlink itself matches what we
803fb7
                          * are looking for */
803fb7
                         if (path_is_absolute(name))
803fb7
@@ -442,6 +633,7 @@ static int find_symlinks_fd(
803fb7
 }
803fb7
 
803fb7
 static int find_symlinks(
803fb7
+                const char *root_dir,
803fb7
                 const char *name,
803fb7
                 const char *config_path,
803fb7
                 bool *same_name_link) {
803fb7
@@ -460,7 +652,7 @@ static int find_symlinks(
803fb7
         }
803fb7
 
803fb7
         /* This takes possession of fd and closes it */
803fb7
-        return find_symlinks_fd(name, fd, config_path, config_path, same_name_link);
803fb7
+        return find_symlinks_fd(root_dir, name, fd, config_path, config_path, same_name_link);
803fb7
 }
803fb7
 
803fb7
 static int find_symlinks_in_scope(
803fb7
@@ -469,385 +661,104 @@ static int find_symlinks_in_scope(
803fb7
                 const char *name,
803fb7
                 UnitFileState *state) {
803fb7
 
803fb7
-        int r;
803fb7
         _cleanup_free_ char *normal_path = NULL, *runtime_path = NULL;
803fb7
         bool same_name_link_runtime = false, same_name_link = false;
803fb7
+        int r;
803fb7
 
803fb7
         assert(scope >= 0);
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
         assert(name);
803fb7
 
803fb7
-        /* First look in runtime config path */
803fb7
-        r = get_config_path(scope, true, root_dir, &normal_path);
803fb7
+        /* First look in the normal config path */
803fb7
+        r = get_config_path(scope, false, root_dir, &normal_path);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        r = find_symlinks(name, normal_path, &same_name_link_runtime);
803fb7
+        r = find_symlinks(root_dir, name, normal_path, &same_name_link);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
-        else if (r > 0) {
803fb7
-                *state = UNIT_FILE_ENABLED_RUNTIME;
803fb7
+        if (r > 0) {
803fb7
+                *state = UNIT_FILE_ENABLED;
803fb7
                 return r;
803fb7
         }
803fb7
 
803fb7
-        /* Then look in the normal config path */
803fb7
-        r = get_config_path(scope, false, root_dir, &runtime_path);
803fb7
+        /* Then look in runtime config path */
803fb7
+        r = get_config_path(scope, true, root_dir, &runtime_path);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        r = find_symlinks(name, runtime_path, &same_name_link);
803fb7
+        r = find_symlinks(root_dir, name, runtime_path, &same_name_link_runtime);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
-        else if (r > 0) {
803fb7
-                *state = UNIT_FILE_ENABLED;
803fb7
+        if (r > 0) {
803fb7
+                *state = UNIT_FILE_ENABLED_RUNTIME;
803fb7
                 return r;
803fb7
         }
803fb7
 
803fb7
         /* Hmm, we didn't find it, but maybe we found the same name
803fb7
          * link? */
803fb7
+        if (same_name_link) {
803fb7
+                *state = UNIT_FILE_LINKED;
803fb7
+                return 1;
803fb7
+        }
803fb7
         if (same_name_link_runtime) {
803fb7
                 *state = UNIT_FILE_LINKED_RUNTIME;
803fb7
                 return 1;
803fb7
-        } else if (same_name_link) {
803fb7
-                *state = UNIT_FILE_LINKED;
803fb7
-                return 1;
803fb7
         }
803fb7
 
803fb7
         return 0;
803fb7
 }
803fb7
 
803fb7
-int unit_file_mask(
803fb7
-                UnitFileScope scope,
803fb7
-                bool runtime,
803fb7
-                const char *root_dir,
803fb7
-                char **files,
803fb7
-                bool force,
803fb7
-                UnitFileChange **changes,
803fb7
-                unsigned *n_changes) {
803fb7
-
803fb7
-        char **i;
803fb7
-        _cleanup_free_ char *prefix = NULL;
803fb7
-        int r;
803fb7
+static void install_info_free(InstallInfo *i) {
803fb7
 
803fb7
-        assert(scope >= 0);
803fb7
-        assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
+        if (!i)
803fb7
+                return;
803fb7
 
803fb7
-        r = get_config_path(scope, runtime, root_dir, &prefix);
803fb7
-        if (r < 0)
803fb7
-                return r;
803fb7
+        free(i->name);
803fb7
+        free(i->path);
803fb7
+        strv_free(i->aliases);
803fb7
+        strv_free(i->wanted_by);
803fb7
+        strv_free(i->required_by);
803fb7
+        strv_free(i->also);
803fb7
+        free(i->default_instance);
803fb7
+        free(i->symlink_target);
803fb7
+        free(i);
803fb7
+}
803fb7
 
803fb7
-        STRV_FOREACH(i, files) {
803fb7
-                _cleanup_free_ char *path = NULL;
803fb7
+static OrderedHashmap* install_info_hashmap_free(OrderedHashmap *m) {
803fb7
+        InstallInfo *i;
803fb7
 
803fb7
-                if (!unit_name_is_valid(*i, TEMPLATE_VALID)) {
803fb7
-                        if (r == 0)
803fb7
-                                r = -EINVAL;
803fb7
-                        continue;
803fb7
-                }
803fb7
+        while ((i = ordered_hashmap_steal_first(m)))
803fb7
+                install_info_free(i);
803fb7
 
803fb7
-                path = path_make_absolute(*i, prefix);
803fb7
-                if (!path) {
803fb7
-                        r = -ENOMEM;
803fb7
-                        break;
803fb7
-                }
803fb7
+        ordered_hashmap_free(m);
803fb7
 
803fb7
-                if (symlink("/dev/null", path) >= 0) {
803fb7
-                        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");
803fb7
-                        continue;
803fb7
-                }
803fb7
+        return NULL;
803fb7
+}
803fb7
 
803fb7
-                if (errno == EEXIST) {
803fb7
+static void install_context_done(InstallContext *c) {
803fb7
+        assert(c);
803fb7
 
803fb7
-                        if (null_or_empty_path(path) > 0)
803fb7
-                                continue;
803fb7
+        c->will_process = install_info_hashmap_free(c->will_process);
803fb7
+        c->have_processed = install_info_hashmap_free(c->have_processed);
803fb7
+}
803fb7
 
803fb7
-                        if (force) {
803fb7
-                                if (symlink_atomic("/dev/null", path) >= 0) {
803fb7
-                                        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
803fb7
-                                        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");
803fb7
-                                        continue;
803fb7
-                                }
803fb7
-                        }
803fb7
+static InstallInfo *install_info_find(InstallContext *c, const char *name) {
803fb7
+        InstallInfo *i;
803fb7
 
803fb7
-                        if (r == 0)
803fb7
-                                r = -EEXIST;
803fb7
-                } else {
803fb7
-                        if (r == 0)
803fb7
-                                r = -errno;
803fb7
-                }
803fb7
-        }
803fb7
+        i = ordered_hashmap_get(c->have_processed, name);
803fb7
+        if (i)
803fb7
+                return i;
803fb7
 
803fb7
-        return r;
803fb7
-}
803fb7
-
803fb7
-int unit_file_unmask(
803fb7
-                UnitFileScope scope,
803fb7
-                bool runtime,
803fb7
-                const char *root_dir,
803fb7
-                char **files,
803fb7
-                UnitFileChange **changes,
803fb7
-                unsigned *n_changes) {
803fb7
-
803fb7
-        char **i, *config_path = NULL;
803fb7
-        int r, q;
803fb7
-        Set *remove_symlinks_to = NULL;
803fb7
-
803fb7
-        assert(scope >= 0);
803fb7
-        assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
-
803fb7
-        r = get_config_path(scope, runtime, root_dir, &config_path);
803fb7
-        if (r < 0)
803fb7
-                goto finish;
803fb7
-
803fb7
-        STRV_FOREACH(i, files) {
803fb7
-                _cleanup_free_ char *path = NULL;
803fb7
-
803fb7
-                if (!unit_name_is_valid(*i, TEMPLATE_VALID)) {
803fb7
-                        if (r == 0)
803fb7
-                                r = -EINVAL;
803fb7
-                        continue;
803fb7
-                }
803fb7
-
803fb7
-                path = path_make_absolute(*i, config_path);
803fb7
-                if (!path) {
803fb7
-                        r = -ENOMEM;
803fb7
-                        break;
803fb7
-                }
803fb7
-
803fb7
-                q = null_or_empty_path(path);
803fb7
-                if (q > 0) {
803fb7
-                        if (unlink(path) < 0)
803fb7
-                                q = -errno;
803fb7
-                        else {
803fb7
-                                q = mark_symlink_for_removal(&remove_symlinks_to, path);
803fb7
-                                unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
803fb7
-                        }
803fb7
-                }
803fb7
-
803fb7
-                if (q != -ENOENT && r == 0)
803fb7
-                        r = q;
803fb7
-        }
803fb7
-
803fb7
-
803fb7
-finish:
803fb7
-        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
803fb7
-        if (r == 0)
803fb7
-                r = q;
803fb7
-
803fb7
-        set_free_free(remove_symlinks_to);
803fb7
-        free(config_path);
803fb7
-
803fb7
-        return r;
803fb7
-}
803fb7
-
803fb7
-int unit_file_link(
803fb7
-                UnitFileScope scope,
803fb7
-                bool runtime,
803fb7
-                const char *root_dir,
803fb7
-                char **files,
803fb7
-                bool force,
803fb7
-                UnitFileChange **changes,
803fb7
-                unsigned *n_changes) {
803fb7
-
803fb7
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
803fb7
-        char **i;
803fb7
-        _cleanup_free_ char *config_path = NULL;
803fb7
-        int r, q;
803fb7
-
803fb7
-        assert(scope >= 0);
803fb7
-        assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
-
803fb7
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
-        if (r < 0)
803fb7
-                return r;
803fb7
-
803fb7
-        r = get_config_path(scope, runtime, root_dir, &config_path);
803fb7
-        if (r < 0)
803fb7
-                return r;
803fb7
-
803fb7
-        STRV_FOREACH(i, files) {
803fb7
-                _cleanup_free_ char *path = NULL;
803fb7
-                char *fn;
803fb7
-                struct stat st;
803fb7
-
803fb7
-                fn = basename(*i);
803fb7
-
803fb7
-                if (!path_is_absolute(*i) ||
803fb7
-                    !unit_name_is_valid(fn, TEMPLATE_VALID)) {
803fb7
-                        if (r == 0)
803fb7
-                                r = -EINVAL;
803fb7
-                        continue;
803fb7
-                }
803fb7
-
803fb7
-                if (lstat(*i, &st) < 0) {
803fb7
-                        if (r == 0)
803fb7
-                                r = -errno;
803fb7
-                        continue;
803fb7
-                }
803fb7
-
803fb7
-                if (!S_ISREG(st.st_mode)) {
803fb7
-                        r = -ENOENT;
803fb7
-                        continue;
803fb7
-                }
803fb7
-
803fb7
-                q = in_search_path(*i, paths.unit_path);
803fb7
-                if (q < 0)
803fb7
-                        return q;
803fb7
-
803fb7
-                if (q > 0)
803fb7
-                        continue;
803fb7
-
803fb7
-                path = path_make_absolute(fn, config_path);
803fb7
-                if (!path)
803fb7
-                        return -ENOMEM;
803fb7
-
803fb7
-                if (symlink(*i, path) >= 0) {
803fb7
-                        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);
803fb7
-                        continue;
803fb7
-                }
803fb7
-
803fb7
-                if (errno == EEXIST) {
803fb7
-                        _cleanup_free_ char *dest = NULL;
803fb7
-
803fb7
-                        q = readlink_and_make_absolute(path, &dest);
803fb7
-                        if (q < 0 && errno != ENOENT) {
803fb7
-                                if (r == 0)
803fb7
-                                        r = q;
803fb7
-                                continue;
803fb7
-                        }
803fb7
-
803fb7
-                        if (q >= 0 && path_equal(dest, *i))
803fb7
-                                continue;
803fb7
-
803fb7
-                        if (force) {
803fb7
-                                if (symlink_atomic(*i, path) >= 0) {
803fb7
-                                        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
803fb7
-                                        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);
803fb7
-                                        continue;
803fb7
-                                }
803fb7
-                        }
803fb7
-
803fb7
-                        if (r == 0)
803fb7
-                                r = -EEXIST;
803fb7
-                } else {
803fb7
-                        if (r == 0)
803fb7
-                                r = -errno;
803fb7
-                }
803fb7
-        }
803fb7
-
803fb7
-        return r;
803fb7
-}
803fb7
-
803fb7
-void unit_file_list_free(Hashmap *h) {
803fb7
-        UnitFileList *i;
803fb7
-
803fb7
-        while ((i = hashmap_steal_first(h))) {
803fb7
-                free(i->path);
803fb7
-                free(i);
803fb7
-        }
803fb7
-
803fb7
-        hashmap_free(h);
803fb7
-}
803fb7
-
803fb7
-int unit_file_changes_add(
803fb7
-                UnitFileChange **changes,
803fb7
-                unsigned *n_changes,
803fb7
-                UnitFileChangeType type,
803fb7
-                const char *path,
803fb7
-                const char *source) {
803fb7
-
803fb7
-        UnitFileChange *c;
803fb7
-        unsigned i;
803fb7
-
803fb7
-        assert(path);
803fb7
-        assert(!changes == !n_changes);
803fb7
-
803fb7
-        if (!changes)
803fb7
-                return 0;
803fb7
-
803fb7
-        c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange));
803fb7
-        if (!c)
803fb7
-                return -ENOMEM;
803fb7
-
803fb7
-        *changes = c;
803fb7
-        i = *n_changes;
803fb7
-
803fb7
-        c[i].type = type;
803fb7
-        c[i].path = strdup(path);
803fb7
-        if (!c[i].path)
803fb7
-                return -ENOMEM;
803fb7
-
803fb7
-        path_kill_slashes(c[i].path);
803fb7
-
803fb7
-        if (source) {
803fb7
-                c[i].source = strdup(source);
803fb7
-                if (!c[i].source) {
803fb7
-                        free(c[i].path);
803fb7
-                        return -ENOMEM;
803fb7
-                }
803fb7
-
803fb7
-                path_kill_slashes(c[i].path);
803fb7
-        } else
803fb7
-                c[i].source = NULL;
803fb7
-
803fb7
-        *n_changes = i+1;
803fb7
-        return 0;
803fb7
-}
803fb7
-
803fb7
-void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) {
803fb7
-        unsigned i;
803fb7
-
803fb7
-        assert(changes || n_changes == 0);
803fb7
-
803fb7
-        if (!changes)
803fb7
-                return;
803fb7
-
803fb7
-        for (i = 0; i < n_changes; i++) {
803fb7
-                free(changes[i].path);
803fb7
-                free(changes[i].source);
803fb7
-        }
803fb7
-
803fb7
-        free(changes);
803fb7
-}
803fb7
-
803fb7
-static void install_info_free(InstallInfo *i) {
803fb7
-        assert(i);
803fb7
-
803fb7
-        free(i->name);
803fb7
-        free(i->path);
803fb7
-        strv_free(i->aliases);
803fb7
-        strv_free(i->wanted_by);
803fb7
-        strv_free(i->required_by);
803fb7
-        strv_free(i->also);
803fb7
-        free(i->default_instance);
803fb7
-        free(i);
803fb7
-}
803fb7
-
803fb7
-static void install_info_hashmap_free(OrderedHashmap *m) {
803fb7
-        InstallInfo *i;
803fb7
-
803fb7
-        if (!m)
803fb7
-                return;
803fb7
-
803fb7
-        while ((i = ordered_hashmap_steal_first(m)))
803fb7
-                install_info_free(i);
803fb7
-
803fb7
-        ordered_hashmap_free(m);
803fb7
-}
803fb7
-
803fb7
-static void install_context_done(InstallContext *c) {
803fb7
-        assert(c);
803fb7
-
803fb7
-        install_info_hashmap_free(c->will_install);
803fb7
-        install_info_hashmap_free(c->have_installed);
803fb7
-
803fb7
-        c->will_install = c->have_installed = NULL;
803fb7
+        return ordered_hashmap_get(c->will_process, name);
803fb7
 }
803fb7
 
803fb7
 static int install_info_add(
803fb7
                 InstallContext *c,
803fb7
                 const char *name,
803fb7
-                const char *path) {
803fb7
+                const char *path,
803fb7
+                InstallInfo **ret) {
803fb7
+
803fb7
         InstallInfo *i = NULL;
803fb7
         int r;
803fb7
 
803fb7
@@ -857,20 +768,24 @@ static int install_info_add(
803fb7
         if (!name)
803fb7
                 name = basename(path);
803fb7
 
803fb7
-        if (!unit_name_is_valid(name, TEMPLATE_VALID))
803fb7
+        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
803fb7
                 return -EINVAL;
803fb7
 
803fb7
-        if (ordered_hashmap_get(c->have_installed, name) ||
803fb7
-            ordered_hashmap_get(c->will_install, name))
803fb7
+        i = install_info_find(c, name);
803fb7
+        if (i) {
803fb7
+                if (ret)
803fb7
+                        *ret = i;
803fb7
                 return 0;
803fb7
+        }
803fb7
 
803fb7
-        r = ordered_hashmap_ensure_allocated(&c->will_install, &string_hash_ops);
803fb7
+        r = ordered_hashmap_ensure_allocated(&c->will_process, &string_hash_ops);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
         i = new0(InstallInfo, 1);
803fb7
         if (!i)
803fb7
                 return -ENOMEM;
803fb7
+        i->type = _UNIT_FILE_TYPE_INVALID;
803fb7
 
803fb7
         i->name = strdup(name);
803fb7
         if (!i->name) {
803fb7
@@ -886,10 +801,13 @@ static int install_info_add(
803fb7
                 }
803fb7
         }
803fb7
 
803fb7
-        r = ordered_hashmap_put(c->will_install, i->name, i);
803fb7
+        r = ordered_hashmap_put(c->will_process, i->name, i);
803fb7
         if (r < 0)
803fb7
                 goto fail;
803fb7
 
803fb7
+        if (ret)
803fb7
+                *ret = i;
803fb7
+
803fb7
         return 0;
803fb7
 
803fb7
 fail:
803fb7
@@ -901,15 +819,16 @@ fail:
803fb7
 
803fb7
 static int install_info_add_auto(
803fb7
                 InstallContext *c,
803fb7
-                const char *name_or_path) {
803fb7
+                const char *name_or_path,
803fb7
+                InstallInfo **ret) {
803fb7
 
803fb7
         assert(c);
803fb7
         assert(name_or_path);
803fb7
 
803fb7
         if (path_is_absolute(name_or_path))
803fb7
-                return install_info_add(c, NULL, name_or_path);
803fb7
+                return install_info_add(c, NULL, name_or_path, ret);
803fb7
         else
803fb7
-                return install_info_add(c, name_or_path, NULL);
803fb7
+                return install_info_add(c, name_or_path, NULL, ret);
803fb7
 }
803fb7
 
803fb7
 static int config_parse_also(
803fb7
@@ -928,6 +847,7 @@ static int config_parse_also(
803fb7
         const char *word, *state;
803fb7
         InstallContext *c = data;
803fb7
         InstallInfo *i = userdata;
803fb7
+        int r;
803fb7
 
803fb7
         assert(filename);
803fb7
         assert(lvalue);
803fb7
@@ -935,19 +855,20 @@ static int config_parse_also(
803fb7
 
803fb7
         FOREACH_WORD_QUOTED(word, l, rvalue, state) {
803fb7
                 _cleanup_free_ char *n;
803fb7
-                int r;
803fb7
 
803fb7
                 n = strndup(word, l);
803fb7
                 if (!n)
803fb7
                         return -ENOMEM;
803fb7
 
803fb7
-                r = install_info_add(c, n, NULL);
803fb7
+                r = install_info_add(c, n, NULL, NULL);
803fb7
                 if (r < 0)
803fb7
                         return r;
803fb7
 
803fb7
-                r = strv_extend(&i->also, n);
803fb7
+                r = strv_push(&i->also, n);
803fb7
                 if (r < 0)
803fb7
                         return r;
803fb7
+
803fb7
+                n = NULL;
803fb7
         }
803fb7
         if (!isempty(state))
803fb7
                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
803fb7
@@ -1026,9 +947,7 @@ static int unit_file_load(
803fb7
                 InstallInfo *info,
803fb7
                 const char *path,
803fb7
                 const char *root_dir,
803fb7
-                bool allow_symlink,
803fb7
-                bool load,
803fb7
-                bool *also) {
803fb7
+                SearchFlags flags) {
803fb7
 
803fb7
         const ConfigTableItem items[] = {
803fb7
                 { "Install", "Alias",           config_parse_strv,             0, &info->aliases           },
803fb7
@@ -1041,7 +960,9 @@ static int unit_file_load(
803fb7
         };
803fb7
 
803fb7
         _cleanup_fclose_ FILE *f = NULL;
803fb7
-        int fd, r;
803fb7
+        _cleanup_close_ int fd = -1;
803fb7
+        struct stat st;
803fb7
+        int r;
803fb7
 
803fb7
         assert(c);
803fb7
         assert(info);
803fb7
@@ -1050,20 +971,43 @@ static int unit_file_load(
803fb7
         if (!isempty(root_dir))
803fb7
                 path = strjoina(root_dir, "/", path);
803fb7
 
803fb7
-        if (!load) {
803fb7
-                r = access(path, F_OK) ? -errno : 0;
803fb7
-                return r;
803fb7
+        if (!(flags & SEARCH_LOAD)) {
803fb7
+                r = lstat(path, &st);
803fb7
+                if (r < 0)
803fb7
+                        return -errno;
803fb7
+
803fb7
+                if (null_or_empty(&st))
803fb7
+                        info->type = UNIT_FILE_TYPE_MASKED;
803fb7
+                else if (S_ISREG(st.st_mode))
803fb7
+                        info->type = UNIT_FILE_TYPE_REGULAR;
803fb7
+                else if (S_ISLNK(st.st_mode))
803fb7
+                        return -ELOOP;
803fb7
+                else if (S_ISDIR(st.st_mode))
803fb7
+                        return -EISDIR;
803fb7
+                else
803fb7
+                        return -ENOTTY;
803fb7
+
803fb7
+                return 0;
803fb7
         }
803fb7
 
803fb7
-        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|(allow_symlink ? 0 : O_NOFOLLOW));
803fb7
+        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
803fb7
         if (fd < 0)
803fb7
                 return -errno;
803fb7
+        if (fstat(fd, &st) < 0)
803fb7
+                return -errno;
803fb7
+        if (null_or_empty(&st)) {
803fb7
+               info->type = UNIT_FILE_MASKED;
803fb7
+                return 0;
803fb7
+        }
803fb7
+        if (S_ISDIR(st.st_mode))
803fb7
+                return -EISDIR;
803fb7
+        if (!S_ISREG(st.st_mode))
803fb7
+                return -ENOTTY;
803fb7
 
803fb7
         f = fdopen(fd, "re");
803fb7
-        if (!f) {
803fb7
-                safe_close(fd);
803fb7
-                return -ENOMEM;
803fb7
-        }
803fb7
+        if (!f)
803fb7
+                return -errno;
803fb7
+        fd = -1;
803fb7
 
803fb7
         r = config_parse(NULL, path, f,
803fb7
                          NULL,
803fb7
@@ -1072,8 +1016,7 @@ static int unit_file_load(
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        if (also)
803fb7
-                *also = !strv_isempty(info->also);
803fb7
+        info->type = UNIT_FILE_TYPE_REGULAR;
803fb7
 
803fb7
         return
803fb7
                 (int) strv_length(info->aliases) +
803fb7
@@ -1081,14 +1024,73 @@ static int unit_file_load(
803fb7
                 (int) strv_length(info->required_by);
803fb7
 }
803fb7
 
803fb7
+static int unit_file_load_or_readlink(
803fb7
+                InstallContext *c,
803fb7
+                InstallInfo *info,
803fb7
+                const char *path,
803fb7
+                const char *root_dir,
803fb7
+                SearchFlags flags) {
803fb7
+
803fb7
+        _cleanup_free_ char *np = NULL;
803fb7
+        int r;
803fb7
+
803fb7
+        r = unit_file_load(c, info, path, root_dir, flags);
803fb7
+        if (r != -ELOOP)
803fb7
+                return r;
803fb7
+
803fb7
+        /* This is a symlink, let's read it. */
803fb7
+
803fb7
+        r = readlink_and_make_absolute_root(root_dir, path, &np);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        if (path_equal(np, "/dev/null"))
803fb7
+                info->type = UNIT_FILE_TYPE_MASKED;
803fb7
+        else {
803fb7
+                const char *bn;
803fb7
+                UnitType a, b;
803fb7
+
803fb7
+                bn = basename(np);
803fb7
+
803fb7
+                if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) {
803fb7
+
803fb7
+                        if (!unit_name_is_valid(bn, UNIT_NAME_PLAIN))
803fb7
+                                return -EINVAL;
803fb7
+
803fb7
+                } else if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
803fb7
+
803fb7
+                        if (!unit_name_is_valid(bn, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
803fb7
+                                return -EINVAL;
803fb7
+
803fb7
+                } else if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE)) {
803fb7
+
803fb7
+                        if (!unit_name_is_valid(bn, UNIT_NAME_TEMPLATE))
803fb7
+                                return -EINVAL;
803fb7
+                } else
803fb7
+                        return -EINVAL;
803fb7
+
803fb7
+                /* Enforce that the symlink destination does not
803fb7
+                 * change the unit file type. */
803fb7
+
803fb7
+                a = unit_name_to_type(info->name);
803fb7
+                b = unit_name_to_type(bn);
803fb7
+                if (a < 0 || b < 0 || a != b)
803fb7
+                        return -EINVAL;
803fb7
+
803fb7
+                info->type = UNIT_FILE_TYPE_SYMLINK;
803fb7
+                info->symlink_target = np;
803fb7
+                np = NULL;
803fb7
+        }
803fb7
+
803fb7
+        return 0;
803fb7
+}
803fb7
+
803fb7
 static int unit_file_search(
803fb7
                 InstallContext *c,
803fb7
                 InstallInfo *info,
803fb7
                 const LookupPaths *paths,
803fb7
                 const char *root_dir,
803fb7
-                bool allow_symlink,
803fb7
-                bool load,
803fb7
-                bool *also) {
803fb7
+                SearchFlags flags) {
803fb7
 
803fb7
         char **p;
803fb7
         int r;
803fb7
@@ -1097,8 +1099,12 @@ static int unit_file_search(
803fb7
         assert(info);
803fb7
         assert(paths);
803fb7
 
803fb7
+        /* Was this unit already loaded? */
803fb7
+        if (info->type != _UNIT_FILE_TYPE_INVALID)
803fb7
+                return 0;
803fb7
+
803fb7
         if (info->path)
803fb7
-                return unit_file_load(c, info, info->path, root_dir, allow_symlink, load, also);
803fb7
+                return unit_file_load_or_readlink(c, info, info->path, root_dir, flags);
803fb7
 
803fb7
         assert(info->name);
803fb7
 
803fb7
@@ -1109,14 +1115,15 @@ static int unit_file_search(
803fb7
                 if (!path)
803fb7
                         return -ENOMEM;
803fb7
 
803fb7
-                r = unit_file_load(c, info, path, root_dir, allow_symlink, load, also);
803fb7
-                if (r >= 0) {
803fb7
+                r = unit_file_load_or_readlink(c, info, path, root_dir, flags);
803fb7
+                if (r < 0) {
803fb7
+                        if (r != -ENOENT)
803fb7
+                                return r;
803fb7
+                } else {
803fb7
                         info->path = path;
803fb7
                         path = NULL;
803fb7
                         return r;
803fb7
                 }
803fb7
-                if (r != -ENOENT && r != -ELOOP)
803fb7
-                        return r;
803fb7
         }
803fb7
 
803fb7
         if (unit_name_is_instance(info->name)) {
803fb7
@@ -1138,92 +1145,149 @@ static int unit_file_search(
803fb7
                         if (!path)
803fb7
                                 return -ENOMEM;
803fb7
 
803fb7
-                        r = unit_file_load(c, info, path, root_dir, allow_symlink, load, also);
803fb7
-                        if (r >= 0) {
803fb7
+                        r = unit_file_load_or_readlink(c, info, path, root_dir, flags);
803fb7
+                        if (r < 0) {
803fb7
+                                if (r != -ENOENT)
803fb7
+                                        return r;
803fb7
+                        } else {
803fb7
                                 info->path = path;
803fb7
                                 path = NULL;
803fb7
                                 return r;
803fb7
                         }
803fb7
-                        if (r != -ENOENT && r != -ELOOP)
803fb7
-                                return r;
803fb7
                 }
803fb7
         }
803fb7
 
803fb7
         return -ENOENT;
803fb7
 }
803fb7
 
803fb7
-static int unit_file_can_install(
803fb7
-                const LookupPaths *paths,
803fb7
+static int install_info_follow(
803fb7
+                InstallContext *c,
803fb7
+                InstallInfo *i,
803fb7
                 const char *root_dir,
803fb7
-                const char *name,
803fb7
-                bool allow_symlink,
803fb7
-                bool *also) {
803fb7
+                SearchFlags flags) {
803fb7
+
803fb7
+        assert(c);
803fb7
+        assert(i);
803fb7
+
803fb7
+        if (i->type != UNIT_FILE_TYPE_SYMLINK)
803fb7
+                return -EINVAL;
803fb7
+        if (!i->symlink_target)
803fb7
+                return -EINVAL;
803fb7
+
803fb7
+        /* If the basename doesn't match, the caller should add a
803fb7
+         * complete new entry for this. */
803fb7
+
803fb7
+        if (!streq(basename(i->symlink_target), i->name))
803fb7
+                return -EXDEV;
803fb7
+
803fb7
+        free(i->path);
803fb7
+        i->path = i->symlink_target;
803fb7
+        i->symlink_target = NULL;
803fb7
+        i->type = _UNIT_FILE_TYPE_INVALID;
803fb7
+
803fb7
+        return unit_file_load_or_readlink(c, i, i->path, root_dir, flags);
803fb7
+}
803fb7
+
803fb7
+static int install_info_traverse(
803fb7
+                UnitFileScope scope,
803fb7
+                InstallContext *c,
803fb7
+                const char *root_dir,
803fb7
+                const LookupPaths *paths,
803fb7
+                InstallInfo *start,
803fb7
+                SearchFlags flags,
803fb7
+                InstallInfo **ret) {
803fb7
 
803fb7
-        _cleanup_(install_context_done) InstallContext c = {};
803fb7
         InstallInfo *i;
803fb7
+        unsigned k = 0;
803fb7
         int r;
803fb7
 
803fb7
         assert(paths);
803fb7
-        assert(name);
803fb7
+        assert(start);
803fb7
+        assert(c);
803fb7
 
803fb7
-        r = install_info_add_auto(&c, name);
803fb7
+        r = unit_file_search(c, start, paths, root_dir, flags);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        assert_se(i = ordered_hashmap_first(c.will_install));
803fb7
+        i = start;
803fb7
+        while (i->type == UNIT_FILE_TYPE_SYMLINK) {
803fb7
+                /* Follow the symlink */
803fb7
 
803fb7
-        r = unit_file_search(&c, i, paths, root_dir, allow_symlink, true, also);
803fb7
+                if (++k > UNIT_FILE_FOLLOW_SYMLINK_MAX)
803fb7
+                        return -ELOOP;
803fb7
 
803fb7
-        if (r >= 0)
803fb7
-                r =
803fb7
-                        (int) strv_length(i->aliases) +
803fb7
-                        (int) strv_length(i->wanted_by) +
803fb7
-                        (int) strv_length(i->required_by);
803fb7
+                if (!(flags & SEARCH_FOLLOW_CONFIG_SYMLINKS) && is_config_path(scope, i->path))
803fb7
+                        return -ELOOP;
803fb7
 
803fb7
-        return r;
803fb7
-}
803fb7
+                r = install_info_follow(c, i, root_dir, flags);
803fb7
+                if (r < 0) {
803fb7
+                        _cleanup_free_ char *buffer = NULL;
803fb7
+                        const char *bn;
803fb7
 
803fb7
-static int create_symlink(
803fb7
-                const char *old_path,
803fb7
-                const char *new_path,
803fb7
-                bool force,
803fb7
-                UnitFileChange **changes,
803fb7
-                unsigned *n_changes) {
803fb7
+                        if (r != -EXDEV)
803fb7
+                                return r;
803fb7
 
803fb7
-        _cleanup_free_ char *dest = NULL;
803fb7
-        int r;
803fb7
+                        /* Target has a different name, create a new
803fb7
+                         * install info object for that, and continue
803fb7
+                         * with that. */
803fb7
 
803fb7
-        assert(old_path);
803fb7
-        assert(new_path);
803fb7
+                        bn = basename(i->symlink_target);
803fb7
 
803fb7
-        mkdir_parents_label(new_path, 0755);
803fb7
+                        if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE) &&
803fb7
+                            unit_name_is_valid(bn, UNIT_NAME_TEMPLATE)) {
803fb7
 
803fb7
-        if (symlink(old_path, new_path) >= 0) {
803fb7
-                unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
803fb7
-                return 0;
803fb7
+                                _cleanup_free_ char *instance = NULL;
803fb7
+
803fb7
+                                r = unit_name_to_instance(i->name, &instance);
803fb7
+                                if (r < 0)
803fb7
+                                        return r;
803fb7
+
803fb7
+                                buffer = unit_name_replace_instance(bn, instance);
803fb7
+                                if (!buffer)
803fb7
+                                        return -ENOMEM;
803fb7
+
803fb7
+                                bn = buffer;
803fb7
+                        }
803fb7
+
803fb7
+                        r = install_info_add(c, bn, NULL, &i);
803fb7
+                        if (r < 0)
803fb7
+                                return r;
803fb7
+
803fb7
+                        r = unit_file_search(c, i, paths, root_dir, flags);
803fb7
+                        if (r < 0)
803fb7
+                                return r;
803fb7
+                }
803fb7
+
803fb7
+                /* Try again, with the new target we found. */
803fb7
         }
803fb7
 
803fb7
-        if (errno != EEXIST)
803fb7
-                return -errno;
803fb7
+        if (ret)
803fb7
+                *ret = i;
803fb7
 
803fb7
-        r = readlink_and_make_absolute(new_path, &dest);
803fb7
-        if (r < 0)
803fb7
-                return r;
803fb7
+        return 0;
803fb7
+}
803fb7
+
803fb7
+static int install_info_discover(
803fb7
+                UnitFileScope scope,
803fb7
+                InstallContext *c,
803fb7
+                const char *root_dir,
803fb7
+                const LookupPaths *paths,
803fb7
+                const char *name,
803fb7
+                SearchFlags flags,
803fb7
+                InstallInfo **ret) {
803fb7
 
803fb7
-        if (path_equal(dest, old_path))
803fb7
-                return 0;
803fb7
+        InstallInfo *i;
803fb7
+        int r;
803fb7
 
803fb7
-        if (!force)
803fb7
-                return -EEXIST;
803fb7
+        assert(c);
803fb7
+        assert(paths);
803fb7
+        assert(name);
803fb7
 
803fb7
-        r = symlink_atomic(old_path, new_path);
803fb7
+        r = install_info_add_auto(c, name, &i);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL);
803fb7
-        unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
803fb7
-
803fb7
-        return 0;
803fb7
+        return install_info_traverse(scope, c, root_dir, paths, i, flags, ret);
803fb7
 }
803fb7
 
803fb7
 static int install_info_symlink_alias(
803fb7
@@ -1298,7 +1362,7 @@ static int install_info_symlink_wants(
803fb7
                 if (q < 0)
803fb7
                         return q;
803fb7
 
803fb7
-                if (!unit_name_is_valid(dst, TEMPLATE_VALID)) {
803fb7
+                if (!unit_name_is_valid(dst, UNIT_NAME_ANY)) {
803fb7
                         r = -EINVAL;
803fb7
                         continue;
803fb7
                 }
803fb7
@@ -1358,6 +1422,9 @@ static int install_info_apply(
803fb7
         assert(paths);
803fb7
         assert(config_path);
803fb7
 
803fb7
+        if (i->type != UNIT_FILE_TYPE_REGULAR)
803fb7
+                return 0;
803fb7
+
803fb7
         r = install_info_symlink_alias(i, config_path, force, changes, n_changes);
803fb7
 
803fb7
         q = install_info_symlink_wants(i, config_path, i->wanted_by, ".wants/", force, changes, n_changes);
803fb7
@@ -1376,53 +1443,59 @@ static int install_info_apply(
803fb7
 }
803fb7
 
803fb7
 static int install_context_apply(
803fb7
+                UnitFileScope scope,
803fb7
                 InstallContext *c,
803fb7
                 const LookupPaths *paths,
803fb7
                 const char *config_path,
803fb7
                 const char *root_dir,
803fb7
                 bool force,
803fb7
+                SearchFlags flags,
803fb7
                 UnitFileChange **changes,
803fb7
                 unsigned *n_changes) {
803fb7
 
803fb7
         InstallInfo *i;
803fb7
-        int r, q;
803fb7
+        int r;
803fb7
 
803fb7
         assert(c);
803fb7
         assert(paths);
803fb7
         assert(config_path);
803fb7
 
803fb7
-        if (!ordered_hashmap_isempty(c->will_install)) {
803fb7
-                r = ordered_hashmap_ensure_allocated(&c->have_installed, &string_hash_ops);
803fb7
-                if (r < 0)
803fb7
-                        return r;
803fb7
+        if (ordered_hashmap_isempty(c->will_process))
803fb7
+                return 0;
803fb7
 
803fb7
-                r = ordered_hashmap_reserve(c->have_installed, ordered_hashmap_size(c->will_install));
803fb7
-                if (r < 0)
803fb7
-                        return r;
803fb7
-        }
803fb7
+        r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
 
803fb7
         r = 0;
803fb7
-        while ((i = ordered_hashmap_first(c->will_install))) {
803fb7
-                assert_se(ordered_hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);
803fb7
+        while ((i = ordered_hashmap_first(c->will_process))) {
803fb7
+                int q;
803fb7
 
803fb7
-                q = unit_file_search(c, i, paths, root_dir, false, true, NULL);
803fb7
-                if (q < 0) {
803fb7
-                        if (r >= 0)
803fb7
-                                r = q;
803fb7
+                q = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name);
803fb7
+                if (q < 0)
803fb7
+                        return q;
803fb7
 
803fb7
+                r = install_info_traverse(scope, c, root_dir, paths, i, flags, NULL);
803fb7
+                if (r < 0)
803fb7
                         return r;
803fb7
-                } else if (r >= 0)
803fb7
-                        r += q;
803fb7
+
803fb7
+                if (i->type != UNIT_FILE_TYPE_REGULAR)
803fb7
+                        continue;
803fb7
 
803fb7
                 q = install_info_apply(i, paths, config_path, root_dir, force, changes, n_changes);
803fb7
-                if (r >= 0 && q < 0)
803fb7
-                        r = q;
803fb7
+                if (r >= 0) {
803fb7
+                        if (q < 0)
803fb7
+                                r = q;
803fb7
+                        else
803fb7
+                                r+= q;
803fb7
+                }
803fb7
         }
803fb7
 
803fb7
         return r;
803fb7
 }
803fb7
 
803fb7
 static int install_context_mark_for_removal(
803fb7
+                UnitFileScope scope,
803fb7
                 InstallContext *c,
803fb7
                 const LookupPaths *paths,
803fb7
                 Set **remove_symlinks_to,
803fb7
@@ -1430,96 +1503,109 @@ static int install_context_mark_for_removal(
803fb7
                 const char *root_dir) {
803fb7
 
803fb7
         InstallInfo *i;
803fb7
-        int r, q;
803fb7
+        int r;
803fb7
 
803fb7
         assert(c);
803fb7
         assert(paths);
803fb7
         assert(config_path);
803fb7
 
803fb7
         /* Marks all items for removal */
803fb7
-
803fb7
-        if (!ordered_hashmap_isempty(c->will_install)) {
803fb7
-                r = ordered_hashmap_ensure_allocated(&c->have_installed, &string_hash_ops);
803fb7
+        if (!ordered_hashmap_isempty(c->will_process)) {
803fb7
+                r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops);
803fb7
                 if (r < 0)
803fb7
                         return r;
803fb7
 
803fb7
-                r = ordered_hashmap_reserve(c->have_installed, ordered_hashmap_size(c->will_install));
803fb7
+                r = ordered_hashmap_reserve(c->have_processed, ordered_hashmap_size(c->will_process));
803fb7
                 if (r < 0)
803fb7
                         return r;
803fb7
         }
803fb7
 
803fb7
         r = 0;
803fb7
-        while ((i = ordered_hashmap_first(c->will_install))) {
803fb7
-                assert_se(ordered_hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);
803fb7
-
803fb7
-                q = unit_file_search(c, i, paths, root_dir, false, true, NULL);
803fb7
-                if (q == -ENOENT) {
803fb7
-                        /* do nothing */
803fb7
-                } else if (q < 0) {
803fb7
-                        if (r >= 0)
803fb7
-                                r = q;
803fb7
+        while ((i = ordered_hashmap_first(c->will_process))) {
803fb7
+
803fb7
+                r = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name);
803fb7
+                if (r < 0)
803fb7
+                        return r;
803fb7
 
803fb7
+                r = install_info_traverse(scope, c, root_dir, paths, i, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL);
803fb7
+                if (r < 0)
803fb7
                         return r;
803fb7
-                } else if (r >= 0)
803fb7
-                        r += q;
803fb7
-
803fb7
-                if (unit_name_is_instance(i->name)) {
803fb7
-                        char *unit_file;
803fb7
-
803fb7
-                        if (i->path) {
803fb7
-                                unit_file = basename(i->path);
803fb7
-
803fb7
-                                if (unit_name_is_instance(unit_file))
803fb7
-                                        /* unit file named as instance exists, thus all symlinks
803fb7
-                                         * pointing to it will be removed */
803fb7
-                                        q = mark_symlink_for_removal(remove_symlinks_to, i->name);
803fb7
-                                else
803fb7
-                                        /* does not exist, thus we will mark for removal symlinks
803fb7
-                                         * to template unit file */
803fb7
-                                        q = mark_symlink_for_removal(remove_symlinks_to, unit_file);
803fb7
-                        } else {
803fb7
-                                /* If i->path is not set, it means that we didn't actually find
803fb7
-                                 * the unit file. But we can still remove symlinks to the
803fb7
-                                 * nonexistent template. */
803fb7
-                                unit_file = unit_name_template(i->name);
803fb7
-                                if (!unit_file)
803fb7
-                                        return log_oom();
803fb7
-
803fb7
-                                q = mark_symlink_for_removal(remove_symlinks_to, unit_file);
803fb7
-                                free(unit_file);
803fb7
-                        }
803fb7
-                } else
803fb7
-                        q = mark_symlink_for_removal(remove_symlinks_to, i->name);
803fb7
 
803fb7
-                if (r >= 0 && q < 0)
803fb7
+                if (i->type != UNIT_FILE_TYPE_REGULAR)
803fb7
+                        continue;
803fb7
+
803fb7
+                r = mark_symlink_for_removal(remove_symlinks_to, i->name);
803fb7
+                if (r < 0)
803fb7
+                        return r;
803fb7
+        }
803fb7
+
803fb7
+        return 0;
803fb7
+}
803fb7
+
803fb7
+int unit_file_mask(
803fb7
+                UnitFileScope scope,
803fb7
+                bool runtime,
803fb7
+                const char *root_dir,
803fb7
+                char **files,
803fb7
+                bool force,
803fb7
+                UnitFileChange **changes,
803fb7
+                unsigned *n_changes) {
803fb7
+
803fb7
+        _cleanup_free_ char *prefix = NULL;
803fb7
+        char **i;
803fb7
+        int r;
803fb7
+
803fb7
+        assert(scope >= 0);
803fb7
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
+
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        r = get_config_path(scope, runtime, root_dir, &prefix);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        STRV_FOREACH(i, files) {
803fb7
+                _cleanup_free_ char *path = NULL;
803fb7
+                int q;
803fb7
+                if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) {
803fb7
+                        if (r == 0)
803fb7
+                                r = -EINVAL;
803fb7
+                        continue;
803fb7
+                }
803fb7
+
803fb7
+                path = path_make_absolute(*i, prefix);
803fb7
+                if (!path)
803fb7
+                        return -ENOMEM;
803fb7
+
803fb7
+                q = create_symlink("/dev/null", path, force, changes, n_changes);
803fb7
+                if (q < 0 && r >= 0)
803fb7
                         r = q;
803fb7
         }
803fb7
 
803fb7
         return r;
803fb7
 }
803fb7
 
803fb7
-int unit_file_add_dependency(
803fb7
+int unit_file_unmask(
803fb7
                 UnitFileScope scope,
803fb7
                 bool runtime,
803fb7
                 const char *root_dir,
803fb7
                 char **files,
803fb7
-                char *target,
803fb7
-                UnitDependency dep,
803fb7
-                bool force,
803fb7
                 UnitFileChange **changes,
803fb7
                 unsigned *n_changes) {
803fb7
 
803fb7
-        _cleanup_lookup_paths_free_ LookupPaths paths = {};
803fb7
-        _cleanup_(install_context_done) InstallContext c = {};
803fb7
+        _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
803fb7
         _cleanup_free_ char *config_path = NULL;
803fb7
+        _cleanup_free_ char **todo = NULL;
803fb7
+        size_t n_todo = 0, n_allocated = 0;
803fb7
         char **i;
803fb7
-        int r;
803fb7
-        InstallInfo *info;
803fb7
+        int r, q;
803fb7
 
803fb7
         assert(scope >= 0);
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
 
803fb7
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
@@ -1528,57 +1614,222 @@ int unit_file_add_dependency(
803fb7
                 return r;
803fb7
 
803fb7
         STRV_FOREACH(i, files) {
803fb7
-                UnitFileState state;
803fb7
+                _cleanup_free_ char *path = NULL;
803fb7
 
803fb7
-                state = unit_file_get_state(scope, root_dir, *i);
803fb7
-                if (state < 0)
803fb7
-                        return log_error_errno(state, "Failed to get unit file state for %s: %m", *i);
803fb7
+                if (!unit_name_is_valid(*i, UNIT_NAME_ANY))
803fb7
+                        return -EINVAL;
803fb7
 
803fb7
-                if (state == UNIT_FILE_MASKED || state == UNIT_FILE_MASKED_RUNTIME) {
803fb7
-                        log_error("Failed to enable unit: Unit %s is masked", *i);
803fb7
-                        return -ENOTSUP;
803fb7
-                }
803fb7
+                path = path_make_absolute(*i, config_path);
803fb7
+                if (!path)
803fb7
+                        return -ENOMEM;
803fb7
 
803fb7
-                r = install_info_add_auto(&c, *i);
803fb7
+                r = null_or_empty_path(path);
803fb7
+                if (r == -ENOENT)
803fb7
+                        continue;
803fb7
                 if (r < 0)
803fb7
                         return r;
803fb7
+                if (r == 0)
803fb7
+                        continue;
803fb7
+
803fb7
+                if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2))
803fb7
+                        return -ENOMEM;
803fb7
+
803fb7
+                todo[n_todo++] = *i;
803fb7
         }
803fb7
 
803fb7
-        if (!ordered_hashmap_isempty(c.will_install)) {
803fb7
-                r = ordered_hashmap_ensure_allocated(&c.have_installed, &string_hash_ops);
803fb7
-                if (r < 0)
803fb7
-                        return r;
803fb7
+        strv_uniq(todo);
803fb7
 
803fb7
-                r = ordered_hashmap_reserve(c.have_installed, ordered_hashmap_size(c.will_install));
803fb7
-                if (r < 0)
803fb7
-                        return r;
803fb7
+        r = 0;
803fb7
+        STRV_FOREACH(i, todo) {
803fb7
+                _cleanup_free_ char *path = NULL;
803fb7
+
803fb7
+                path = path_make_absolute(*i, config_path);
803fb7
+                if (!path)
803fb7
+                        return -ENOMEM;
803fb7
+
803fb7
+                if (unlink(path) < 0) {
803fb7
+                        if (errno != -ENOENT && r >= 0)
803fb7
+                                r = -errno;
803fb7
+                } else {
803fb7
+                        q = mark_symlink_for_removal(&remove_symlinks_to, path);
803fb7
+                        if (q < 0)
803fb7
+                                return q;
803fb7
+
803fb7
+                        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
803fb7
+                }
803fb7
         }
803fb7
 
803fb7
-        while ((info = ordered_hashmap_first(c.will_install))) {
803fb7
-                assert_se(ordered_hashmap_move_one(c.have_installed, c.will_install, info->name) == 0);
803fb7
+        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
803fb7
+        if (r >= 0)
803fb7
+                r = q;
803fb7
 
803fb7
-                r = unit_file_search(&c, info, &paths, root_dir, false, false, NULL);
803fb7
-                if (r < 0)
803fb7
-                        return r;
803fb7
+        return r;
803fb7
+}
803fb7
 
803fb7
-                if (dep == UNIT_WANTS)
803fb7
-                        r = strv_extend(&info->wanted_by, target);
803fb7
-                else if (dep == UNIT_REQUIRES)
803fb7
-                        r = strv_extend(&info->required_by, target);
803fb7
-                else
803fb7
-                        r = -EINVAL;
803fb7
+int unit_file_link(
803fb7
+                UnitFileScope scope,
803fb7
+                bool runtime,
803fb7
+                const char *root_dir,
803fb7
+                char **files,
803fb7
+                bool force,
803fb7
+                UnitFileChange **changes,
803fb7
+                unsigned *n_changes) {
803fb7
 
803fb7
-                if (r < 0)
803fb7
-                        return r;
803fb7
+        _cleanup_lookup_paths_free_ LookupPaths paths = {};
803fb7
+        _cleanup_free_ char *config_path = NULL;
803fb7
+        _cleanup_free_ char **todo = NULL;
803fb7
+        size_t n_todo = 0, n_allocated = 0;
803fb7
+        char **i;
803fb7
+        int r,q;
803fb7
 
803fb7
-                r = install_info_apply(info, &paths, config_path, root_dir, force, changes, n_changes);
803fb7
-                if (r < 0)
803fb7
-                        return r;
803fb7
+        assert(scope >= 0);
803fb7
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
+
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        r = get_config_path(scope, runtime, root_dir, &config_path);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        STRV_FOREACH(i, files) {
803fb7
+                _cleanup_free_ char *full = NULL;
803fb7
+                struct stat st;
803fb7
+                char *fn;
803fb7
+
803fb7
+                if (!path_is_absolute(*i))
803fb7
+                        return -EINVAL;
803fb7
+
803fb7
+                fn = basename(*i);
803fb7
+                if (!unit_name_is_valid(fn, UNIT_NAME_ANY))
803fb7
+                        return -EINVAL;
803fb7
+
803fb7
+                full = prefix_root(root_dir, *i);
803fb7
+                if (!full)
803fb7
+                        return -ENOMEM;
803fb7
+
803fb7
+                if (lstat(full, &st) < 0)
803fb7
+                        return -errno;
803fb7
+                if (S_ISLNK(st.st_mode))
803fb7
+                        return -ELOOP;
803fb7
+                if (S_ISDIR(st.st_mode))
803fb7
+                        return -EISDIR;
803fb7
+                if (!S_ISREG(st.st_mode))
803fb7
+                        return -ENOTTY;
803fb7
+
803fb7
+                q = in_search_path(*i, paths.unit_path);
803fb7
+                if (q < 0)
803fb7
+                        return q;
803fb7
+                if (q > 0)
803fb7
+                        continue;
803fb7
+
803fb7
+                if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2))
803fb7
+                        return -ENOMEM;
803fb7
+
803fb7
+                todo[n_todo++] = *i;
803fb7
         }
803fb7
 
803fb7
-        return 0;
803fb7
+        strv_uniq(todo);
803fb7
+
803fb7
+        r = 0;
803fb7
+        STRV_FOREACH(i, todo) {
803fb7
+                _cleanup_free_ char *path = NULL;
803fb7
+
803fb7
+                path = path_make_absolute(basename(*i), config_path);
803fb7
+                if (!path)
803fb7
+                        return -ENOMEM;
803fb7
+
803fb7
+                q = create_symlink(*i, path, force, changes, n_changes);
803fb7
+                if (q < 0 && r >= 0)
803fb7
+                        r = q;
803fb7
+        }
803fb7
+
803fb7
+        return r;
803fb7
+}
803fb7
+
803fb7
+int unit_file_add_dependency(
803fb7
+                UnitFileScope scope,
803fb7
+                bool runtime,
803fb7
+                const char *root_dir,
803fb7
+                char **files,
803fb7
+                const char *target,
803fb7
+                UnitDependency dep,
803fb7
+                bool force,
803fb7
+                UnitFileChange **changes,
803fb7
+                unsigned *n_changes) {
803fb7
+
803fb7
+        _cleanup_lookup_paths_free_ LookupPaths paths = {};
803fb7
+        _cleanup_(install_context_done) InstallContext c = {};
803fb7
+        _cleanup_free_ char *config_path = NULL;
803fb7
+        InstallInfo *i, *target_info;
803fb7
+        char **f;
803fb7
+        int r;
803fb7
+
803fb7
+        assert(scope >= 0);
803fb7
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
+        assert(target);
803fb7
+
803fb7
+        if (!IN_SET(dep, UNIT_WANTS, UNIT_REQUIRES))
803fb7
+                return -EINVAL;
803fb7
+
803fb7
+        if (!unit_name_is_valid(target, UNIT_NAME_ANY))
803fb7
+                return -EINVAL;
803fb7
+
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        r = get_config_path(scope, runtime, root_dir, &config_path);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        r = install_info_discover(scope, &c, root_dir, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS, &target_info);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+        if (target_info->type == UNIT_FILE_TYPE_MASKED)
803fb7
+                return -ESHUTDOWN;
803fb7
+
803fb7
+        assert(target_info->type == UNIT_FILE_TYPE_REGULAR);
803fb7
+
803fb7
+        STRV_FOREACH(f, files) {
803fb7
+                char ***l;
803fb7
+
803fb7
+                r = install_info_discover(scope, &c, root_dir, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i);
803fb7
+                 if (r < 0)
803fb7
+                         return r;
803fb7
+                if (i->type == UNIT_FILE_TYPE_MASKED)
803fb7
+                        return -ESHUTDOWN;
803fb7
+
803fb7
+                assert(i->type == UNIT_FILE_TYPE_REGULAR);
803fb7
+
803fb7
+                /* We didn't actually load anything from the unit
803fb7
+                 * file, but instead just add in our new symlink to
803fb7
+                 * create. */
803fb7
+
803fb7
+                 if (dep == UNIT_WANTS)
803fb7
+                        l = &i->wanted_by;
803fb7
+                 else
803fb7
+                        l = &i->required_by;
803fb7
+
803fb7
+                strv_free(*l);
803fb7
+                *l = strv_new(target_info->name, NULL);
803fb7
+                if (!*l)
803fb7
+                        return -ENOMEM;
803fb7
+         }
803fb7
+
803fb7
+        return install_context_apply(scope, &c, &paths, config_path, root_dir, force, SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes);
803fb7
 }
803fb7
 
803fb7
+
803fb7
 int unit_file_enable(
803fb7
                 UnitFileScope scope,
803fb7
                 bool runtime,
803fb7
@@ -1590,13 +1841,18 @@ int unit_file_enable(
803fb7
 
803fb7
         _cleanup_lookup_paths_free_ LookupPaths paths = {};
803fb7
         _cleanup_(install_context_done) InstallContext c = {};
803fb7
-        char **i;
803fb7
         _cleanup_free_ char *config_path = NULL;
803fb7
+        InstallInfo *i;
803fb7
+        char **f;
803fb7
         int r;
803fb7
 
803fb7
         assert(scope >= 0);
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
 
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
         r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
@@ -1605,21 +1861,14 @@ int unit_file_enable(
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        STRV_FOREACH(i, files) {
803fb7
-                UnitFileState state;
803fb7
-
803fb7
-                /* We only want to know if this unit is masked, so we ignore
803fb7
-                 * errors from unit_file_get_state, deferring other checks.
803fb7
-                 * This allows templated units to be enabled on the fly. */
803fb7
-                state = unit_file_get_state(scope, root_dir, *i);
803fb7
-                if (state == UNIT_FILE_MASKED || state == UNIT_FILE_MASKED_RUNTIME) {
803fb7
-                        log_error("Failed to enable unit: Unit %s is masked", *i);
803fb7
-                        return -ENOTSUP;
803fb7
-                }
803fb7
-
803fb7
-                r = install_info_add_auto(&c, *i);
803fb7
+        STRV_FOREACH(f, files) {
803fb7
+                r = install_info_discover(scope, &c, root_dir, &paths, *f, SEARCH_LOAD, &i);
803fb7
                 if (r < 0)
803fb7
                         return r;
803fb7
+                if (i->type == UNIT_FILE_TYPE_MASKED)
803fb7
+                        return -ESHUTDOWN;
803fb7
+
803fb7
+                assert(i->type == UNIT_FILE_TYPE_REGULAR);
803fb7
         }
803fb7
 
803fb7
         /* This will return the number of symlink rules that were
803fb7
@@ -1627,7 +1876,7 @@ int unit_file_enable(
803fb7
         useful to determine whether the passed files had any
803fb7
         installation data at all. */
803fb7
 
803fb7
-        return install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes);
803fb7
+        return install_context_apply(scope, &c, &paths, config_path, root_dir, force, SEARCH_LOAD, changes, n_changes);
803fb7
 }
803fb7
 
803fb7
 int unit_file_disable(
803fb7
@@ -1640,14 +1889,18 @@ int unit_file_disable(
803fb7
 
803fb7
         _cleanup_lookup_paths_free_ LookupPaths paths = {};
803fb7
         _cleanup_(install_context_done) InstallContext c = {};
803fb7
-        char **i;
803fb7
         _cleanup_free_ char *config_path = NULL;
803fb7
         _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
803fb7
-        int r, q;
803fb7
+        char **i;
803fb7
+        int r;
803fb7
 
803fb7
         assert(scope >= 0);
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
 
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
         r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
@@ -1657,18 +1910,19 @@ int unit_file_disable(
803fb7
                 return r;
803fb7
 
803fb7
         STRV_FOREACH(i, files) {
803fb7
-                r = install_info_add_auto(&c, *i);
803fb7
+                if (!unit_name_is_valid(*i, UNIT_NAME_ANY))
803fb7
+                        return -EINVAL;
803fb7
+
803fb7
+                r = install_info_add(&c, *i, NULL, NULL);
803fb7
                 if (r < 0)
803fb7
                         return r;
803fb7
         }
803fb7
 
803fb7
-        r = install_context_mark_for_removal(&c, &paths, &remove_symlinks_to, config_path, root_dir);
803fb7
-
803fb7
-        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
803fb7
-        if (r >= 0)
803fb7
-                r = q;
803fb7
+        r = install_context_mark_for_removal(scope, &c, &paths, &remove_symlinks_to, config_path, root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
 
803fb7
-        return r;
803fb7
+        return remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
803fb7
 }
803fb7
 
803fb7
 int unit_file_reenable(
803fb7
@@ -1679,21 +1933,30 @@ int unit_file_reenable(
803fb7
                 bool force,
803fb7
                 UnitFileChange **changes,
803fb7
                 unsigned *n_changes) {
803fb7
+
803fb7
+        char **n;
803fb7
         int r;
803fb7
+        size_t l, i;
803fb7
+
803fb7
+        /* First, we invoke the disable command with only the basename... */
803fb7
+        l = strv_length(files);
803fb7
+        n = newa(char*, l+1);
803fb7
+        for (i = 0; i < l; i++)
803fb7
+                n[i] = basename(files[i]);
803fb7
+        n[i] = NULL;
803fb7
 
803fb7
-        r = unit_file_disable(scope, runtime, root_dir, files,
803fb7
-                              changes, n_changes);
803fb7
+        r = unit_file_disable(scope, runtime, root_dir, n, changes, n_changes);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        return unit_file_enable(scope, runtime, root_dir, files, force,
803fb7
-                                changes, n_changes);
803fb7
+        /* But the enable command with the full name */
803fb7
+        return unit_file_enable(scope, runtime, root_dir, files, force, changes, n_changes);
803fb7
 }
803fb7
 
803fb7
 int unit_file_set_default(
803fb7
                 UnitFileScope scope,
803fb7
                 const char *root_dir,
803fb7
-                const char *file,
803fb7
+                const char *name,
803fb7
                 bool force,
803fb7
                 UnitFileChange **changes,
803fb7
                 unsigned *n_changes) {
803fb7
@@ -1701,42 +1964,40 @@ int unit_file_set_default(
803fb7
         _cleanup_lookup_paths_free_ LookupPaths paths = {};
803fb7
         _cleanup_(install_context_done) InstallContext c = {};
803fb7
         _cleanup_free_ char *config_path = NULL;
803fb7
-        char *path;
803fb7
+        InstallInfo *i;
803fb7
+        const char *path;
803fb7
         int r;
803fb7
-        InstallInfo *i = NULL;
803fb7
 
803fb7
         assert(scope >= 0);
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
-        assert(file);
803fb7
+        assert(name);
803fb7
 
803fb7
-        if (unit_name_to_type(file) != UNIT_TARGET)
803fb7
+        if (unit_name_to_type(name) != UNIT_TARGET)
803fb7
+                return -EINVAL;
803fb7
+        if (streq(name, SPECIAL_DEFAULT_TARGET))
803fb7
                 return -EINVAL;
803fb7
 
803fb7
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        r = get_config_path(scope, false, root_dir, &config_path);
803fb7
+        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        r = install_info_add_auto(&c, file);
803fb7
+        r = get_config_path(scope, false, root_dir, &config_path);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        assert_se(i = ordered_hashmap_first(c.will_install));
803fb7
-
803fb7
-        r = unit_file_search(&c, i, &paths, root_dir, false, true, NULL);
803fb7
+        r = install_info_discover(scope, &c, root_dir, &paths, name, 0, &i);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
+        if (i->type == UNIT_FILE_TYPE_MASKED)
803fb7
+                return -ESHUTDOWN;
803fb7
 
803fb7
         path = strjoina(config_path, "/" SPECIAL_DEFAULT_TARGET);
803fb7
 
803fb7
-        r = create_symlink(i->path, path, force, changes, n_changes);
803fb7
-        if (r < 0)
803fb7
-                return r;
803fb7
-
803fb7
-        return 0;
803fb7
+        return create_symlink(i->path, path, force, changes, n_changes);
803fb7
 }
803fb7
 
803fb7
 int unit_file_get_default(
803fb7
@@ -1745,127 +2006,100 @@ int unit_file_get_default(
803fb7
                 char **name) {
803fb7
 
803fb7
         _cleanup_lookup_paths_free_ LookupPaths paths = {};
803fb7
-        char **p;
803fb7
+        _cleanup_(install_context_done) InstallContext c = {};
803fb7
+        InstallInfo *i;
803fb7
+        char *n;
803fb7
         int r;
803fb7
 
803fb7
         assert(scope >= 0);
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
         assert(name);
803fb7
 
803fb7
-        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        STRV_FOREACH(p, paths.unit_path) {
803fb7
-                _cleanup_free_ char *path = NULL, *tmp = NULL;
803fb7
-                char *n;
803fb7
-
803fb7
-                path = path_join(root_dir, *p, SPECIAL_DEFAULT_TARGET);
803fb7
-                if (!path)
803fb7
-                        return -ENOMEM;
803fb7
-
803fb7
-                r = readlink_malloc(path, &tmp);
803fb7
-                if (r == -ENOENT)
803fb7
-                        continue;
803fb7
-                else if (r == -EINVAL)
803fb7
-                        /* not a symlink */
803fb7
-                        n = strdup(SPECIAL_DEFAULT_TARGET);
803fb7
-                else if (r < 0)
803fb7
-                        return r;
803fb7
-                else
803fb7
-                        n = strdup(basename(tmp));
803fb7
+        r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
 
803fb7
-                if (!n)
803fb7
-                        return -ENOMEM;
803fb7
+        r = install_info_discover(scope, &c, root_dir, &paths, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+        if (i->type == UNIT_FILE_TYPE_MASKED)
803fb7
+                return -ESHUTDOWN;
803fb7
 
803fb7
-                *name = n;
803fb7
-                return 0;
803fb7
-        }
803fb7
+        n = strdup(i->name);
803fb7
+        if (!n)
803fb7
+                return -ENOMEM;
803fb7
 
803fb7
-        return -ENOENT;
803fb7
+        *name = n;
803fb7
+        return 0;
803fb7
 }
803fb7
 
803fb7
 UnitFileState unit_file_lookup_state(
803fb7
                 UnitFileScope scope,
803fb7
                 const char *root_dir,
803fb7
                 const LookupPaths *paths,
803fb7
-                const char *name) {
803fb7
-
803fb7
-        UnitFileState state = _UNIT_FILE_STATE_INVALID;
803fb7
-        char **i;
803fb7
-        _cleanup_free_ char *path = NULL;
803fb7
+                const char *name,
803fb7
+                UnitFileState *ret) {
803fb7
+        _cleanup_(install_context_done) InstallContext c = {};
803fb7
+        InstallInfo *i;
803fb7
+        UnitFileState state;
803fb7
         int r;
803fb7
 
803fb7
         assert(paths);
803fb7
+        assert(name);
803fb7
 
803fb7
-        if (!unit_name_is_valid(name, TEMPLATE_VALID))
803fb7
+        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
803fb7
                 return -EINVAL;
803fb7
 
803fb7
-        STRV_FOREACH(i, paths->unit_path) {
803fb7
-                struct stat st;
803fb7
-                char *partial;
803fb7
-                bool also = false;
803fb7
-
803fb7
-                free(path);
803fb7
-                path = path_join(root_dir, *i, name);
803fb7
-                if (!path)
803fb7
-                        return -ENOMEM;
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
 
803fb7
-                if (root_dir)
803fb7
-                        partial = path + strlen(root_dir);
803fb7
-                else
803fb7
-                        partial = path;
803fb7
+        r = install_info_discover(scope, &c, root_dir, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
 
803fb7
-                /*
803fb7
-                 * Search for a unit file in our default paths, to
803fb7
-                 * be sure, that there are no broken symlinks.
803fb7
-                 */
803fb7
-                if (lstat(path, &st) < 0) {
803fb7
-                        r = -errno;
803fb7
-                        if (errno != ENOENT)
803fb7
-                                return r;
803fb7
+        /* Shortcut things, if the caller just wants to know if this unit exists. */
803fb7
+        if (!ret)
803fb7
+                return 0;
803fb7
 
803fb7
-                        if (!unit_name_is_instance(name))
803fb7
-                                continue;
803fb7
-                } else {
803fb7
-                        if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
803fb7
-                                return -ENOENT;
803fb7
+        switch (i->type) {
803fb7
 
803fb7
-                        r = null_or_empty_path(path);
803fb7
-                        if (r < 0 && r != -ENOENT)
803fb7
-                                return r;
803fb7
-                        else if (r > 0) {
803fb7
-                                state = path_startswith(*i, "/run") ?
803fb7
-                                        UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
803fb7
-                                return state;
803fb7
-                        }
803fb7
-                }
803fb7
+        case UNIT_FILE_TYPE_MASKED:
803fb7
+                state = path_startswith(i->path, "/run") ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
803fb7
+                break;
803fb7
 
803fb7
-                r = find_symlinks_in_scope(scope, root_dir, name, &state);
803fb7
+        case UNIT_FILE_TYPE_REGULAR:
803fb7
+                r = find_symlinks_in_scope(scope, root_dir, i->name, &state);
803fb7
                 if (r < 0)
803fb7
                         return r;
803fb7
-                else if (r > 0)
803fb7
-                        return state;
803fb7
-
803fb7
-                r = unit_file_can_install(paths, root_dir, partial, true, &also);
803fb7
-                if (r < 0 && errno != ENOENT)
803fb7
-                        return r;
803fb7
-                else if (r > 0)
803fb7
-                        return UNIT_FILE_DISABLED;
803fb7
-                else if (r == 0) {
803fb7
-                        if (also)
803fb7
-                                return UNIT_FILE_INDIRECT;
803fb7
-                        return UNIT_FILE_STATIC;
803fb7
+                if (r == 0) {
803fb7
+                        if (UNIT_FILE_INSTALL_INFO_HAS_RULES(i))
803fb7
+                                state = UNIT_FILE_DISABLED;
803fb7
+                        else if (UNIT_FILE_INSTALL_INFO_HAS_ALSO(i))
803fb7
+                                state = UNIT_FILE_INDIRECT;
803fb7
+                        else
803fb7
+                                state = UNIT_FILE_STATIC;
803fb7
                 }
803fb7
+
803fb7
+                break;
803fb7
+
803fb7
+        default:
803fb7
+                assert_not_reached("Unexpect unit file type.");
803fb7
         }
803fb7
 
803fb7
-        return r < 0 ? r : state;
803fb7
+        *ret = state;
803fb7
+        return 0;
803fb7
 }
803fb7
 
803fb7
-UnitFileState unit_file_get_state(
803fb7
+int unit_file_get_state(
803fb7
                 UnitFileScope scope,
803fb7
                 const char *root_dir,
803fb7
-                const char *name) {
803fb7
+                const char *name,
803fb7
+                UnitFileState *ret) {
803fb7
 
803fb7
         _cleanup_lookup_paths_free_ LookupPaths paths = {};
803fb7
         int r;
803fb7
@@ -1874,14 +2108,15 @@ UnitFileState unit_file_get_state(
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
         assert(name);
803fb7
 
803fb7
-        if (root_dir && scope != UNIT_FILE_SYSTEM)
803fb7
-                return -EINVAL;
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
 
803fb7
         r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
-        return unit_file_lookup_state(scope, root_dir, &paths, name);
803fb7
+        return unit_file_lookup_state(scope, root_dir, &paths, name, ret);
803fb7
 }
803fb7
 
803fb7
 int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name) {
803fb7
@@ -1893,6 +2128,13 @@ int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
         assert(name);
803fb7
 
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        if (!unit_name_is_valid(name, UNIT_NAME_ANY))
803fb7
+                return -EINVAL;
803fb7
+
803fb7
         if (scope == UNIT_FILE_SYSTEM)
803fb7
                 r = conf_files_list(&files, ".preset", root_dir,
803fb7
                                     "/etc/systemd/system-preset",
803fb7
@@ -1909,13 +2151,14 @@ int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char
803fb7
                                     "/usr/lib/systemd/user-preset",
803fb7
                                     NULL);
803fb7
         else
803fb7
-                return 1;
803fb7
+                return 1; /* Default is "enable" */
803fb7
 
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
 
803fb7
         STRV_FOREACH(p, files) {
803fb7
                 _cleanup_fclose_ FILE *f;
803fb7
+                char line[LINE_MAX];
803fb7
 
803fb7
                 f = fopen(*p, "re");
803fb7
                 if (!f) {
803fb7
@@ -1925,39 +2168,38 @@ int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char
803fb7
                         return -errno;
803fb7
                 }
803fb7
 
803fb7
-                for (;;) {
803fb7
-                        char line[LINE_MAX], *l;
803fb7
-
803fb7
-                        if (!fgets(line, sizeof(line), f))
803fb7
-                                break;
803fb7
+                FOREACH_LINE(line, f, return -errno) {
803fb7
+                        const char *parameter;
803fb7
+                        char *l;
803fb7
 
803fb7
                         l = strstrip(line);
803fb7
-                        if (!*l)
803fb7
-                                continue;
803fb7
 
803fb7
-                        if (strchr(COMMENTS "\n", *l))
803fb7
+                        if (isempty(l))
803fb7
+                                continue;
803fb7
+                        if (strchr(COMMENTS, *l))
803fb7
                                 continue;
803fb7
 
803fb7
-                        if (first_word(l, "enable")) {
803fb7
-                                l += 6;
803fb7
-                                l += strspn(l, WHITESPACE);
803fb7
-
803fb7
-                                if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
803fb7
+                        parameter = first_word(l, "enable");
803fb7
+                        if (parameter) {
803fb7
+                                if (fnmatch(parameter, name, FNM_NOESCAPE) == 0) {
803fb7
                                         log_debug("Preset file says enable %s.", name);
803fb7
                                         return 1;
803fb7
                                 }
803fb7
 
803fb7
-                        } else if (first_word(l, "disable")) {
803fb7
-                                l += 7;
803fb7
-                                l += strspn(l, WHITESPACE);
803fb7
+                                continue;
803fb7
+                        }
803fb7
 
803fb7
-                                if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
803fb7
+                        parameter = first_word(l, "disable");
803fb7
+                        if (parameter) {
803fb7
+                                if (fnmatch(parameter, name, FNM_NOESCAPE) == 0) {
803fb7
                                         log_debug("Preset file says disable %s.", name);
803fb7
                                         return 0;
803fb7
                                 }
803fb7
 
803fb7
-                        } else
803fb7
-                                log_debug("Couldn't parse line '%s'", l);
803fb7
+                                continue;
803fb7
+                        }
803fb7
+
803fb7
+                        log_debug("Couldn't parse line '%s'", l);
803fb7
                 }
803fb7
         }
803fb7
 
803fb7
@@ -1966,6 +2208,86 @@ int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char
803fb7
         return 1;
803fb7
 }
803fb7
 
803fb7
+static int execute_preset(
803fb7
+                UnitFileScope scope,
803fb7
+                InstallContext *plus,
803fb7
+                InstallContext *minus,
803fb7
+                const LookupPaths *paths,
803fb7
+                const char *config_path,
803fb7
+                const char *root_dir,
803fb7
+                char **files,
803fb7
+                UnitFilePresetMode mode,
803fb7
+                bool force,
803fb7
+                UnitFileChange **changes,
803fb7
+                unsigned *n_changes) {
803fb7
+
803fb7
+        int r;
803fb7
+
803fb7
+        assert(plus);
803fb7
+        assert(minus);
803fb7
+        assert(paths);
803fb7
+        assert(config_path);
803fb7
+
803fb7
+        if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) {
803fb7
+                _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
803fb7
+
803fb7
+                r = install_context_mark_for_removal(scope, minus, paths, &remove_symlinks_to, config_path, root_dir);
803fb7
+                if (r < 0)
803fb7
+                        return r;
803fb7
+
803fb7
+                r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
803fb7
+        } else
803fb7
+                r = 0;
803fb7
+
803fb7
+        if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) {
803fb7
+                int q;
803fb7
+
803fb7
+                /* Returns number of symlinks that where supposed to be installed. */
803fb7
+                q = install_context_apply(scope, plus, paths, config_path, root_dir, force, SEARCH_LOAD, changes, n_changes);
803fb7
+                if (r >= 0) {
803fb7
+                        if (q < 0)
803fb7
+                                r = q;
803fb7
+                        else
803fb7
+                                r+= q;
803fb7
+                }
803fb7
+        }
803fb7
+
803fb7
+        return r;
803fb7
+}
803fb7
+
803fb7
+static int preset_prepare_one(
803fb7
+                UnitFileScope scope,
803fb7
+                InstallContext *plus,
803fb7
+                InstallContext *minus,
803fb7
+                LookupPaths *paths,
803fb7
+                const char *root_dir,
803fb7
+                UnitFilePresetMode mode,
803fb7
+                const char *name) {
803fb7
+
803fb7
+        InstallInfo *i;
803fb7
+        int r;
803fb7
+
803fb7
+        if (install_info_find(plus, name) ||
803fb7
+            install_info_find(minus, name))
803fb7
+                return 0;
803fb7
+
803fb7
+        r = unit_file_query_preset(scope, root_dir, name);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        if (r > 0) {
803fb7
+                r = install_info_discover(scope, plus, root_dir, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i);
803fb7
+                if (r < 0)
803fb7
+                        return r;
803fb7
+
803fb7
+                if (i->type == UNIT_FILE_TYPE_MASKED)
803fb7
+                        return -ESHUTDOWN;
803fb7
+        } else
803fb7
+                r = install_info_discover(scope, minus, root_dir, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i);
803fb7
+
803fb7
+        return r;
803fb7
+}
803fb7
+
803fb7
 int unit_file_preset(
803fb7
                 UnitFileScope scope,
803fb7
                 bool runtime,
803fb7
@@ -1980,12 +2302,16 @@ int unit_file_preset(
803fb7
         _cleanup_lookup_paths_free_ LookupPaths paths = {};
803fb7
         _cleanup_free_ char *config_path = NULL;
803fb7
         char **i;
803fb7
-        int r, q;
803fb7
+        int r;
803fb7
 
803fb7
         assert(scope >= 0);
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
         assert(mode < _UNIT_FILE_PRESET_MAX);
803fb7
 
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
         r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
@@ -1995,44 +2321,15 @@ int unit_file_preset(
803fb7
                 return r;
803fb7
 
803fb7
         STRV_FOREACH(i, files) {
803fb7
-
803fb7
-                if (!unit_name_is_valid(*i, TEMPLATE_VALID))
803fb7
+                if (!unit_name_is_valid(*i, UNIT_NAME_ANY))
803fb7
                         return -EINVAL;
803fb7
 
803fb7
-                r = unit_file_query_preset(scope, root_dir, *i);
803fb7
-                if (r < 0)
803fb7
-                        return r;
803fb7
-
803fb7
-                if (r && mode != UNIT_FILE_PRESET_DISABLE_ONLY)
803fb7
-                        r = install_info_add_auto(&plus, *i);
803fb7
-                else if (!r && mode != UNIT_FILE_PRESET_ENABLE_ONLY)
803fb7
-                        r = install_info_add_auto(&minus, *i);
803fb7
-                else
803fb7
-                        r = 0;
803fb7
+                r = preset_prepare_one(scope, &plus, &minus, &paths, root_dir, mode, *i);
803fb7
                 if (r < 0)
803fb7
                         return r;
803fb7
         }
803fb7
 
803fb7
-        r = 0;
803fb7
-
803fb7
-        if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) {
803fb7
-                _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
803fb7
-
803fb7
-                r = install_context_mark_for_removal(&minus, &paths, &remove_symlinks_to, config_path, root_dir);
803fb7
-
803fb7
-                q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
803fb7
-                if (r == 0)
803fb7
-                        r = q;
803fb7
-        }
803fb7
-
803fb7
-        if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) {
803fb7
-                /* Returns number of symlinks that where supposed to be installed. */
803fb7
-                q = install_context_apply(&plus, &paths, config_path, root_dir, force, changes, n_changes);
803fb7
-                if (r == 0)
803fb7
-                        r = q;
803fb7
-        }
803fb7
-
803fb7
-        return r;
803fb7
+        return execute_preset(scope, &plus, &minus, &paths, config_path, root_dir, files, mode, force, changes, n_changes);
803fb7
 }
803fb7
 
803fb7
 int unit_file_preset_all(
803fb7
@@ -2048,12 +2345,16 @@ int unit_file_preset_all(
803fb7
         _cleanup_lookup_paths_free_ LookupPaths paths = {};
803fb7
         _cleanup_free_ char *config_path = NULL;
803fb7
         char **i;
803fb7
-        int r, q;
803fb7
+        int r;
803fb7
 
803fb7
         assert(scope >= 0);
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
         assert(mode < _UNIT_FILE_PRESET_MAX);
803fb7
 
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
         r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
         if (r < 0)
803fb7
                 return r;
803fb7
@@ -2092,48 +2393,21 @@ int unit_file_preset_all(
803fb7
                         if (hidden_file(de->d_name))
803fb7
                                 continue;
803fb7
 
803fb7
-                        if (!unit_name_is_valid(de->d_name, TEMPLATE_VALID))
803fb7
+                        if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
803fb7
                                 continue;
803fb7
 
803fb7
                         dirent_ensure_type(d, de);
803fb7
 
803fb7
-                        if (de->d_type != DT_REG)
803fb7
+                        if (!IN_SET(de->d_type, DT_LNK, DT_REG))
803fb7
                                 continue;
803fb7
 
803fb7
-                        r = unit_file_query_preset(scope, root_dir, de->d_name);
803fb7
-                        if (r < 0)
803fb7
-                                return r;
803fb7
-
803fb7
-                        if (r && mode != UNIT_FILE_PRESET_DISABLE_ONLY)
803fb7
-                                r = install_info_add_auto(&plus, de->d_name);
803fb7
-                        else if (!r && mode != UNIT_FILE_PRESET_ENABLE_ONLY)
803fb7
-                                r = install_info_add_auto(&minus, de->d_name);
803fb7
-                        else
803fb7
-                                r = 0;
803fb7
+                        r = preset_prepare_one(scope, &plus, &minus, &paths, root_dir, mode, de->d_name);
803fb7
                         if (r < 0)
803fb7
                                 return r;
803fb7
                 }
803fb7
         }
803fb7
 
803fb7
-        r = 0;
803fb7
-
803fb7
-        if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) {
803fb7
-                _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
803fb7
-
803fb7
-                r = install_context_mark_for_removal(&minus, &paths, &remove_symlinks_to, config_path, root_dir);
803fb7
-
803fb7
-                q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, NULL);
803fb7
-                if (r == 0)
803fb7
-                        r = q;
803fb7
-        }
803fb7
-
803fb7
-        if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) {
803fb7
-                q = install_context_apply(&plus, &paths, config_path, root_dir, force, changes, n_changes);
803fb7
-                if (r == 0)
803fb7
-                        r = q;
803fb7
-        }
803fb7
-
803fb7
-        return r;
803fb7
+        return execute_preset(scope, &plus, &minus, &paths, config_path, root_dir, NULL, mode, force, changes, n_changes);
803fb7
 }
803fb7
 
803fb7
 static void unit_file_list_free_one(UnitFileList *f) {
803fb7
@@ -2144,6 +2418,17 @@ static void unit_file_list_free_one(UnitFileList *f) {
803fb7
         free(f);
803fb7
 }
803fb7
 
803fb7
+Hashmap* unit_file_list_free(Hashmap *h) {
803fb7
+        UnitFileList *i;
803fb7
+
803fb7
+        while ((i = hashmap_steal_first(h)))
803fb7
+                unit_file_list_free_one(i);
803fb7
+
803fb7
+        hashmap_free(h);
803fb7
+
803fb7
+        return NULL;
803fb7
+}
803fb7
+
803fb7
 DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free_one);
803fb7
 
803fb7
 int unit_file_get_list(
803fb7
@@ -2159,14 +2444,9 @@ int unit_file_get_list(
803fb7
         assert(scope < _UNIT_FILE_SCOPE_MAX);
803fb7
         assert(h);
803fb7
 
803fb7
-        if (root_dir && scope != UNIT_FILE_SYSTEM)
803fb7
-                return -EINVAL;
803fb7
-
803fb7
-        if (root_dir) {
803fb7
-                r = access(root_dir, F_OK);
803fb7
-                if (r < 0)
803fb7
-                        return -errno;
803fb7
-        }
803fb7
+        r = verify_root_dir(scope, &root_dir);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
 
803fb7
         r = lookup_paths_init_from_scope(&paths, scope, root_dir);
803fb7
         if (r < 0)
803fb7
@@ -2191,7 +2471,6 @@ int unit_file_get_list(
803fb7
                 for (;;) {
803fb7
                         _cleanup_(unit_file_list_free_onep) UnitFileList *f = NULL;
803fb7
                         struct dirent *de;
803fb7
-                        _cleanup_free_ char *path = NULL;
803fb7
 
803fb7
                         errno = 0;
803fb7
                         de = readdir(d);
803fb7
@@ -2204,7 +2483,7 @@ int unit_file_get_list(
803fb7
                         if (hidden_file(de->d_name))
803fb7
                                 continue;
803fb7
 
803fb7
-                        if (!unit_name_is_valid(de->d_name, TEMPLATE_VALID))
803fb7
+                        if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
803fb7
                                 continue;
803fb7
 
803fb7
                         if (hashmap_get(h, de->d_name))
803fb7
@@ -2223,44 +2502,14 @@ int unit_file_get_list(
803fb7
                         if (!f->path)
803fb7
                                 return -ENOMEM;
803fb7
 
803fb7
-                        r = null_or_empty_path(f->path);
803fb7
-                        if (r < 0 && r != -ENOENT)
803fb7
-                                return r;
803fb7
-                        else if (r > 0) {
803fb7
-                                f->state =
803fb7
-                                        path_startswith(*i, "/run") ?
803fb7
-                                        UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
803fb7
-                                goto found;
803fb7
-                        }
803fb7
-
803fb7
-                        r = find_symlinks_in_scope(scope, root_dir, de->d_name, &f->state);
803fb7
+                        r = unit_file_lookup_state(scope, root_dir, &paths, basename(f->path), &f->state);
803fb7
                         if (r < 0)
803fb7
-                                return r;
803fb7
-                        else if (r > 0) {
803fb7
-                                f->state = UNIT_FILE_ENABLED;
803fb7
-                                goto found;
803fb7
-                        }
803fb7
-
803fb7
-                        path = path_make_absolute(de->d_name, *i);
803fb7
-                        if (!path)
803fb7
-                                return -ENOMEM;
803fb7
+                                f->state = UNIT_FILE_BAD;
803fb7
 
803fb7
-                        r = unit_file_can_install(&paths, root_dir, path, true, NULL);
803fb7
-                        if (r == -EINVAL ||  /* Invalid setting? */
803fb7
-                            r == -EBADMSG || /* Invalid format? */
803fb7
-                            r == -ENOENT     /* Included file not found? */)
803fb7
-                                f->state = UNIT_FILE_INVALID;
803fb7
-                        else if (r < 0)
803fb7
-                                return r;
803fb7
-                        else if (r > 0)
803fb7
-                                f->state = UNIT_FILE_DISABLED;
803fb7
-                        else
803fb7
-                                f->state = UNIT_FILE_STATIC;
803fb7
-
803fb7
-                found:
803fb7
                         r = hashmap_put(h, basename(f->path), f);
803fb7
                         if (r < 0)
803fb7
                                 return r;
803fb7
+
803fb7
                         f = NULL; /* prevent cleanup */
803fb7
                 }
803fb7
         }
803fb7
@@ -2278,7 +2527,7 @@ static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = {
803fb7
         [UNIT_FILE_STATIC] = "static",
803fb7
         [UNIT_FILE_DISABLED] = "disabled",
803fb7
         [UNIT_FILE_INDIRECT] = "indirect",
803fb7
-        [UNIT_FILE_INVALID] = "invalid",
803fb7
+        [UNIT_FILE_BAD] = "bad",
803fb7
 };
803fb7
 
803fb7
 DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState);
803fb7
diff --git a/src/shared/install.h b/src/shared/install.h
803fb7
index d729e6ed1..87a40b67c 100644
803fb7
--- a/src/shared/install.h
803fb7
+++ b/src/shared/install.h
803fb7
@@ -24,6 +24,7 @@
803fb7
 #include "hashmap.h"
803fb7
 #include "unit-name.h"
803fb7
 #include "path-lookup.h"
803fb7
+#include "strv.h"
803fb7
 
803fb7
 typedef enum UnitFileScope {
803fb7
         UNIT_FILE_SYSTEM,
803fb7
@@ -43,7 +44,7 @@ typedef enum UnitFileState {
803fb7
         UNIT_FILE_STATIC,
803fb7
         UNIT_FILE_DISABLED,
803fb7
         UNIT_FILE_INDIRECT,
803fb7
-        UNIT_FILE_INVALID,
803fb7
+        UNIT_FILE_BAD,
803fb7
         _UNIT_FILE_STATE_MAX,
803fb7
         _UNIT_FILE_STATE_INVALID = -1
803fb7
 } UnitFileState;
803fb7
@@ -74,6 +75,14 @@ typedef struct UnitFileList {
803fb7
         UnitFileState state;
803fb7
 } UnitFileList;
803fb7
 
803fb7
+typedef enum UnitFileType {
803fb7
+        UNIT_FILE_TYPE_REGULAR,
803fb7
+        UNIT_FILE_TYPE_SYMLINK,
803fb7
+        UNIT_FILE_TYPE_MASKED,
803fb7
+       _UNIT_FILE_TYPE_MAX,
803fb7
+        _UNIT_FILE_TYPE_INVALID = -1,
803fb7
+} UnitFileType;
803fb7
+
803fb7
 typedef struct {
803fb7
         char *name;
803fb7
         char *path;
803fb7
@@ -85,8 +94,26 @@ typedef struct {
803fb7
         char **also;
803fb7
 
803fb7
         char *default_instance;
803fb7
+
803fb7
+        UnitFileType type;
803fb7
+
803fb7
+        char *symlink_target;
803fb7
 } InstallInfo;
803fb7
 
803fb7
+static inline bool UNIT_FILE_INSTALL_INFO_HAS_RULES(InstallInfo *i) {
803fb7
+        assert(i);
803fb7
+
803fb7
+        return !strv_isempty(i->aliases) ||
803fb7
+               !strv_isempty(i->wanted_by) ||
803fb7
+               !strv_isempty(i->required_by);
803fb7
+}
803fb7
+
803fb7
+static inline bool UNIT_FILE_INSTALL_INFO_HAS_ALSO(InstallInfo *i) {
803fb7
+        assert(i);
803fb7
+
803fb7
+        return !strv_isempty(i->also);
803fb7
+}
803fb7
+
803fb7
 int unit_file_enable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes);
803fb7
 int unit_file_disable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes);
803fb7
 int unit_file_reenable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes);
803fb7
@@ -97,21 +124,14 @@ int unit_file_mask(UnitFileScope scope, bool runtime, const char *root_dir, char
803fb7
 int unit_file_unmask(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes);
803fb7
 int unit_file_set_default(UnitFileScope scope, const char *root_dir, const char *file, bool force, UnitFileChange **changes, unsigned *n_changes);
803fb7
 int unit_file_get_default(UnitFileScope scope, const char *root_dir, char **name);
803fb7
-int unit_file_add_dependency(UnitFileScope scope, bool runtime, const char *root_dir, char **files, char *target, UnitDependency dep, bool force, UnitFileChange **changes, unsigned *n_changes);
803fb7
-
803fb7
-UnitFileState unit_file_lookup_state(
803fb7
-                UnitFileScope scope,
803fb7
-                const char *root_dir,
803fb7
-                const LookupPaths *paths,
803fb7
-                const char *name);
803fb7
-UnitFileState unit_file_get_state(
803fb7
-                UnitFileScope scope,
803fb7
-                const char *root_dir,
803fb7
-                const char *filename);
803fb7
+int unit_file_add_dependency(UnitFileScope scope, bool runtime, const char *root_dir, char **files, const char *target, UnitDependency dep, bool force, UnitFileChange **changes, unsigned *n_changes);
803fb7
+
803fb7
+int unit_file_lookup_state(UnitFileScope scope, const char *root_dir,const LookupPaths *paths, const char *name, UnitFileState *ret);
803fb7
+int unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename, UnitFileState *ret);
803fb7
 
803fb7
 int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h);
803fb7
+Hashmap* unit_file_list_free(Hashmap *h);
803fb7
 
803fb7
-void unit_file_list_free(Hashmap *h);
803fb7
 int unit_file_changes_add(UnitFileChange **changes, unsigned *n_changes, UnitFileChangeType type, const char *path, const char *source);
803fb7
 void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes);
803fb7
 
803fb7
diff --git a/src/shared/path-util.c b/src/shared/path-util.c
803fb7
index d5510bf56..1181ffb9d 100644
803fb7
--- a/src/shared/path-util.c
803fb7
+++ b/src/shared/path-util.c
803fb7
@@ -704,3 +704,37 @@ int fsck_exists(const char *fstype) {
803fb7
 
803fb7
         return 0;
803fb7
 }
803fb7
+
803fb7
+char *prefix_root(const char *root, const char *path) {
803fb7
+        char *n, *p;
803fb7
+        size_t l;
803fb7
+
803fb7
+        /* If root is passed, prefixes path with it. Otherwise returns
803fb7
+         * it as is. */
803fb7
+
803fb7
+        assert(path);
803fb7
+
803fb7
+        /* First, drop duplicate prefixing slashes from the path */
803fb7
+        while (path[0] == '/' && path[1] == '/')
803fb7
+                path++;
803fb7
+
803fb7
+        if (isempty(root) || path_equal(root, "/"))
803fb7
+                return strdup(path);
803fb7
+
803fb7
+        l = strlen(root) + 1 + strlen(path) + 1;
803fb7
+
803fb7
+        n = new(char, l);
803fb7
+        if (!n)
803fb7
+                return NULL;
803fb7
+
803fb7
+        p = stpcpy(n, root);
803fb7
+
803fb7
+        while (p > n && p[-1] == '/')
803fb7
+                p--;
803fb7
+
803fb7
+        if (path[0] != '/')
803fb7
+                *(p++) = '/';
803fb7
+
803fb7
+        strcpy(p, path);
803fb7
+        return n;
803fb7
+}
803fb7
diff --git a/src/shared/path-util.h b/src/shared/path-util.h
803fb7
index ca81b49cb..71bb740e9 100644
803fb7
--- a/src/shared/path-util.h
803fb7
+++ b/src/shared/path-util.h
803fb7
@@ -63,6 +63,33 @@ bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool
803fb7
 
803fb7
 int fsck_exists(const char *fstype);
803fb7
 
803fb7
+char *prefix_root(const char *root, const char *path);
803fb7
+
803fb7
+/* Similar to prefix_root(), but returns an alloca() buffer, or
803fb7
+ * possibly a const pointer into the path parameter */
803fb7
+#define prefix_roota(root, path)                                        \
803fb7
+        ({                                                              \
803fb7
+                const char* _path = (path), *_root = (root), *_ret;     \
803fb7
+                char *_p, *_n;                                          \
803fb7
+                size_t _l;                                              \
803fb7
+                while (_path[0] == '/' && _path[1] == '/')              \
803fb7
+                        _path ++;                                       \
803fb7
+                if (isempty(_root) || path_equal(_root, "/"))           \
803fb7
+                        _ret = _path;                                   \
803fb7
+                else {                                                  \
803fb7
+                        _l = strlen(_root) + 1 + strlen(_path) + 1;     \
803fb7
+                        _n = alloca(_l);                                \
803fb7
+                        _p = stpcpy(_n, _root);                         \
803fb7
+                        while (_p > _n && _p[-1] == '/')                \
803fb7
+                                _p--;                                   \
803fb7
+                        if (_path[0] != '/')                            \
803fb7
+                                *(_p++) = '/';                          \
803fb7
+                        strcpy(_p, _path);                              \
803fb7
+                        _ret = _n;                                      \
803fb7
+                }                                                       \
803fb7
+                _ret;                                                   \
803fb7
+        })
803fb7
+
803fb7
 /* Iterates through the path prefixes of the specified path, going up
803fb7
  * the tree, to root. Also returns "" (and not "/"!) for the root
803fb7
  * directory. Excludes the specified directory itself */
803fb7
diff --git a/src/shared/unit-name.c b/src/shared/unit-name.c
803fb7
index f728af4a8..b7827d14b 100644
803fb7
--- a/src/shared/unit-name.c
803fb7
+++ b/src/shared/unit-name.c
803fb7
@@ -63,16 +63,13 @@ static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
803fb7
 
803fb7
 DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
803fb7
 
803fb7
-bool unit_name_is_valid(const char *n, enum template_valid template_ok) {
803fb7
+bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
803fb7
         const char *e, *i, *at;
803fb7
 
803fb7
-        /* Valid formats:
803fb7
-         *
803fb7
-         *         string@instance.suffix
803fb7
-         *         string.suffix
803fb7
-         */
803fb7
+        assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
803fb7
 
803fb7
-        assert(IN_SET(template_ok, TEMPLATE_VALID, TEMPLATE_INVALID));
803fb7
+        if (_unlikely_(flags == 0))
803fb7
+                return false;
803fb7
 
803fb7
         if (isempty(n))
803fb7
                 return false;
803fb7
@@ -96,15 +93,22 @@ bool unit_name_is_valid(const char *n, enum template_valid template_ok) {
803fb7
                         return false;
803fb7
         }
803fb7
 
803fb7
-        if (at) {
803fb7
-                if (at == n)
803fb7
-                        return false;
803fb7
+        if (at == n)
803fb7
+                return false;
803fb7
 
803fb7
-                if (template_ok != TEMPLATE_VALID && at+1 == e)
803fb7
-                        return false;
803fb7
-        }
803fb7
+        if (flags & UNIT_NAME_PLAIN)
803fb7
+                if (!at)
803fb7
+                        return true;
803fb7
+
803fb7
+        if (flags & UNIT_NAME_INSTANCE)
803fb7
+                if (at && e > at + 1)
803fb7
+                        return true;
803fb7
+
803fb7
+        if (flags & UNIT_NAME_TEMPLATE)
803fb7
+                if (at && e == at + 1)
803fb7
+                        return true;
803fb7
 
803fb7
-        return true;
803fb7
+        return false;
803fb7
 }
803fb7
 
803fb7
 bool unit_instance_is_valid(const char *i) {
803fb7
diff --git a/src/shared/unit-name.h b/src/shared/unit-name.h
803fb7
index 6f139cc4c..436486062 100644
803fb7
--- a/src/shared/unit-name.h
803fb7
+++ b/src/shared/unit-name.h
803fb7
@@ -107,6 +107,13 @@ enum UnitDependency {
803fb7
         _UNIT_DEPENDENCY_INVALID = -1
803fb7
 };
803fb7
 
803fb7
+typedef enum UnitNameFlags {
803fb7
+        UNIT_NAME_PLAIN = 1,      /* Allow foo.service */
803fb7
+        UNIT_NAME_INSTANCE = 2,   /* Allow foo@bar.service */
803fb7
+        UNIT_NAME_TEMPLATE = 4,   /* Allow foo@.service */
803fb7
+        UNIT_NAME_ANY = UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE,
803fb7
+} UnitNameFlags;
803fb7
+
803fb7
 const char *unit_type_to_string(UnitType i) _const_;
803fb7
 UnitType unit_type_from_string(const char *s) _pure_;
803fb7
 
803fb7
@@ -117,12 +124,7 @@ int unit_name_to_instance(const char *n, char **instance);
803fb7
 char* unit_name_to_prefix(const char *n);
803fb7
 char* unit_name_to_prefix_and_instance(const char *n);
803fb7
 
803fb7
-enum template_valid {
803fb7
-        TEMPLATE_INVALID,
803fb7
-        TEMPLATE_VALID,
803fb7
-};
803fb7
-
803fb7
-bool unit_name_is_valid(const char *n, enum template_valid template_ok) _pure_;
803fb7
+bool unit_name_is_valid(const char *n, UnitNameFlags flags) _pure_;
803fb7
 bool unit_prefix_is_valid(const char *p) _pure_;
803fb7
 bool unit_instance_is_valid(const char *i) _pure_;
803fb7
 
803fb7
diff --git a/src/shared/util.c b/src/shared/util.c
803fb7
index a24aa7f93..036677eb4 100644
803fb7
--- a/src/shared/util.c
803fb7
+++ b/src/shared/util.c
803fb7
@@ -1094,6 +1094,27 @@ int readlink_and_canonicalize(const char *p, char **r) {
803fb7
         return 0;
803fb7
 }
803fb7
 
803fb7
+
803fb7
+int readlink_and_make_absolute_root(const char *root, const char *path, char **ret) {
803fb7
+        _cleanup_free_ char *target = NULL, *t = NULL;
803fb7
+        const char *full;
803fb7
+        int r;
803fb7
+
803fb7
+        full = prefix_roota(root, path);
803fb7
+        r = readlink_malloc(full, &target);
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        t = file_in_same_dir(path, target);
803fb7
+        if (!t)
803fb7
+                return -ENOMEM;
803fb7
+
803fb7
+        *ret = t;
803fb7
+        t = NULL;
803fb7
+
803fb7
+        return 0;
803fb7
+}
803fb7
+
803fb7
 int reset_all_signal_handlers(void) {
803fb7
         int sig, r = 0;
803fb7
 
803fb7
diff --git a/src/shared/util.h b/src/shared/util.h
803fb7
index b4a4a491f..a441e44ff 100644
803fb7
--- a/src/shared/util.h
803fb7
+++ b/src/shared/util.h
803fb7
@@ -281,6 +281,7 @@ int readlink_malloc(const char *p, char **r);
803fb7
 int readlink_value(const char *p, char **ret);
803fb7
 int readlink_and_make_absolute(const char *p, char **r);
803fb7
 int readlink_and_canonicalize(const char *p, char **r);
803fb7
+int readlink_and_make_absolute_root(const char *root, const char *path, char **ret);
803fb7
 
803fb7
 int reset_all_signal_handlers(void);
803fb7
 int reset_signal_mask(void);
803fb7
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
803fb7
index bf5bb398b..95ddf3be7 100644
803fb7
--- a/src/systemctl/systemctl.c
803fb7
+++ b/src/systemctl/systemctl.c
803fb7
@@ -1304,7 +1304,7 @@ static void output_unit_file_list(const UnitFileList *units, unsigned c) {
803fb7
                 if (u->state == UNIT_FILE_MASKED ||
803fb7
                     u->state == UNIT_FILE_MASKED_RUNTIME ||
803fb7
                     u->state == UNIT_FILE_DISABLED ||
803fb7
-                    u->state == UNIT_FILE_INVALID) {
803fb7
+                    u->state == UNIT_FILE_BAD) {
803fb7
                         on  = ansi_highlight_red();
803fb7
                         off = ansi_highlight_off();
803fb7
                 } else if (u->state == UNIT_FILE_ENABLED) {
803fb7
@@ -5637,8 +5637,8 @@ static int unit_is_enabled(sd_bus *bus, char **args) {
803fb7
                 STRV_FOREACH(name, names) {
803fb7
                         UnitFileState state;
803fb7
 
803fb7
-                        state = unit_file_get_state(arg_scope, arg_root, *name);
803fb7
-                        if (state < 0)
803fb7
+                        r = unit_file_get_state(arg_scope, arg_root, *name, &state);
803fb7
+                        if (r < 0)
803fb7
                                 return log_error_errno(state, "Failed to get unit file state for %s: %m", *name);
803fb7
 
803fb7
                         if (state == UNIT_FILE_ENABLED ||
803fb7
diff --git a/src/sysv-generator/sysv-generator.c b/src/sysv-generator/sysv-generator.c
803fb7
index d60e75a06..7e0e7fc28 100644
803fb7
--- a/src/sysv-generator/sysv-generator.c
803fb7
+++ b/src/sysv-generator/sysv-generator.c
803fb7
@@ -712,6 +712,7 @@ static int fix_order(SysvStub *s, Hashmap *all_services) {
803fb7
 
803fb7
 static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) {
803fb7
         char **path;
803fb7
+        int r;
803fb7
 
803fb7
         STRV_FOREACH(path, lp->sysvinit_path) {
803fb7
                 _cleanup_closedir_ DIR *d = NULL;
803fb7
@@ -728,7 +729,6 @@ static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) {
803fb7
                         _cleanup_free_ char *fpath = NULL, *name = NULL;
803fb7
                         _cleanup_free_ SysvStub *service = NULL;
803fb7
                         struct stat st;
803fb7
-                        int r;
803fb7
 
803fb7
                         if (hidden_file(de->d_name))
803fb7
                                 continue;
803fb7
@@ -755,8 +755,12 @@ static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) {
803fb7
                         if (!fpath)
803fb7
                                 return log_oom();
803fb7
 
803fb7
-                        if (unit_file_lookup_state(UNIT_FILE_SYSTEM, NULL, lp, name) >= 0) {
803fb7
-                                log_debug("Native unit for %s already exists, skipping", name);
803fb7
+                        r = unit_file_lookup_state(UNIT_FILE_SYSTEM, NULL, lp, name, NULL);
803fb7
+                        if (r < 0 && r != -ENOENT) {
803fb7
+                                log_debug_errno(r, "Failed to detect whether %s exists, skipping: %m", name);
803fb7
+                                continue;
803fb7
+                        } else if (r >= 0) {
803fb7
+                                log_debug("Native unit for %s already exists, skipping.", name);
803fb7
                                 continue;
803fb7
                         }
803fb7
 
803fb7
diff --git a/src/test/test-install-root.c b/src/test/test-install-root.c
803fb7
new file mode 100644
803fb7
index 000000000..89d91d3d6
803fb7
--- /dev/null
803fb7
+++ b/src/test/test-install-root.c
803fb7
@@ -0,0 +1,663 @@
803fb7
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
803fb7
+
803fb7
+/***
803fb7
+  This file is part of systemd.
803fb7
+
803fb7
+  Copyright 2011 Lennart Poettering
803fb7
+
803fb7
+  systemd is free software; you can redistribute it and/or modify it
803fb7
+  under the terms of the GNU Lesser General Public License as published by
803fb7
+  the Free Software Foundation; either version 2.1 of the License, or
803fb7
+  (at your option) any later version.
803fb7
+
803fb7
+  systemd is distributed in the hope that it will be useful, but
803fb7
+  WITHOUT ANY WARRANTY; without even the implied warranty of
803fb7
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
803fb7
+  Lesser General Public License for more details.
803fb7
+
803fb7
+  You should have received a copy of the GNU Lesser General Public License
803fb7
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
803fb7
+***/
803fb7
+
803fb7
+#include "fileio.h"
803fb7
+#include "install.h"
803fb7
+#include "mkdir.h"
803fb7
+#include "util.h"
803fb7
+
803fb7
+static void test_basic_mask_and_enable(const char *root) {
803fb7
+        const char *p;
803fb7
+        UnitFileState state;
803fb7
+        UnitFileChange *changes = NULL;
803fb7
+        unsigned n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "a.service", NULL) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", NULL) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", NULL) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", NULL) == -ENOENT);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/a.service");
803fb7
+        assert_se(write_string_file(p,
803fb7
+                                    "[Install]\n"
803fb7
+                                    "WantedBy=multi-user.target\n") >= 0);
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "a.service", NULL) >= 0);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/b.service");
803fb7
+        assert_se(symlink("a.service", p) >= 0);
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", NULL) >= 0);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/c.service");
803fb7
+        assert_se(symlink("/usr/lib/systemd/system/a.service", p) >= 0);
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", NULL) >= 0);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/d.service");
803fb7
+        assert_se(symlink("c.service", p) >= 0);
803fb7
+
803fb7
+        /* This one is interesting, as d follows a relative, then an absolute symlink */
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", NULL) >= 0);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        assert_se(unit_file_mask(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[0].source, "/dev/null"));
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/a.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_MASKED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_MASKED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_MASKED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_MASKED);
803fb7
+
803fb7
+        /* Enabling a masked unit should fail! */
803fb7
+        assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), false, &changes, &n_changes) == -ESHUTDOWN);
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_unmask(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_UNLINK);
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/a.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), false, &changes, &n_changes) == 1);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[0].source, "/usr/lib/systemd/system/a.service"));
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/a.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+
803fb7
+        /* Enabling it again should succeed but be a NOP */
803fb7
+        assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), false, &changes, &n_changes) == 1);
803fb7
+        assert_se(n_changes == 0);
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_UNLINK);
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/a.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        /* Disabling a disabled unit must suceed but be a NOP */
803fb7
+        assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 0);
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        /* Let's enable this indirectly via a symlink */
803fb7
+        assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("d.service"), false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[0].source, "/usr/lib/systemd/system/a.service"));
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/a.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+
803fb7
+        /* Let's try to reenable */
803fb7
+
803fb7
+        assert_se(unit_file_reenable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("b.service"), false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 2);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_UNLINK);
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/a.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        assert_se(changes[1].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[1].source, "/usr/lib/systemd/system/a.service"));
803fb7
+        assert_se(streq(changes[1].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+}
803fb7
+
803fb7
+static void test_linked_units(const char *root) {
803fb7
+        const char *p, *q;
803fb7
+        UnitFileState state;
803fb7
+        UnitFileChange *changes = NULL;
803fb7
+        unsigned n_changes = 0, i;
803fb7
+
803fb7
+        /*
803fb7
+         * We'll test three cases here:
803fb7
+         *
803fb7
+         * a) a unit file in /opt, that we use "systemctl link" and
803fb7
+         * "systemctl enable" on to make it available to the system
803fb7
+         *
803fb7
+         * b) a unit file in /opt, that is statically linked into
803fb7
+         * /usr/lib/systemd/system, that "enable" should work on
803fb7
+         * correctly.
803fb7
+         *
803fb7
+         * c) a unit file in /opt, that is linked into
803fb7
+         * /etc/systemd/system, and where "enable" should result in
803fb7
+         * -ELOOP, since using information from /etc to generate
803fb7
+         * information in /etc should not be allowed.
803fb7
+         */
803fb7
+
803fb7
+        p = strjoina(root, "/opt/linked.service");
803fb7
+        assert_se(write_string_file(p,
803fb7
+                                    "[Install]\n"
803fb7
+                                    "WantedBy=multi-user.target\n") >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/opt/linked2.service");
803fb7
+        assert_se(write_string_file(p,
803fb7
+                                    "[Install]\n"
803fb7
+                                    "WantedBy=multi-user.target\n") >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/opt/linked3.service");
803fb7
+        assert_se(write_string_file(p,
803fb7
+                                    "[Install]\n"
803fb7
+                                    "WantedBy=multi-user.target\n") >= 0);
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked.service", NULL) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked2.service", NULL) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked3.service", NULL) == -ENOENT);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/linked2.service");
803fb7
+        assert_se(symlink("/opt/linked2.service", p) >= 0);
803fb7
+
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/linked3.service");
803fb7
+        assert_se(symlink("/opt/linked3.service", p) >= 0);
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked.service", &state) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked2.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked3.service", &state) >= 0 && state == UNIT_FILE_LINKED);
803fb7
+
803fb7
+        /* First, let's link the unit into the search path */
803fb7
+        assert_se(unit_file_link(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("/opt/linked.service"), false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[0].source, "/opt/linked.service"));
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/linked.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked.service", &state) >= 0 && state == UNIT_FILE_LINKED);
803fb7
+
803fb7
+        /* Let's unlink it from the search path again */
803fb7
+        assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("linked.service"), &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_UNLINK);
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/linked.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked.service", NULL) == -ENOENT);
803fb7
+
803fb7
+        /* Now, let's not just link it, but also enable it */
803fb7
+        assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("/opt/linked.service"), false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 2);
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/linked.service");
803fb7
+        q = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/linked.service");
803fb7
+        for (i = 0 ; i < n_changes; i++) {
803fb7
+                assert_se(changes[i].type == UNIT_FILE_SYMLINK);
803fb7
+                assert_se(streq(changes[i].source, "/opt/linked.service"));
803fb7
+
803fb7
+                if (p && streq(changes[i].path, p))
803fb7
+                        p = NULL;
803fb7
+                else if (q && streq(changes[i].path, q))
803fb7
+                        q = NULL;
803fb7
+                else
803fb7
+                        assert_not_reached("wut?");
803fb7
+        }
803fb7
+        assert(!p && !q);
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+
803fb7
+        /* And let's unlink it again */
803fb7
+        assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("linked.service"), &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 2);
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/linked.service");
803fb7
+        q = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/linked.service");
803fb7
+        for (i = 0; i < n_changes; i++) {
803fb7
+                assert_se(changes[i].type == UNIT_FILE_UNLINK);
803fb7
+
803fb7
+                if (p && streq(changes[i].path, p))
803fb7
+                        p = NULL;
803fb7
+                else if (q && streq(changes[i].path, q))
803fb7
+                        q = NULL;
803fb7
+                else
803fb7
+                        assert_not_reached("wut?");
803fb7
+        }
803fb7
+        assert(!p && !q);
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked.service", NULL) == -ENOENT);
803fb7
+
803fb7
+        assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("linked2.service"), false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 2);
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/linked2.service");
803fb7
+        q = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/linked2.service");
803fb7
+        for (i = 0 ; i < n_changes; i++) {
803fb7
+                assert_se(changes[i].type == UNIT_FILE_SYMLINK);
803fb7
+                assert_se(streq(changes[i].source, "/opt/linked2.service"));
803fb7
+
803fb7
+                if (p && streq(changes[i].path, p))
803fb7
+                        p = NULL;
803fb7
+                else if (q && streq(changes[i].path, q))
803fb7
+                        q = NULL;
803fb7
+                else
803fb7
+                        assert_not_reached("wut?");
803fb7
+        }
803fb7
+        assert(!p && !q);
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("linked3.service"), false, &changes, &n_changes) == -ELOOP);
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+}
803fb7
+
803fb7
+static void test_default(const char *root) {
803fb7
+        _cleanup_free_ char *def = NULL;
803fb7
+        UnitFileChange *changes = NULL;
803fb7
+        unsigned n_changes = 0;
803fb7
+        const char *p;
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/test-default-real.target");
803fb7
+        assert_se(write_string_file(p, "# pretty much empty") >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/test-default.target");
803fb7
+        assert_se(symlink("test-default-real.target", p) >= 0);
803fb7
+
803fb7
+        assert_se(unit_file_get_default(UNIT_FILE_SYSTEM, root, &def) == -ENOENT);
803fb7
+
803fb7
+        assert_se(unit_file_set_default(UNIT_FILE_SYSTEM, root, "idontexist.target", false, &changes, &n_changes) == -ENOENT);
803fb7
+        assert_se(n_changes == 0);
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_default(UNIT_FILE_SYSTEM, root, &def) == -ENOENT);
803fb7
+
803fb7
+        assert_se(unit_file_set_default(UNIT_FILE_SYSTEM, root, "test-default.target", false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[0].source, "/usr/lib/systemd/system/test-default-real.target"));
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/default.target");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_default(UNIT_FILE_SYSTEM, root, &def) >= 0);
803fb7
+        assert_se(streq_ptr(def, "test-default-real.target"));
803fb7
+}
803fb7
+
803fb7
+static void test_add_dependency(const char *root) {
803fb7
+        UnitFileChange *changes = NULL;
803fb7
+        unsigned n_changes = 0;
803fb7
+        const char *p;
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/real-add-dependency-test-target.target");
803fb7
+        assert_se(write_string_file(p, "# pretty much empty") >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/add-dependency-test-target.target");
803fb7
+        assert_se(symlink("real-add-dependency-test-target.target", p) >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/real-add-dependency-test-service.service");
803fb7
+        assert_se(write_string_file(p, "# pretty much empty") >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/add-dependency-test-service.service");
803fb7
+        assert_se(symlink("real-add-dependency-test-service.service", p) >= 0);
803fb7
+
803fb7
+        assert_se(unit_file_add_dependency(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("add-dependency-test-service.service"), "add-dependency-test-target.target", UNIT_WANTS, false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[0].source, "/usr/lib/systemd/system/real-add-dependency-test-service.service"));
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/real-add-dependency-test-target.target.wants/real-add-dependency-test-service.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+}
803fb7
+
803fb7
+static void test_template_enable(const char *root) {
803fb7
+        UnitFileChange *changes = NULL;
803fb7
+        unsigned n_changes = 0;
803fb7
+        UnitFileState state;
803fb7
+        const char *p;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@.service", &state) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@def.service", &state) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@foo.service", &state) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) == -ENOENT);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/template@.service");
803fb7
+        assert_se(write_string_file(p,
803fb7
+                                    "[Install]\n"
803fb7
+                                    "DefaultInstance=def\n"
803fb7
+                                    "WantedBy=multi-user.target\n") >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/template-symlink@.service");
803fb7
+        assert_se(symlink("template@.service", p) >= 0);
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("template@.service"), false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service"));
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/template@def.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("template@.service"), &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_UNLINK);
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("template@foo.service"), false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service"));
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/template@foo.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+
803fb7
+        assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("template@foo.service"), &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_UNLINK);
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@quux.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@quux.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("template-symlink@quux.service"), false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service"));
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/template@quux.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template@quux.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@quux.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+}
803fb7
+
803fb7
+static void test_indirect(const char *root) {
803fb7
+        UnitFileChange *changes = NULL;
803fb7
+        unsigned n_changes = 0;
803fb7
+        UnitFileState state;
803fb7
+        const char *p;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirecta.service", &state) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirectb.service", &state) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirectc.service", &state) == -ENOENT);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/indirecta.service");
803fb7
+        assert_se(write_string_file(p,
803fb7
+                                    "[Install]\n"
803fb7
+                                    "Also=indirectb.service\n") >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/indirectb.service");
803fb7
+        assert_se(write_string_file(p,
803fb7
+                                    "[Install]\n"
803fb7
+                                    "WantedBy=multi-user.target\n") >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/indirectc.service");
803fb7
+        assert_se(symlink("indirecta.service", p) >= 0);
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirecta.service", &state) >= 0 && state == UNIT_FILE_INDIRECT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirectb.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirectc.service", &state) >= 0 && state == UNIT_FILE_INDIRECT);
803fb7
+
803fb7
+        assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("indirectc.service"), false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[0].source, "/usr/lib/systemd/system/indirectb.service"));
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/indirectb.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirecta.service", &state) >= 0 && state == UNIT_FILE_INDIRECT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirectb.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirectc.service", &state) >= 0 && state == UNIT_FILE_INDIRECT);
803fb7
+
803fb7
+        assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("indirectc.service"), &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_UNLINK);
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/indirectb.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+}
803fb7
+
803fb7
+static void test_preset_and_list(const char *root) {
803fb7
+        UnitFileChange *changes = NULL;
803fb7
+        unsigned n_changes = 0, i;
803fb7
+        const char *p, *q;
803fb7
+        UnitFileState state;
803fb7
+        bool got_yes = false, got_no = false;
803fb7
+        Iterator j;
803fb7
+        UnitFileList *fl;
803fb7
+        Hashmap *h;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-yes.service", &state) == -ENOENT);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-no.service", &state) == -ENOENT);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/preset-yes.service");
803fb7
+        assert_se(write_string_file(p,
803fb7
+                                    "[Install]\n"
803fb7
+                                    "WantedBy=multi-user.target\n") >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/preset-no.service");
803fb7
+        assert_se(write_string_file(p,
803fb7
+                                    "[Install]\n"
803fb7
+                                    "WantedBy=multi-user.target\n") >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system-preset/test.preset");
803fb7
+        assert_se(write_string_file(p,
803fb7
+                                    "enable *-yes.*\n"
803fb7
+                                    "disable *\n") >= 0);
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        assert_se(unit_file_preset(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("preset-yes.service"), UNIT_FILE_PRESET_FULL, false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
803fb7
+        assert_se(streq(changes[0].source, "/usr/lib/systemd/system/preset-yes.service"));
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/preset-yes.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("preset-yes.service"), &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 1);
803fb7
+        assert_se(changes[0].type == UNIT_FILE_UNLINK);
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/preset-yes.service");
803fb7
+        assert_se(streq(changes[0].path, p));
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        assert_se(unit_file_preset(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("preset-no.service"), UNIT_FILE_PRESET_FULL, false, &changes, &n_changes) >= 0);
803fb7
+        assert_se(n_changes == 0);
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        assert_se(unit_file_preset_all(UNIT_FILE_SYSTEM, false, root, UNIT_FILE_PRESET_FULL, false, &changes, &n_changes) >= 0);
803fb7
+
803fb7
+        assert_se(n_changes > 0);
803fb7
+
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/preset-yes.service");
803fb7
+
803fb7
+        for (i = 0; i < n_changes; i++) {
803fb7
+
803fb7
+                if (changes[i].type == UNIT_FILE_SYMLINK) {
803fb7
+                        assert_se(streq(changes[i].source, "/usr/lib/systemd/system/preset-yes.service"));
803fb7
+                        assert_se(streq(changes[i].path, p));
803fb7
+                } else
803fb7
+                        assert_se(changes[i].type == UNIT_FILE_UNLINK);
803fb7
+        }
803fb7
+
803fb7
+        unit_file_changes_free(changes, n_changes);
803fb7
+        changes = NULL; n_changes = 0;
803fb7
+
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
803fb7
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
803fb7
+
803fb7
+        assert_se(h = hashmap_new(&string_hash_ops));
803fb7
+        assert_se(unit_file_get_list(UNIT_FILE_SYSTEM, root, h) >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/preset-yes.service");
803fb7
+        q = strjoina(root, "/usr/lib/systemd/system/preset-no.service");
803fb7
+
803fb7
+        HASHMAP_FOREACH(fl, h, j) {
803fb7
+                assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, basename(fl->path), &state) >= 0);
803fb7
+                assert_se(fl->state == state);
803fb7
+
803fb7
+                if (streq(fl->path, p)) {
803fb7
+                        got_yes = true;
803fb7
+                        assert_se(fl->state == UNIT_FILE_ENABLED);
803fb7
+                } else if (streq(fl->path, q)) {
803fb7
+                        got_no = true;
803fb7
+                        assert_se(fl->state == UNIT_FILE_DISABLED);
803fb7
+                } else
803fb7
+                        assert_se(IN_SET(fl->state, UNIT_FILE_DISABLED, UNIT_FILE_STATIC, UNIT_FILE_INDIRECT));
803fb7
+        }
803fb7
+
803fb7
+        unit_file_list_free(h);
803fb7
+
803fb7
+        assert_se(got_yes && got_no);
803fb7
+}
803fb7
+
803fb7
+int main(int argc, char *argv[]) {
803fb7
+        char root[] = "/tmp/rootXXXXXX";
803fb7
+        const char *p;
803fb7
+
803fb7
+        assert_se(mkdtemp(root));
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system/");
803fb7
+        assert_se(mkdir_p(p, 0755) >= 0);
803fb7
+
803fb7
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/");
803fb7
+        assert_se(mkdir_p(p, 0755) >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/run/systemd/system/");
803fb7
+        assert_se(mkdir_p(p, 0755) >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/opt/");
803fb7
+        assert_se(mkdir_p(p, 0755) >= 0);
803fb7
+
803fb7
+        p = strjoina(root, "/usr/lib/systemd/system-preset/");
803fb7
+        assert_se(mkdir_p(p, 0755) >= 0);
803fb7
+
803fb7
+        test_basic_mask_and_enable(root);
803fb7
+        test_linked_units(root);
803fb7
+        test_default(root);
803fb7
+        test_add_dependency(root);
803fb7
+        test_template_enable(root);
803fb7
+        test_indirect(root);
803fb7
+        test_preset_and_list(root);
803fb7
+
803fb7
+        assert_se(rm_rf_dangerous(root, false, true, false));
803fb7
+
803fb7
+        return 0;
803fb7
+}
803fb7
diff --git a/src/test/test-install.c b/src/test/test-install.c
803fb7
index 467970b00..08a1faf2c 100644
803fb7
--- a/src/test/test-install.c
803fb7
+++ b/src/test/test-install.c
803fb7
@@ -50,17 +50,19 @@ int main(int argc, char* argv[]) {
803fb7
         const char *const files2[] = { "/home/lennart/test.service", NULL };
803fb7
         UnitFileChange *changes = NULL;
803fb7
         unsigned n_changes = 0;
803fb7
+        UnitFileState state = 0;
803fb7
 
803fb7
         h = hashmap_new(&string_hash_ops);
803fb7
         r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h);
803fb7
         assert_se(r == 0);
803fb7
 
803fb7
         HASHMAP_FOREACH(p, h, i) {
803fb7
-                UnitFileState s;
803fb7
+                UnitFileState s = _UNIT_FILE_STATE_INVALID;
803fb7
 
803fb7
-                s = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(p->path));
803fb7
+                r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(p->path), &s);
803fb7
 
803fb7
-                assert_se(p->state == s);
803fb7
+                assert_se((r < 0 && p->state == UNIT_FILE_BAD) ||
803fb7
+                          (p->state == s));
803fb7
 
803fb7
                 fprintf(stderr, "%s (%s)\n",
803fb7
                         p->path,
803fb7
@@ -82,7 +84,9 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_ENABLED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0], &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_ENABLED);
803fb7
 
803fb7
         log_error("disable");
803fb7
 
803fb7
@@ -95,7 +99,9 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_DISABLED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0], &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_DISABLED);
803fb7
 
803fb7
         log_error("mask");
803fb7
         changes = NULL;
803fb7
@@ -110,7 +116,9 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_MASKED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0], &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_MASKED);
803fb7
 
803fb7
         log_error("unmask");
803fb7
         changes = NULL;
803fb7
@@ -125,7 +133,9 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_DISABLED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0], &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_DISABLED);
803fb7
 
803fb7
         log_error("mask");
803fb7
         changes = NULL;
803fb7
@@ -137,7 +147,9 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_MASKED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0], &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_MASKED);
803fb7
 
803fb7
         log_error("disable");
803fb7
         changes = NULL;
803fb7
@@ -152,7 +164,9 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_MASKED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0], &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_MASKED);
803fb7
 
803fb7
         log_error("umask");
803fb7
         changes = NULL;
803fb7
@@ -164,7 +178,9 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_DISABLED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0], &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_DISABLED);
803fb7
 
803fb7
         log_error("enable files2");
803fb7
         changes = NULL;
803fb7
@@ -176,19 +192,22 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0])) == UNIT_FILE_ENABLED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0]), &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_ENABLED);
803fb7
 
803fb7
         log_error("disable files2");
803fb7
         changes = NULL;
803fb7
         n_changes = 0;
803fb7
 
803fb7
-        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, &changes, &n_changes);
803fb7
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes);
803fb7
         assert_se(r >= 0);
803fb7
 
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0])) == _UNIT_FILE_STATE_INVALID);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0]), &state);
803fb7
+        assert_se(r < 0);
803fb7
 
803fb7
         log_error("link files2");
803fb7
         changes = NULL;
803fb7
@@ -200,19 +219,22 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0])) == UNIT_FILE_LINKED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0]), &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_LINKED);
803fb7
 
803fb7
         log_error("disable files2");
803fb7
         changes = NULL;
803fb7
         n_changes = 0;
803fb7
 
803fb7
-        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, &changes, &n_changes);
803fb7
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes);
803fb7
         assert_se(r >= 0);
803fb7
 
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0])) == _UNIT_FILE_STATE_INVALID);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0]), &state);
803fb7
+        assert_se(r < 0);
803fb7
 
803fb7
         log_error("link files2");
803fb7
         changes = NULL;
803fb7
@@ -224,7 +246,9 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0])) == UNIT_FILE_LINKED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0]), &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_LINKED);
803fb7
 
803fb7
         log_error("reenable files2");
803fb7
         changes = NULL;
803fb7
@@ -236,19 +260,22 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0])) == UNIT_FILE_ENABLED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0]), &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_ENABLED);
803fb7
 
803fb7
         log_error("disable files2");
803fb7
         changes = NULL;
803fb7
         n_changes = 0;
803fb7
 
803fb7
-        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, &changes, &n_changes);
803fb7
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes);
803fb7
         assert_se(r >= 0);
803fb7
 
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0])) == _UNIT_FILE_STATE_INVALID);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files2[0]), &state);
803fb7
+        assert_se(r < 0);
803fb7
         log_error("preset files");
803fb7
         changes = NULL;
803fb7
         n_changes = 0;
803fb7
@@ -259,7 +286,9 @@ int main(int argc, char* argv[]) {
803fb7
         dump_changes(changes, n_changes);
803fb7
         unit_file_changes_free(changes, n_changes);
803fb7
 
803fb7
-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files[0])) == UNIT_FILE_ENABLED);
803fb7
+        r = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, basename(files[0]), &state);
803fb7
+        assert_se(r >= 0);
803fb7
+        assert_se(state == UNIT_FILE_ENABLED);
803fb7
 
803fb7
         return 0;
803fb7
 }