anitazha / rpms / systemd

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