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