[systemd-devel] [PATCH, REVIEW] Added unit enabled-context cache to improve performance w/ many units.

Ken Sedgwick ksedgwic at bonsai.com
Tue Oct 7 23:08:13 PDT 2014


Resubmitting using git format-patch, git imap-send ... no code changes.

---
 .gitignore                                         |   1 +
 Makefile.am                                        |  44 +++-
 src/core/dbus-manager.c                            |   4 +-
 src/core/manager.c                                 |   6 +
 src/core/manager.h                                 |   2 +
 src/core/unit.c                                    |   2 +-
 src/shared/install.c                               | 268 +++++++++++++++++----
 src/shared/install.h                               |  16 +-
 src/systemctl/systemctl.c                          |   4 +-
 src/test/test-enabled.c                            | 151 ++++++++++++
 src/test/test-install.c                            |  87 ++++---
 src/test/test-unit-file.c                          |   2 +-
 .../etc/systemd/system/masked.service              |   1 +
 .../etc/systemd/system/maskedstatic.service        |   1 +
 .../etc/systemd/system/some.target                 |  11 +
 .../system/some.target.wants/aliased.service       |   1 +
 .../system/some.target.wants/also_masked.service   |   1 +
 .../system/some.target.wants/another.service       |   1 +
 .../system/some.target.wants/different.service     |   1 +
 .../system/some.target.wants/masked.service        |   1 +
 .../some.target.wants/templating at four.service      |   1 +
 .../some.target.wants/templating at one.service       |   1 +
 .../some.target.wants/templating at three.service     |   1 +
 .../some.target.wants/templating at two.service       |   1 +
 .../run/systemd/system/maskedruntime.service       |   1 +
 .../run/systemd/system/maskedruntimestatic.service |   1 +
 .../run/systemd/system/other.target                |  14 ++
 .../system/other.target.wants/runtime.service      |   1 +
 .../usr/lib/systemd/system/another.service         |   9 +
 .../usr/lib/systemd/system/disabled.service        |   9 +
 .../usr/lib/systemd/system/invalid.service         |   1 +
 .../usr/lib/systemd/system/masked.service          |   9 +
 .../usr/lib/systemd/system/maskedruntime.service   |   9 +
 .../lib/systemd/system/maskedruntimestatic.service |   6 +
 .../usr/lib/systemd/system/maskedstatic.service    |   6 +
 .../usr/lib/systemd/system/runtime.service         |   9 +
 .../usr/lib/systemd/system/static.service          |   6 +
 .../usr/lib/systemd/system/templating at .service     |   9 +
 .../lib/systemd/system/templating at three.service    |   9 +
 .../usr/lib/systemd/system/templating at two.service  |   9 +
 .../usr/lib/systemd/system/unique.service          |   9 +
 41 files changed, 626 insertions(+), 100 deletions(-)
 create mode 100644 src/test/test-enabled.c
 create mode 120000 test/test-enabled-root/etc/systemd/system/masked.service
 create mode 120000
test/test-enabled-root/etc/systemd/system/maskedstatic.service
 create mode 100644 test/test-enabled-root/etc/systemd/system/some.target
 create mode 120000
test/test-enabled-root/etc/systemd/system/some.target.wants/aliased.service
 create mode 120000
test/test-enabled-root/etc/systemd/system/some.target.wants/also_masked.service
 create mode 120000
test/test-enabled-root/etc/systemd/system/some.target.wants/another.service
 create mode 120000
test/test-enabled-root/etc/systemd/system/some.target.wants/different.service
 create mode 120000
test/test-enabled-root/etc/systemd/system/some.target.wants/masked.service
 create mode 120000
test/test-enabled-root/etc/systemd/system/some.target.wants/templating at four.service
 create mode 120000
test/test-enabled-root/etc/systemd/system/some.target.wants/templating at one.service
 create mode 120000
test/test-enabled-root/etc/systemd/system/some.target.wants/templating at three.service
 create mode 120000
test/test-enabled-root/etc/systemd/system/some.target.wants/templating at two.service
 create mode 120000
test/test-enabled-root/run/systemd/system/maskedruntime.service
 create mode 120000
test/test-enabled-root/run/systemd/system/maskedruntimestatic.service
 create mode 100644 test/test-enabled-root/run/systemd/system/other.target
 create mode 120000
test/test-enabled-root/run/systemd/system/other.target.wants/runtime.service
 create mode 100644
test/test-enabled-root/usr/lib/systemd/system/another.service
 create mode 100644
test/test-enabled-root/usr/lib/systemd/system/disabled.service
 create mode 100644
test/test-enabled-root/usr/lib/systemd/system/invalid.service
 create mode 100644 test/test-enabled-root/usr/lib/systemd/system/masked.service
 create mode 100644
test/test-enabled-root/usr/lib/systemd/system/maskedruntime.service
 create mode 100644
test/test-enabled-root/usr/lib/systemd/system/maskedruntimestatic.service
 create mode 100644
test/test-enabled-root/usr/lib/systemd/system/maskedstatic.service
 create mode 100644
test/test-enabled-root/usr/lib/systemd/system/runtime.service
 create mode 100644 test/test-enabled-root/usr/lib/systemd/system/static.service
 create mode 100644
test/test-enabled-root/usr/lib/systemd/system/templating at .service
 create mode 100644
test/test-enabled-root/usr/lib/systemd/system/templating at three.service
 create mode 100644
test/test-enabled-root/usr/lib/systemd/system/templating at two.service
 create mode 100644 test/test-enabled-root/usr/lib/systemd/system/unique.service

diff --git a/.gitignore b/.gitignore
index f119b57..97b2b2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -173,6 +173,7 @@
 /test-icmp6-rs
 /test-ellipsize
 /test-engine
+/test-enabled
 /test-env-replace
 /test-event
 /test-fdset
diff --git a/Makefile.am b/Makefile.am
index e52db17..7d4f2f5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1358,7 +1358,8 @@ tests += \
  test-ratelimit \
  test-condition-util \
  test-uid-range \
- test-bus-policy
+ test-bus-policy \
+ test-enabled

 EXTRA_DIST += \
  test/a.service \
@@ -1394,8 +1395,36 @@ EXTRA_DIST += \
  test/bus-policy/hello.conf \
  test/bus-policy/methods.conf \
  test/bus-policy/ownerships.conf \
- test/bus-policy/signals.conf
-
+ test/bus-policy/signals.conf \
+ test/test-enabled-root/usr/lib/systemd/system/masked.service \
+ test/test-enabled-root/usr/lib/systemd/system/runtime.service \
+ test/test-enabled-root/usr/lib/systemd/system/maskedruntime.service \
+ test/test-enabled-root/usr/lib/systemd/system/another.service \
+ test/test-enabled-root/usr/lib/systemd/system/templating at three.service \
+ test/test-enabled-root/usr/lib/systemd/system/maskedstatic.service \
+ test/test-enabled-root/usr/lib/systemd/system/invalid.service \
+ test/test-enabled-root/usr/lib/systemd/system/disabled.service \
+ test/test-enabled-root/usr/lib/systemd/system/templating at two.service \
+ test/test-enabled-root/usr/lib/systemd/system/unique.service \
+ test/test-enabled-root/usr/lib/systemd/system/templating at .service \
+ test/test-enabled-root/usr/lib/systemd/system/static.service \
+ test/test-enabled-root/usr/lib/systemd/system/maskedruntimestatic.service \
+ test/test-enabled-root/run/systemd/system/other.target.wants/runtime.service \
+ test/test-enabled-root/run/systemd/system/maskedruntime.service \
+ test/test-enabled-root/run/systemd/system/other.target \
+ test/test-enabled-root/run/systemd/system/maskedruntimestatic.service \
+ test/test-enabled-root/etc/systemd/system/masked.service \
+ test/test-enabled-root/etc/systemd/system/some.target.wants/masked.service \
+ test/test-enabled-root/etc/systemd/system/some.target.wants/another.service \
+ test/test-enabled-root/etc/systemd/system/some.target.wants/templating at three.service
\
+ test/test-enabled-root/etc/systemd/system/some.target.wants/templating at one.service
\
+ test/test-enabled-root/etc/systemd/system/some.target.wants/templating at two.service
\
+ test/test-enabled-root/etc/systemd/system/some.target.wants/templating at four.service
\
+ test/test-enabled-root/etc/systemd/system/some.target.wants/also_masked.service
\
+ test/test-enabled-root/etc/systemd/system/some.target.wants/different.service
\
+ test/test-enabled-root/etc/systemd/system/some.target.wants/aliased.service \
+ test/test-enabled-root/etc/systemd/system/maskedstatic.service \
+ test/test-enabled-root/etc/systemd/system/some.target

 EXTRA_DIST += \
  src/test/test-helper.h
@@ -1782,6 +1811,15 @@ test_install_LDADD = \
  libsystemd-shared.la \
  libsystemd-internal.la

+test_enabled_SOURCES = \
+ src/test/test-enabled.c
+
+test_enabled_LDADD = \
+ libsystemd-units.la \
+ libsystemd-label.la \
+ libsystemd-shared.la \
+ libsystemd-internal.la
+
 test_watchdog_SOURCES = \
  src/test/test-watchdog.c

diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index 533ce43..8dcd552 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -1403,7 +1403,7 @@ static int method_list_unit_files(sd_bus *bus,
sd_bus_message *message, void *us
         if (!h)
                 return -ENOMEM;

-        r = unit_file_get_list(m->running_as == SYSTEMD_SYSTEM ?
UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, h);
+        r = unit_file_get_list(m->running_as == SYSTEMD_SYSTEM ?
UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, h, m->enabled);
         if (r < 0)
                 goto fail;

@@ -1454,7 +1454,7 @@ static int method_get_unit_file_state(sd_bus
*bus, sd_bus_message *message, void

         scope = m->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM :
UNIT_FILE_USER;

-        state = unit_file_get_state(scope, NULL, name);
+        state = unit_file_get_state(scope, NULL, name, m->enabled);
         if (state < 0)
                 return state;

diff --git a/src/core/manager.c b/src/core/manager.c
index e0c1cd1..c9aef42 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -465,6 +465,10 @@ int manager_new(SystemdRunningAs running_as, bool
test_run, Manager **_m) {
         if (r < 0)
                 goto fail;

+        m->enabled = enabled_context_new();
+        if (!m->enabled)
+                goto fail;
+
         r = set_ensure_allocated(&m->startup_units, NULL);
         if (r < 0)
                 goto fail;
@@ -808,6 +812,8 @@ void manager_free(Manager *m) {
         hashmap_free(m->watch_pids2);
         hashmap_free(m->watch_bus);

+        enabled_context_free(m->enabled);
+
         set_free(m->startup_units);
         set_free(m->failed_units);

diff --git a/src/core/manager.h b/src/core/manager.h
index 8e3c146..3f54fe0 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -72,6 +72,7 @@ typedef enum ManagerExitCode {
 #include "unit-name.h"
 #include "exit-status.h"
 #include "show-status.h"
+#include "install.h"
 #include "failure-action.h"

 struct Manager {
@@ -82,6 +83,7 @@ struct Manager {
         /* Active jobs and units */
         Hashmap *units;  /* name string => Unit object n:1 */
         Hashmap *jobs;   /* job id => Job object 1:1 */
+        EnabledContext *enabled; /* name string => is enabled */

         /* To make it easy to iterate through the units of a specific
          * type we maintain a per type linked list */
diff --git a/src/core/unit.c b/src/core/unit.c
index 399d202..c97b061 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -2941,7 +2941,7 @@ UnitFileState unit_get_unit_file_state(Unit *u) {
         if (u->unit_file_state < 0 && u->fragment_path)
                 u->unit_file_state = unit_file_get_state(
                                 u->manager->running_as ==
SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER,
-                                NULL, basename(u->fragment_path));
+                                NULL, basename(u->fragment_path),
u->manager->enabled);

         return u->unit_file_state;
 }
diff --git a/src/shared/install.c b/src/shared/install.c
index fa064c2..b4e4bc1 100644
--- a/src/shared/install.c
+++ b/src/shared/install.c
@@ -25,6 +25,7 @@
 #include <string.h>
 #include <fnmatch.h>

+#include "macro.h"
 #include "util.h"
 #include "mkdir.h"
 #include "hashmap.h"
@@ -382,21 +383,106 @@ static int remove_marked_symlinks(
         return r;
 }

-static int find_symlinks_fd(
-                const char *name,
+EnabledContext *enabled_context_new(void) {
+        EnabledContext *ec;
+        int r;
+
+        ec = new0(EnabledContext, 1);
+        if (!ec)
+                return NULL;
+
+        r = hashmap_ensure_allocated(&ec->config_paths_forward,
&string_hash_ops);
+        if (r < 0) {
+                free(ec);
+                return NULL;
+        }
+
+        r = hashmap_ensure_allocated(&ec->config_paths_reverse,
&string_hash_ops);
+        if (r < 0) {
+                free(ec);
+                return NULL;
+        }
+
+        return ec;
+}
+
+void enabled_context_free(EnabledContext *ec) {
+        char *key;
+        Hashmap *h;
+
+        while ((key = hashmap_first_key(ec->config_paths_forward))) {
+                h = hashmap_steal_first(ec->config_paths_forward);
+                hashmap_free_free_free(h);
+                free(key);
+        }
+        hashmap_free_free_free(ec->config_paths_forward);
+        ec->config_paths_forward = NULL;
+
+        while ((key = hashmap_first_key(ec->config_paths_reverse))) {
+                h = hashmap_steal_first(ec->config_paths_reverse);
+                hashmap_free_free_free(h);
+                free(key);
+        }
+        hashmap_free_free_free(ec->config_paths_reverse);
+        ec->config_paths_reverse = NULL;
+
+        free(ec);
+}
+
+static int insert_link_mapping(Hashmap *h, const char * key, const char *val) {
+        int r = 0;
+        char *keycpy, *valcpy;
+
+        /* Make copies of the key and value */
+        keycpy = strdup(key);
+        valcpy = strdup(val);
+
+        r = hashmap_put(h, keycpy, valcpy);
+        if (r == 1) {
+                /* Our copies were inserted, we're done */
+                return 0;
+        } else {
+                /* Either an error or the mapping already existed, either way
+                 * we don't want the key and value copies any more ...
+                 */
+                free(keycpy);
+                free(valcpy);
+                return r;
+        }
+}
+
+static int fill_enabled_context(
                 int fd,
                 const char *path,
                 const char *config_path,
-                bool *same_name_link) {
-
+                EnabledContext *ec) {
         int r = 0;
         _cleanup_closedir_ DIR *d = NULL;
-
-        assert(name);
-        assert(fd >= 0);
-        assert(path);
-        assert(config_path);
-        assert(same_name_link);
+        Hashmap *config_path_forward;
+        Hashmap *config_path_reverse;
+
+        config_path_forward = hashmap_get(ec->config_paths_forward,
config_path);
+        config_path_reverse = hashmap_get(ec->config_paths_reverse,
config_path);
+
+        /* If config_path_forward isn't cached, generate both directions */
+        if (config_path_forward == NULL) {
+                /* Initialize empty forward and reverse lookups for the
+                 * enabled context cache */
+                r = hashmap_ensure_allocated(&config_path_forward,
&string_hash_ops);
+                if (r < 0) {
+                        return r;
+                }
+                r = hashmap_put(ec->config_paths_forward,
strdup(config_path), config_path_forward);
+                if (r < 0)
+                        return r;
+                r = hashmap_ensure_allocated(&config_path_reverse,
&string_hash_ops);
+                if (r < 0) {
+                        return r;
+                }
+                r = hashmap_put(ec->config_paths_reverse,
strdup(config_path), config_path_reverse);
+                if (r < 0)
+                        return r;
+        }

         d = fdopendir(fd);
         if (!d) {
@@ -413,7 +499,7 @@ static int find_symlinks_fd(
                         return -errno;

                 if (!de)
-                        return r;
+                        break;

                 if (ignore_file(de->d_name))
                         continue;
@@ -441,15 +527,12 @@ static int find_symlinks_fd(
                         }

                         /* This will close nfd, regardless whether it
succeeds or not */
-                        q = find_symlinks_fd(name, nfd, p,
config_path, same_name_link);
-                        if (q > 0)
-                                return 1;
+                        q = fill_enabled_context(nfd, p, config_path, ec);
                         if (r == 0)
                                 r = q;

                 } else if (de->d_type == DT_LNK) {
                         _cleanup_free_ char *p = NULL, *dest = NULL;
-                        bool found_path, found_dest, b = false;
                         int q;

                         /* Acquire symlink name */
@@ -468,44 +551,121 @@ static int find_symlinks_fd(
                                 continue;
                         }

-                        /* Check if the symlink itself matches what we
-                         * are looking for */
-                        if (path_is_absolute(name))
-                                found_path = path_equal(p, name);
-                        else
-                                found_path = streq(de->d_name, name);
+                        /* Insert symlink's own full path and
+                         * name as keys pointing to the unit's
+                         * full path */
+                        q = insert_link_mapping(config_path_forward, p, dest);
+                        if (r == 0)
+                                r = q;
+                        q = insert_link_mapping(config_path_forward,
de->d_name, dest);
+                        if (r == 0)
+                                r = q;

-                        /* Check if what the symlink points to
-                         * matches what we are looking for */
-                        if (path_is_absolute(name))
-                                found_dest = path_equal(dest, name);
-                        else
-                                found_dest = streq(basename(dest), name);
+                        /* Insert full path and base of the
+                         * symlink target as keys pointing to
+                         * the symlink's own full path */
+                        q = insert_link_mapping(config_path_reverse, dest, p);
+                        if (r == 0)
+                                r = q;
+                        q = insert_link_mapping(config_path_reverse,
basename(dest), p);
+                        if (r == 0)
+                                r = q;
+                }
+        }

-                        if (found_path && found_dest) {
-                                _cleanup_free_ char *t = NULL;
+        return r;
+}

-                                /* Filter out same name links in the main
-                                 * config path */
-                                t = path_make_absolute(name, config_path);
-                                if (!t)
-                                        return -ENOMEM;
+static int find_symlinks_fd(
+                const char *name,
+                int fd,
+                const char *path,
+                const char *config_path,
+                bool *same_name_link,
+                EnabledContext *ec) {

-                                b = path_equal(t, p);
-                        }
+        int r = 0;
+        _cleanup_closedir_ DIR *d = NULL;
+        _cleanup_enabled_context_ EnabledContext *temp_ec = NULL;
+        char *symlink_path;
+        char *symlink_target_path;
+        Hashmap *config_path_forward;
+        Hashmap *config_path_reverse;
+        bool found_path, found_dest, b = false;

-                        if (b)
-                                *same_name_link = true;
-                        else if (found_path || found_dest)
-                                return 1;
-                }
+        assert(name);
+        assert(fd >= 0);
+        assert(path);
+        assert(config_path);
+        assert(same_name_link);
+
+        /* If no ec is passed in, use a temporary one that auto-frees */
+        if (ec == NULL) {
+                ec = enabled_context_new();
+                if (ec == NULL)
+                        return -ENOMEM;
+                temp_ec = ec;
+        }
+
+        config_path_forward = hashmap_get(ec->config_paths_forward,
config_path);
+
+        /* If config_path_forward isn't cached, generate both directions */
+        if (config_path_forward == NULL) {
+                r = fill_enabled_context(fd, path, config_path, ec);
+                if (r < 0)
+                        return r;
+        } else {
+                safe_close(fd);
+        }
+
+        /* Refetch, may have been filled. */
+        config_path_forward = hashmap_get(ec->config_paths_forward,
config_path);
+        config_path_reverse = hashmap_get(ec->config_paths_reverse,
config_path);
+
+        /* Use the cache to perform a reverse lookup of symlink
+         * destinations to see if any one matches what we are looking
+         * for. Because both full and base names are keys, there is no
+         * need to check if "name" is a full path or base name. */
+        symlink_path = hashmap_get(config_path_reverse, name);
+        found_dest = (symlink_path != NULL);
+
+        /* Choose a strategy to determine if the unit's name matches the
+         * symlink's name. If the reverse lookup succeeded, we can skip
+         * the forward lookup. */
+        if (found_dest) {
+                if (path_is_absolute(name))
+                        found_path = path_equal(symlink_path, name);
+                else
+                        found_path = streq(basename(symlink_path), name);
+        } else {
+                symlink_target_path = hashmap_get(config_path_forward, name);
+                found_path = (symlink_target_path != NULL);
         }
+
+        if (found_path && found_dest) {
+                _cleanup_free_ char *t = NULL;
+
+                /* Filter out same name links in the main config path */
+                t = path_make_absolute(name, config_path);
+                if (!t)
+                        return -ENOMEM;
+
+                b = path_equal(t, symlink_path);
+        }
+
+        if (b)
+                *same_name_link = true;
+        else if (found_path || found_dest)
+                return 1;
+
+        return 0;
 }

 static int find_symlinks(
                 const char *name,
                 const char *config_path,
-                bool *same_name_link) {
+                bool *same_name_link,
+                EnabledContext *ec) {

         int fd;

@@ -521,14 +681,15 @@ static int find_symlinks(
         }

         /* This takes possession of fd and closes it */
-        return find_symlinks_fd(name, fd, config_path, config_path,
same_name_link);
+        return find_symlinks_fd(name, fd, config_path, config_path,
same_name_link, ec);
 }

 static int find_symlinks_in_scope(
                 UnitFileScope scope,
                 const char *root_dir,
                 const char *name,
-                UnitFileState *state) {
+                UnitFileState *state,
+                EnabledContext *ec) {

         int r;
         _cleanup_free_ char *path = NULL;
@@ -544,7 +705,7 @@ static int find_symlinks_in_scope(
         if (r < 0)
                 return r;

-        r = find_symlinks(name, path, &same_name_link_runtime);
+        r = find_symlinks(name, path, &same_name_link_runtime, ec);
         if (r < 0)
                 return r;
         else if (r > 0) {
@@ -557,7 +718,7 @@ static int find_symlinks_in_scope(
         if (r < 0)
                 return r;

-        r = find_symlinks(name, path, &same_name_link);
+        r = find_symlinks(name, path, &same_name_link, ec);
         if (r < 0)
                 return r;
         else if (r > 0) {
@@ -1687,7 +1848,8 @@ int unit_file_get_default(
 UnitFileState unit_file_get_state(
                 UnitFileScope scope,
                 const char *root_dir,
-                const char *name) {
+                const char *name,
+                EnabledContext *ec) {

         _cleanup_lookup_paths_free_ LookupPaths paths = {};
         UnitFileState state = _UNIT_FILE_STATE_INVALID;
@@ -1750,7 +1912,7 @@ UnitFileState unit_file_get_state(
                         }
                 }

-                r = find_symlinks_in_scope(scope, root_dir, name, &state);
+                r = find_symlinks_in_scope(scope, root_dir, name, &state, ec);
                 if (r < 0)
                         return r;
                 else if (r > 0)
@@ -2033,7 +2195,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*,
unit_file_list_free_one);
 int unit_file_get_list(
                 UnitFileScope scope,
                 const char *root_dir,
-                Hashmap *h) {
+                Hashmap *h,
+                EnabledContext *ec) {

         _cleanup_lookup_paths_free_ LookupPaths paths = {};
         char **i;
@@ -2117,7 +2280,7 @@ int unit_file_get_list(
                                 goto found;
                         }

-                        r = find_symlinks_in_scope(scope, root_dir,
de->d_name, &f->state);
+                        r = find_symlinks_in_scope(scope, root_dir,
de->d_name, &f->state, ec);
                         if (r < 0)
                                 return r;
                         else if (r > 0) {
@@ -2149,7 +2312,10 @@ int unit_file_get_list(
                 }
         }

-        return r;
+        /* The 1 that hashmap_put in the "found" section returns on
+         * successful put can get here.  No valid error r value can
+         * get here. */
+        return 0;
 }

 static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = {
diff --git a/src/shared/install.h b/src/shared/install.h
index ff16d9f..fd3d1e3 100644
--- a/src/shared/install.h
+++ b/src/shared/install.h
@@ -83,6 +83,11 @@ typedef struct {
         char *default_instance;
 } InstallInfo;

+typedef struct {
+        Hashmap* config_paths_forward; /* config_path string =>
symlink name/path => unit_name path */
+        Hashmap* config_paths_reverse; /* config_path string =>
unit_name/path string => symlink path */
+} EnabledContext;
+
 int unit_file_enable(UnitFileScope scope, bool runtime, const char
*root_dir, char **files, bool force, UnitFileChange **changes,
unsigned *n_changes);
 int unit_file_disable(UnitFileScope scope, bool runtime, const char
*root_dir, char **files, UnitFileChange **changes, unsigned
*n_changes);
 int unit_file_reenable(UnitFileScope scope, bool runtime, const char
*root_dir, char **files, bool force, UnitFileChange **changes,
unsigned *n_changes);
@@ -94,9 +99,9 @@ int unit_file_unmask(UnitFileScope scope, bool
runtime, const char *root_dir, ch
 int unit_file_set_default(UnitFileScope scope, const char *root_dir,
const char *file, bool force, UnitFileChange **changes, unsigned
*n_changes);
 int unit_file_get_default(UnitFileScope scope, const char *root_dir,
char **name);

-UnitFileState unit_file_get_state(UnitFileScope scope, const char
*root_dir, const char *filename);
+UnitFileState unit_file_get_state(UnitFileScope scope, const char
*root_dir, const char *filename, EnabledContext *ec);

-int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h);
+int unit_file_get_list(UnitFileScope scope, const char *root_dir,
Hashmap *h, EnabledContext *ec);

 void unit_file_list_free(Hashmap *h);
 void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes);
@@ -109,5 +114,12 @@ UnitFileState unit_file_state_from_string(const
char *s) _pure_;
 const char *unit_file_change_type_to_string(UnitFileChangeType s) _const_;
 UnitFileChangeType unit_file_change_type_from_string(const char *s) _pure_;

+EnabledContext *enabled_context_new(void);
+void enabled_context_free(EnabledContext *ec);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(EnabledContext*, enabled_context_free);
+
+#define _cleanup_enabled_context_ _cleanup_(enabled_context_freep)
+
 const char *unit_file_preset_mode_to_string(UnitFilePresetMode m) _const_;
 UnitFilePresetMode unit_file_preset_mode_from_string(const char *s) _pure_;
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 1c6fef4..b3f491a 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -1342,7 +1342,7 @@ static int list_unit_files(sd_bus *bus, char **args) {
                 if (!h)
                         return log_oom();

-                r = unit_file_get_list(arg_scope, arg_root, h);
+                r = unit_file_get_list(arg_scope, arg_root, h, NULL);
                 if (r < 0) {
                         unit_file_list_free(h);
                         log_error("Failed to get unit file list: %s",
strerror(-r));
@@ -5366,7 +5366,7 @@ static int unit_is_enabled(sd_bus *bus, char **args) {
                 STRV_FOREACH(name, names) {
                         UnitFileState state;

-                        state = unit_file_get_state(arg_scope,
arg_root, *name);
+                        state = unit_file_get_state(arg_scope,
arg_root, *name, NULL);
                         if (state < 0) {
                                 log_error("Failed to get unit file
state for %s: %s", *name, strerror(-state));
                                 return state;
diff --git a/src/test/test-enabled.c b/src/test/test-enabled.c
new file mode 100644
index 0000000..fa5d596
--- /dev/null
+++ b/src/test/test-enabled.c
@@ -0,0 +1,151 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Pantheon, Inc.
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "util.h"
+#include "path-util.h"
+#include "unit-name.h"
+#include "install.h"
+
+/*
+.
+├── etc
+│   └── systemd
+│       └── system
+│           ├── masked.service -> /dev/null
+│           ├── maskedstatic.service -> /dev/null
+│           ├── some.target
+│           └── some.target.wants
+│               ├── another.service ->
../../../../usr/lib/systemd/system/another.service
+│               ├── aliased.service ->
../../../../usr/lib/systemd/system/another.service
+│               ├── different.service ->
../../../../usr/lib/systemd/system/unique.service
+│               ├── masked.service ->
../../../../usr/lib/systemd/system/masked.service
+│               ├── also_masked.service ->
../../../../usr/lib/systemd/system/masked.service
+│               ├── templating at one.service ->
../../../../usr/lib/systemd/system/templating at .service
+│               ├── templating at two.service ->
../../../../usr/lib/systemd/system/templating at two.service
+│               ├── templating at three.service ->
../../../../usr/lib/systemd/system/templating at .service
+│               └── templating at four.service ->
../../../../usr/lib/systemd/system/templating at four.service
+├── run
+│   └── systemd
+│       └── system
+│           ├── maskedruntime.service -> /dev/null
+│           ├── maskedruntimestatic.service -> /dev/null
+│           ├── other.target
+│           └── other.target.wants
+│               └── runtime.service ->
../../../../usr/lib/systemd/system/runtime.service
+└── usr
+    └── lib
+        └── systemd
+            └── system
+                ├── invalid.service
+                ├── disabled.service
+                ├── another.service
+                ├── runtime.service
+                ├── masked.service
+                ├── maskedruntime.service
+                ├── static.service
+                ├── maskedstatic.service
+                ├── maskedruntimestatic.service
+                ├── templating at .service
+                ├── templating at two.service
+                ├── templating at three.service
+                └── unique.service
+*/
+
+
+#define confirm_unit_state(unit, expected)                              \
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
unit, ec) == expected)
+
+static void test_enabled(int argc, char* argv[], EnabledContext *ec) {
+        Hashmap *h;
+        UnitFileList *p;
+        Iterator i;
+        int r;
+        const char *root_dir;
+
+        root_dir = strappenda(TEST_DIR, "/test-enabled-root");
+
+        /* Explicitly check each of the units. */
+        confirm_unit_state("nonexistent.service", -ENOENT);
+        confirm_unit_state("invalid.service", -EBADMSG);
+        confirm_unit_state("disabled.service", UNIT_FILE_DISABLED);
+        confirm_unit_state("another.service", UNIT_FILE_ENABLED);
+        confirm_unit_state("runtime.service", UNIT_FILE_ENABLED_RUNTIME);
+        confirm_unit_state("masked.service", UNIT_FILE_MASKED);
+        confirm_unit_state("maskedruntime.service", UNIT_FILE_MASKED_RUNTIME);
+        confirm_unit_state("static.service", UNIT_FILE_STATIC);
+        confirm_unit_state("maskedstatic.service", UNIT_FILE_MASKED);
+        confirm_unit_state("maskedruntimestatic.service",
UNIT_FILE_MASKED_RUNTIME);
+        confirm_unit_state("templating at .service", UNIT_FILE_ENABLED);
+        confirm_unit_state("templating at two.service", UNIT_FILE_ENABLED);
+        confirm_unit_state("templating at three.service", UNIT_FILE_ENABLED);
+        confirm_unit_state("unique.service", UNIT_FILE_ENABLED);
+
+        /* Reconcile unit_file_get_list with the return for each unit. */
+        h = hashmap_new(&string_hash_ops);
+        r = unit_file_get_list(UNIT_FILE_SYSTEM, root_dir, h, ec);
+        assert_se(r == 0);
+        HASHMAP_FOREACH(p, h, i) {
+                UnitFileState s;
+
+                s = unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
+                                        basename(p->path), ec);
+
+                /* unit_file_get_list and unit_file_get_state are
+                 * a little different in some cases.  Handle these
+                 * cases here ...
+                 */
+                switch ((int)s) {
+                case UNIT_FILE_ENABLED_RUNTIME:
+                        assert_se(p->state == UNIT_FILE_ENABLED);
+                        break;
+                case -EBADMSG:
+                        assert_se(p->state == UNIT_FILE_INVALID);
+                        break;
+                default:
+                        assert_se(p->state == s);
+                        break;
+                }
+
+                fprintf(stderr, "%s (%s)\n",
+                        p->path,
+                        unit_file_state_to_string(p->state));
+        }
+        unit_file_list_free(h);
+}
+
+int main(int argc, char* argv[]) {
+        _cleanup_enabled_context_ EnabledContext *ec = NULL;
+
+        /* built-in EnabledContext */
+        test_enabled(argc, argv, NULL);
+
+        /* explicit EnabledContext */
+        ec = enabled_context_new();
+        assert(ec);
+        test_enabled(argc, argv, ec);
+
+        return 0;
+}
diff --git a/src/test/test-install.c b/src/test/test-install.c
index 467970b..0c8c05b 100644
--- a/src/test/test-install.c
+++ b/src/test/test-install.c
@@ -41,24 +41,25 @@ static void dump_changes(UnitFileChange *c, unsigned n) {
         }
 }

-int main(int argc, char* argv[]) {
+static void test_install(int argc, char* argv[], EnabledContext *ec) {
         Hashmap *h;
         UnitFileList *p;
         Iterator i;
         int r;
-        const char *const files[] = { "avahi-daemon.service", NULL };
+        const char *root_dir = argv[1] ?: NULL;
+        const char *const files[] = { "systemd-timesyncd.service", NULL };
         const char *const files2[] = { "/home/lennart/test.service", NULL };
         UnitFileChange *changes = NULL;
         unsigned n_changes = 0;

         h = hashmap_new(&string_hash_ops);
-        r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h);
+        r = unit_file_get_list(UNIT_FILE_SYSTEM, root_dir, h, ec);
         assert_se(r == 0);

         HASHMAP_FOREACH(p, h, i) {
                 UnitFileState s;

-                s = unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
basename(p->path));
+                s = unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
basename(p->path), ec);

                 assert_se(p->state == s);

@@ -71,195 +72,205 @@ int main(int argc, char* argv[]) {

         log_error("enable");

-        r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, false, &changes, &n_changes);
+        r = unit_file_enable(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, false, &changes, &n_changes);
         assert_se(r >= 0);

         log_error("enable2");

-        r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, false, &changes, &n_changes);
+        r = unit_file_enable(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, false, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
files[0]) == UNIT_FILE_ENABLED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
files[0], ec) == UNIT_FILE_ENABLED);

         log_error("disable");

         changes = NULL;
         n_changes = 0;

-        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, &changes, &n_changes);
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
files[0]) == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
files[0], ec) == UNIT_FILE_DISABLED);

         log_error("mask");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, false, &changes, &n_changes);
+        r = unit_file_mask(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, false, &changes, &n_changes);
         assert_se(r >= 0);
         log_error("mask2");
-        r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, false, &changes, &n_changes);
+        r = unit_file_mask(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, false, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
files[0]) == UNIT_FILE_MASKED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
files[0], ec) == UNIT_FILE_MASKED);

         log_error("unmask");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, &changes, &n_changes);
+        r = unit_file_unmask(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, &changes, &n_changes);
         assert_se(r >= 0);
         log_error("unmask2");
-        r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, &changes, &n_changes);
+        r = unit_file_unmask(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
files[0]) == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
files[0], ec) == UNIT_FILE_DISABLED);

         log_error("mask");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, false, &changes, &n_changes);
+        r = unit_file_mask(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, false, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
files[0]) == UNIT_FILE_MASKED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
files[0], ec) == UNIT_FILE_MASKED);

         log_error("disable");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, &changes, &n_changes);
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, &changes, &n_changes);
         assert_se(r >= 0);
         log_error("disable2");
-        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, &changes, &n_changes);
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
files[0]) == UNIT_FILE_MASKED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
files[0], ec) == UNIT_FILE_MASKED);

         log_error("umask");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, &changes, &n_changes);
+        r = unit_file_unmask(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
files[0]) == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
files[0], ec) == UNIT_FILE_DISABLED);

         log_error("enable files2");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**)
files2, false, &changes, &n_changes);
+        r = unit_file_enable(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files2, false, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
basename(files2[0])) == UNIT_FILE_ENABLED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
basename(files2[0]), ec) == UNIT_FILE_ENABLED);

         log_error("disable files2");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**)
files2, &changes, &n_changes);
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files2, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
basename(files2[0])) == _UNIT_FILE_STATE_INVALID);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
basename(files2[0]), ec) == _UNIT_FILE_STATE_INVALID);

         log_error("link files2");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_link(UNIT_FILE_SYSTEM, false, NULL, (char**)
files2, false, &changes, &n_changes);
+        r = unit_file_link(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files2, false, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
basename(files2[0])) == UNIT_FILE_LINKED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
basename(files2[0]), ec) == UNIT_FILE_LINKED);

         log_error("disable files2");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**)
files2, &changes, &n_changes);
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files2, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
basename(files2[0])) == _UNIT_FILE_STATE_INVALID);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
basename(files2[0]), ec) == _UNIT_FILE_STATE_INVALID);

         log_error("link files2");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_link(UNIT_FILE_SYSTEM, false, NULL, (char**)
files2, false, &changes, &n_changes);
+        r = unit_file_link(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files2, false, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
basename(files2[0])) == UNIT_FILE_LINKED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
basename(files2[0]), ec) == UNIT_FILE_LINKED);

         log_error("reenable files2");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_reenable(UNIT_FILE_SYSTEM, false, NULL,
(char**) files2, false, &changes, &n_changes);
+        r = unit_file_reenable(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files2, false, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
basename(files2[0])) == UNIT_FILE_ENABLED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
basename(files2[0]), ec) == UNIT_FILE_ENABLED);

         log_error("disable files2");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**)
files2, &changes, &n_changes);
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files2, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
basename(files2[0])) == _UNIT_FILE_STATE_INVALID);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
basename(files2[0]), ec) == _UNIT_FILE_STATE_INVALID);
         log_error("preset files");
         changes = NULL;
         n_changes = 0;

-        r = unit_file_preset(UNIT_FILE_SYSTEM, false, NULL, (char**)
files, UNIT_FILE_PRESET_FULL, false, &changes, &n_changes);
+        r = unit_file_preset(UNIT_FILE_SYSTEM, false, root_dir,
(char**) files, UNIT_FILE_PRESET_FULL, false, &changes, &n_changes);
         assert_se(r >= 0);

         dump_changes(changes, n_changes);
         unit_file_changes_free(changes, n_changes);

-        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL,
basename(files[0])) == UNIT_FILE_ENABLED);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root_dir,
basename(files[0]), ec) == UNIT_FILE_ENABLED);
+}
+
+int main(int argc, char* argv[]) {
+        EnabledContext *ec;
+
+        test_install(argc, argv, NULL);
+
+        ec = enabled_context_new();
+        assert(ec);
+        test_install(argc, argv, ec);

         return 0;
 }
diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c
index 03b3e25..227ec10 100644
--- a/src/test/test-unit-file.c
+++ b/src/test/test-unit-file.c
@@ -47,7 +47,7 @@ static int test_unit_file_get_set(void) {
         h = hashmap_new(&string_hash_ops);
         assert_se(h);

-        r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h);
+        r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h, NULL);

         if (r == -EPERM || r == -EACCES) {
                 printf("Skipping test: unit_file_get_list: %s", strerror(-r));
diff --git a/test/test-enabled-root/etc/systemd/system/masked.service
b/test/test-enabled-root/etc/systemd/system/masked.service
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/masked.service
@@ -0,0 +1 @@
+/dev/null
\ No newline at end of file
diff --git a/test/test-enabled-root/etc/systemd/system/maskedstatic.service
b/test/test-enabled-root/etc/systemd/system/maskedstatic.service
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/maskedstatic.service
@@ -0,0 +1 @@
+/dev/null
\ No newline at end of file
diff --git a/test/test-enabled-root/etc/systemd/system/some.target
b/test/test-enabled-root/etc/systemd/system/some.target
new file mode 100644
index 0000000..06eb04e
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/some.target
@@ -0,0 +1,11 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Some sort of target.
+Requires=multi-user.target
+After=multi-user.target
diff --git a/test/test-enabled-root/etc/systemd/system/some.target.wants/aliased.service
b/test/test-enabled-root/etc/systemd/system/some.target.wants/aliased.service
new file mode 120000
index 0000000..fec3d43
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/some.target.wants/aliased.service
@@ -0,0 +1 @@
+../../../../usr/lib/systemd/system/another.service
\ No newline at end of file
diff --git a/test/test-enabled-root/etc/systemd/system/some.target.wants/also_masked.service
b/test/test-enabled-root/etc/systemd/system/some.target.wants/also_masked.service
new file mode 120000
index 0000000..322a87b
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/some.target.wants/also_masked.service
@@ -0,0 +1 @@
+../../../../usr/lib/systemd/system/masked.service
\ No newline at end of file
diff --git a/test/test-enabled-root/etc/systemd/system/some.target.wants/another.service
b/test/test-enabled-root/etc/systemd/system/some.target.wants/another.service
new file mode 120000
index 0000000..fec3d43
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/some.target.wants/another.service
@@ -0,0 +1 @@
+../../../../usr/lib/systemd/system/another.service
\ No newline at end of file
diff --git a/test/test-enabled-root/etc/systemd/system/some.target.wants/different.service
b/test/test-enabled-root/etc/systemd/system/some.target.wants/different.service
new file mode 120000
index 0000000..40b36c8
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/some.target.wants/different.service
@@ -0,0 +1 @@
+../../../../usr/lib/systemd/system/unique.service
\ No newline at end of file
diff --git a/test/test-enabled-root/etc/systemd/system/some.target.wants/masked.service
b/test/test-enabled-root/etc/systemd/system/some.target.wants/masked.service
new file mode 120000
index 0000000..322a87b
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/some.target.wants/masked.service
@@ -0,0 +1 @@
+../../../../usr/lib/systemd/system/masked.service
\ No newline at end of file
diff --git a/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at four.service
b/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at four.service
new file mode 120000
index 0000000..7ccde6b
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at four.service
@@ -0,0 +1 @@
+../../../../usr/lib/systemd/system/templating at four.service
\ No newline at end of file
diff --git a/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at one.service
b/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at one.service
new file mode 120000
index 0000000..16f781d
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at one.service
@@ -0,0 +1 @@
+../../../../usr/lib/systemd/system/templating at .service
\ No newline at end of file
diff --git a/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at three.service
b/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at three.service
new file mode 120000
index 0000000..16f781d
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at three.service
@@ -0,0 +1 @@
+../../../../usr/lib/systemd/system/templating at .service
\ No newline at end of file
diff --git a/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at two.service
b/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at two.service
new file mode 120000
index 0000000..ee2c510
--- /dev/null
+++ b/test/test-enabled-root/etc/systemd/system/some.target.wants/templating at two.service
@@ -0,0 +1 @@
+../../../../usr/lib/systemd/system/templating at two.service
\ No newline at end of file
diff --git a/test/test-enabled-root/run/systemd/system/maskedruntime.service
b/test/test-enabled-root/run/systemd/system/maskedruntime.service
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/test/test-enabled-root/run/systemd/system/maskedruntime.service
@@ -0,0 +1 @@
+/dev/null
\ No newline at end of file
diff --git a/test/test-enabled-root/run/systemd/system/maskedruntimestatic.service
b/test/test-enabled-root/run/systemd/system/maskedruntimestatic.service
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/test/test-enabled-root/run/systemd/system/maskedruntimestatic.service
@@ -0,0 +1 @@
+/dev/null
\ No newline at end of file
diff --git a/test/test-enabled-root/run/systemd/system/other.target
b/test/test-enabled-root/run/systemd/system/other.target
new file mode 100644
index 0000000..0f54eb3
--- /dev/null
+++ b/test/test-enabled-root/run/systemd/system/other.target
@@ -0,0 +1,14 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Other Target
+Requires=multi-user.target
+After=multi-user.target
+Conflicts=rescue.target
+Wants=display-manager.service
+AllowIsolate=yes
diff --git a/test/test-enabled-root/run/systemd/system/other.target.wants/runtime.service
b/test/test-enabled-root/run/systemd/system/other.target.wants/runtime.service
new file mode 120000
index 0000000..a28ebda
--- /dev/null
+++ b/test/test-enabled-root/run/systemd/system/other.target.wants/runtime.service
@@ -0,0 +1 @@
+../../../../usr/lib/systemd/system/runtime.service
\ No newline at end of file
diff --git a/test/test-enabled-root/usr/lib/systemd/system/another.service
b/test/test-enabled-root/usr/lib/systemd/system/another.service
new file mode 100644
index 0000000..e4ea7f3
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/another.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Another Service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Another Service Start"
+
+[Install]
+WantedBy=some.target
diff --git a/test/test-enabled-root/usr/lib/systemd/system/disabled.service
b/test/test-enabled-root/usr/lib/systemd/system/disabled.service
new file mode 100644
index 0000000..f1d1fc6
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/disabled.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Disabled Service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Disabled Service Start"
+
+[Install]
+WantedBy=some.target
diff --git a/test/test-enabled-root/usr/lib/systemd/system/invalid.service
b/test/test-enabled-root/usr/lib/systemd/system/invalid.service
new file mode 100644
index 0000000..e420fe4
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/invalid.service
@@ -0,0 +1 @@
+INVALID
diff --git a/test/test-enabled-root/usr/lib/systemd/system/masked.service
b/test/test-enabled-root/usr/lib/systemd/system/masked.service
new file mode 100644
index 0000000..7a64302
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/masked.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Masked Service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Masked Service Start"
+
+[Install]
+WantedBy=some.target
diff --git a/test/test-enabled-root/usr/lib/systemd/system/maskedruntime.service
b/test/test-enabled-root/usr/lib/systemd/system/maskedruntime.service
new file mode 100644
index 0000000..db50f6e
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/maskedruntime.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Masked Runtime Service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Masked Runtime Service Start"
+
+[Install]
+WantedBy=some.target
diff --git a/test/test-enabled-root/usr/lib/systemd/system/maskedruntimestatic.service
b/test/test-enabled-root/usr/lib/systemd/system/maskedruntimestatic.service
new file mode 100644
index 0000000..d2a95d2
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/maskedruntimestatic.service
@@ -0,0 +1,6 @@
+[Unit]
+Description=Masked Runtime Static Service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Masked Runtime Static Service Start"
diff --git a/test/test-enabled-root/usr/lib/systemd/system/maskedstatic.service
b/test/test-enabled-root/usr/lib/systemd/system/maskedstatic.service
new file mode 100644
index 0000000..dd2ff1e
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/maskedstatic.service
@@ -0,0 +1,6 @@
+[Unit]
+Description=Masked Static Service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Masked Static Service Start"
diff --git a/test/test-enabled-root/usr/lib/systemd/system/runtime.service
b/test/test-enabled-root/usr/lib/systemd/system/runtime.service
new file mode 100644
index 0000000..0b24bd0
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/runtime.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Runtime Service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Runtime Service Start"
+
+[Install]
+WantedBy=other.target
diff --git a/test/test-enabled-root/usr/lib/systemd/system/static.service
b/test/test-enabled-root/usr/lib/systemd/system/static.service
new file mode 100644
index 0000000..a5f6c11
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/static.service
@@ -0,0 +1,6 @@
+[Unit]
+Description=Static Service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Static Service Start"
diff --git a/test/test-enabled-root/usr/lib/systemd/system/templating at .service
b/test/test-enabled-root/usr/lib/systemd/system/templating at .service
new file mode 100644
index 0000000..57a8c9d
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/templating at .service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Templating Service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Templating Service Start"
+
+[Install]
+WantedBy=some.target
diff --git a/test/test-enabled-root/usr/lib/systemd/system/templating at three.service
b/test/test-enabled-root/usr/lib/systemd/system/templating at three.service
new file mode 100644
index 0000000..d2b2d6c
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/templating at three.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Templating Three Service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Templating Three Start"
+
+[Install]
+WantedBy=some.target
diff --git a/test/test-enabled-root/usr/lib/systemd/system/templating at two.service
b/test/test-enabled-root/usr/lib/systemd/system/templating at two.service
new file mode 100644
index 0000000..3517229
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/templating at two.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Templating Two
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Templating Two Start"
+
+[Install]
+WantedBy=some.target
diff --git a/test/test-enabled-root/usr/lib/systemd/system/unique.service
b/test/test-enabled-root/usr/lib/systemd/system/unique.service
new file mode 100644
index 0000000..efd176f
--- /dev/null
+++ b/test/test-enabled-root/usr/lib/systemd/system/unique.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Unique Service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/echo "Unique Service Start"
+
+[Install]
+WantedBy=some.target
-- 
1.9.3


More information about the systemd-devel mailing list