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

Zbigniew Jędrzejewski-Szmek zbyszek at in.waw.pl
Sun Oct 12 12:07:55 PDT 2014


On Tue, Oct 07, 2014 at 11:08:13PM -0700, Ken Sedgwick wrote:
> Resubmitting using git format-patch, git imap-send ... no code changes.

Hi,
thank you for working on systemd, it is appreciated. Nevertheless,
this patch is hard to apply for a couple of reasons.

First, it does not apply: it is line broken and base64 encoded. The
encoding is not a problem, but I'd simply fix the lines otherwise, and
with base64 encoding it is a hassle. Please, try to send it to
yourself, save the mail and apply with git am. (You wrote that you
send it with git-send-email, but this doesn't seem possible.)

Second, please structure your commit messages as
- a short line
  (an empty line)
- a paragraph
  (en empty line)
- ...

And describe what the patch does, so the first (short) line should be
a title like 'tests: add test for ...' and then the paragraphs
following that should give an overview of the *reasons* for the
change, and describe the new state if necessary. In case of a test
that is fairly obvious, so a long description is not necessary, but at
least write one sentence about what you are testing. The first line is
short because it is shown in gitk, git log --oneline, etc, where
there's no space for a sentence.

Third, you sometimes included a "changelog" of what changed between
patch versions, but not always. Please do this in case of a big 
patch like this. But do not include details like 'resent using
git format-patch...' in this, because this is not something that
would be interesting for a person reading the git log.

Fourth, when sending multiple versions of multiple patches please
either number them by version, or use --in-reply-to to attach
them at the end of an existing thread, and if possible do both.

Also, not strictly necessary, but if the patch was split (as it
originally was) into two parts, the first adding tests, and the
second one changing functionality, it would be much easier to
review, at least for me.

Fifth, please describe the patch that changes the functionality
in detail: your assumptions, old behaviour, new behaviour, etc.
This area of the code has had a long series of mistakes and broken
assumptions.

So, please resend, I'll try to review and apply.

Zbyszek


> 
> ---
>  .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
> _______________________________________________
> systemd-devel mailing list
> systemd-devel at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/systemd-devel


More information about the systemd-devel mailing list