/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/

/***
  This file is part of systemd.

  Copyright 2011 Lennart Poettering

  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 <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <fnmatch.h>

#include "util.h"
#include "mkdir.h"
#include "hashmap.h"
#include "set.h"
#include "path-util.h"
#include "path-lookup.h"
#include "strv.h"
#include "unit-name.h"
#include "install.h"
#include "conf-parser.h"
#include "conf-files.h"

typedef struct {
        char *name;
        char *path;

        char **aliases;
        char **wanted_by;
        char **required_by;
} InstallInfo;

typedef struct {
        Hashmap *will_install;
        Hashmap *have_installed;
} InstallContext;

static int lookup_paths_init_from_scope(LookupPaths *paths, UnitFileScope scope) {
        assert(paths);
        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);

        zero(*paths);

        return lookup_paths_init(paths,
                                 scope == UNIT_FILE_SYSTEM ? SYSTEMD_SYSTEM : SYSTEMD_USER,
                                 scope == UNIT_FILE_USER,
                                 NULL, NULL, NULL);
}

static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) {
        char *p = NULL;
        int r;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);
        assert(ret);

        switch (scope) {

        case UNIT_FILE_SYSTEM:

                if (root_dir && runtime)
                        asprintf(&p, "%s/run/systemd/system", root_dir);
                else if (runtime)
                        p = strdup("/run/systemd/system");
                else if (root_dir)
                        asprintf(&p, "%s/%s", root_dir, SYSTEM_CONFIG_UNIT_PATH);
                else
                        p = strdup(SYSTEM_CONFIG_UNIT_PATH);

                break;

        case UNIT_FILE_GLOBAL:

                if (root_dir)
                        return -EINVAL;

                if (runtime)
                        p = strdup("/run/systemd/user");
                else
                        p = strdup(USER_CONFIG_UNIT_PATH);
                break;

        case UNIT_FILE_USER:

                if (root_dir || runtime)
                        return -EINVAL;

                r = user_config_home(&p);
                if (r <= 0)
                        return r < 0 ? r : -ENOENT;

                break;

        default:
                assert_not_reached("Bad scope");
        }

        if (!p)
                return -ENOMEM;

        *ret = p;
        return 0;
}

static int add_file_change(
                UnitFileChange **changes,
                unsigned *n_changes,
                UnitFileChangeType type,
                const char *path,
                const char *source) {

        UnitFileChange *c;
        unsigned i;

        assert(path);
        assert(!changes == !n_changes);

        if (!changes)
                return 0;

        c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange));
        if (!c)
                return -ENOMEM;

        *changes = c;
        i = *n_changes;

        c[i].type = type;
        c[i].path = strdup(path);
        if (!c[i].path)
                return -ENOMEM;

        if (source) {
                c[i].source = strdup(source);
                if (!c[i].source) {
                        free(c[i].path);
                        return -ENOMEM;
                }
        } else
                c[i].source = NULL;

        *n_changes = i+1;
        return 0;
}

static int mark_symlink_for_removal(
                Set **remove_symlinks_to,
                const char *p) {

        char *n;
        int r;

        assert(p);

        r = set_ensure_allocated(remove_symlinks_to, string_hash_func, string_compare_func);
        if (r < 0)
                return r;

        n = strdup(p);
        if (!n)
                return -ENOMEM;

        path_kill_slashes(n);

        r = set_put(*remove_symlinks_to, n);
        if (r < 0) {
                free(n);
                return r == -EEXIST ? 0 : r;
        }

        return 0;
}

static int remove_marked_symlinks_fd(
                Set *remove_symlinks_to,
                int fd,
                const char *path,
                const char *config_path,
                bool *deleted,
                UnitFileChange **changes,
                unsigned *n_changes,
                char** files) {

        int r = 0;
        DIR *d;

        assert(remove_symlinks_to);
        assert(fd >= 0);
        assert(path);
        assert(config_path);
        assert(deleted);

        d = fdopendir(fd);
        if (!d) {
                close_nointr_nofail(fd);
                return -errno;
        }

        rewinddir(d);

        for (;;) {
                struct dirent *de;
                union dirent_storage buf;
                int k;

                k = readdir_r(d, &buf.de, &de);
                if (k != 0) {
                        r = -errno;
                        break;
                }

                if (!de)
                        break;

                if (ignore_file(de->d_name))
                        continue;

                dirent_ensure_type(d, de);

                if (de->d_type == DT_DIR) {
                        int nfd, q;
                        char *p;

                        nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
                        if (nfd < 0) {
                                if (errno == ENOENT)
                                        continue;

                                if (r == 0)
                                        r = -errno;
                                continue;
                        }

                        p = path_make_absolute(de->d_name, path);
                        if (!p) {
                                close_nointr_nofail(nfd);
                                r = -ENOMEM;
                                break;
                        }

                        /* This will close nfd, regardless whether it succeeds or not */
                        q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, deleted, changes, n_changes, files);
                        free(p);

                        if (r == 0)
                                r = q;

                } else if (de->d_type == DT_LNK) {
                        char *p, *dest;
                        int q;
                        bool found;

                        p = path_make_absolute(de->d_name, path);
                        if (!p) {
                                r = -ENOMEM;
                                break;
                        }

                        q = readlink_and_canonicalize(p, &dest);
                        if (q < 0) {
                                free(p);

                                if (q == -ENOENT)
                                        continue;

                                if (r == 0)
                                        r = q;
                                continue;
                        }

                        found =
                                set_get(remove_symlinks_to, dest) ||
                                set_get(remove_symlinks_to, path_get_file_name(dest));

                        if (unit_name_is_instance(p))
                                found = found && strv_contains(files, path_get_file_name(p));

                        if (found) {

                                if (unlink(p) < 0 && errno != ENOENT) {

                                        if (r == 0)
                                                r = -errno;
                                } else {
                                        rmdir_parents(p, config_path);
                                        path_kill_slashes(p);

                                        add_file_change(changes, n_changes, UNIT_FILE_UNLINK, p, NULL);

                                        if (!set_get(remove_symlinks_to, p)) {

                                                q = mark_symlink_for_removal(&remove_symlinks_to, p);
                                                if (q < 0) {
                                                        if (r == 0)
                                                                r = q;
                                                } else
                                                        *deleted = true;
                                        }
                                }
                        }

                        free(p);
                        free(dest);
                }
        }

        closedir(d);

        return r;
}

static int remove_marked_symlinks(
                Set *remove_symlinks_to,
                const char *config_path,
                UnitFileChange **changes,
                unsigned *n_changes,
                char** files) {

        int fd, r = 0;
        bool deleted;

        assert(config_path);

        if (set_size(remove_symlinks_to) <= 0)
                return 0;

        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
        if (fd < 0)
                return -errno;

        do {
                int q, cfd;
                deleted = false;

                cfd = dup(fd);
                if (cfd < 0) {
                        r = -errno;
                        break;
                }

                /* This takes possession of cfd and closes it */
                q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &deleted, changes, n_changes, files);
                if (r == 0)
                        r = q;
        } while (deleted);

        close_nointr_nofail(fd);

        return r;
}

static int find_symlinks_fd(
                const char *name,
                int fd,
                const char *path,
                const char *config_path,
                bool *same_name_link) {

        int r = 0;
        DIR *d;

        assert(name);
        assert(fd >= 0);
        assert(path);
        assert(config_path);
        assert(same_name_link);

        d = fdopendir(fd);
        if (!d) {
                close_nointr_nofail(fd);
                return -errno;
        }

        for (;;) {
                int k;
                struct dirent *de;
                union dirent_storage buf;

                k = readdir_r(d, &buf.de, &de);
                if (k != 0) {
                        r = -errno;
                        break;
                }

                if (!de)
                        break;

                if (ignore_file(de->d_name))
                        continue;

                dirent_ensure_type(d, de);

                if (de->d_type == DT_DIR) {
                        int nfd, q;
                        char *p;

                        nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
                        if (nfd < 0) {
                                if (errno == ENOENT)
                                        continue;

                                if (r == 0)
                                        r = -errno;
                                continue;
                        }

                        p = path_make_absolute(de->d_name, path);
                        if (!p) {
                                close_nointr_nofail(nfd);
                                r = -ENOMEM;
                                break;
                        }

                        /* This will close nfd, regardless whether it succeeds or not */
                        q = find_symlinks_fd(name, nfd, p, config_path, same_name_link);
                        free(p);

                        if (q > 0) {
                                r = 1;
                                break;
                        }

                        if (r == 0)
                                r = q;

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

                        /* Acquire symlink name */
                        p = path_make_absolute(de->d_name, path);
                        if (!p) {
                                r = -ENOMEM;
                                break;
                        }

                        /* Acquire symlink destination */
                        q = readlink_and_canonicalize(p, &dest);
                        if (q < 0) {
                                free(p);

                                if (q == -ENOENT)
                                        continue;

                                if (r == 0)
                                        r = q;
                                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);

                        /* 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(path_get_file_name(dest), name);

                        free(dest);

                        if (found_path && found_dest) {
                                char *t;

                                /* Filter out same name links in the main
                                 * config path */
                                t = path_make_absolute(name, config_path);
                                if (!t) {
                                        free(p);
                                        r = -ENOMEM;
                                        break;
                                }

                                b = path_equal(t, p);
                                free(t);
                        }

                        free(p);

                        if (b)
                                *same_name_link = true;
                        else if (found_path || found_dest) {
                                r = 1;
                                break;
                        }
                }
        }

        closedir(d);

        return r;
}

static int find_symlinks(
                const char *name,
                const char *config_path,
                bool *same_name_link) {

        int fd;

        assert(name);
        assert(config_path);
        assert(same_name_link);

        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
        if (fd < 0) {
                if (errno == ENOENT)
                        return 0;
                return -errno;
        }

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

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

        int r;
        char _cleanup_free_ *path = NULL;
        bool same_name_link_runtime = false, same_name_link = false;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);
        assert(name);

        if (scope == UNIT_FILE_SYSTEM || scope == UNIT_FILE_GLOBAL) {

                /* First look in runtime config path */
                r = get_config_path(scope, true, root_dir, &path);
                if (r < 0)
                        return r;

                r = find_symlinks(name, path, &same_name_link_runtime);
                if (r < 0)
                        return r;
                else if (r > 0) {
                        *state = UNIT_FILE_ENABLED_RUNTIME;
                        return r;
                }
        }

        /* Then look in the normal config path */
        r = get_config_path(scope, false, root_dir, &path);
        if (r < 0)
                return r;

        r = find_symlinks(name, path, &same_name_link);
        if (r < 0)
                return r;
        else if (r > 0) {
                *state = UNIT_FILE_ENABLED;
                return r;
        }

        /* Hmm, we didn't find it, but maybe we found the same name
         * link? */
        if (same_name_link_runtime) {
                *state = UNIT_FILE_LINKED_RUNTIME;
                return 1;
        } else if (same_name_link) {
                *state = UNIT_FILE_LINKED;
                return 1;
        }

        return 0;
}

int unit_file_mask(
                UnitFileScope scope,
                bool runtime,
                const char *root_dir,
                char *files[],
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        char **i;
        char _cleanup_free_ *prefix;
        int r;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);

        r = get_config_path(scope, runtime, root_dir, &prefix);
        if (r < 0)
                return r;

        STRV_FOREACH(i, files) {
                char _cleanup_free_ *path = NULL;

                if (!unit_name_is_valid(*i, true)) {
                        if (r == 0)
                                r = -EINVAL;
                        continue;
                }

                path = path_make_absolute(*i, prefix);
                if (!path) {
                        r = -ENOMEM;
                        break;
                }

                if (symlink("/dev/null", path) >= 0) {
                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");

                        continue;
                }

                if (errno == EEXIST) {

                        if (null_or_empty_path(path) > 0)
                                continue;

                        if (force) {
                                unlink(path);

                                if (symlink("/dev/null", path) >= 0) {

                                        add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
                                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");

                                        continue;
                                }
                        }

                        if (r == 0)
                                r = -EEXIST;
                } else {
                        if (r == 0)
                                r = -errno;
                }
        }

        return r;
}

int unit_file_unmask(
                UnitFileScope scope,
                bool runtime,
                const char *root_dir,
                char *files[],
                UnitFileChange **changes,
                unsigned *n_changes) {

        char **i, *config_path = NULL;
        int r, q;
        Set *remove_symlinks_to = NULL;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);

        r = get_config_path(scope, runtime, root_dir, &config_path);
        if (r < 0)
                goto finish;

        STRV_FOREACH(i, files) {
                char *path;

                if (!unit_name_is_valid(*i, true)) {
                        if (r == 0)
                                r = -EINVAL;
                        continue;
                }

                path = path_make_absolute(*i, config_path);
                if (!path) {
                        r = -ENOMEM;
                        break;
                }

                q = null_or_empty_path(path);
                if (q > 0) {
                        if (unlink(path) >= 0) {
                                mark_symlink_for_removal(&remove_symlinks_to, path);
                                add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);

                                free(path);
                                continue;
                        }

                        q = -errno;
                }

                if (q != -ENOENT && r == 0)
                        r = q;

                free(path);
        }


finish:
        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
        if (r == 0)
                r = q;

        set_free_free(remove_symlinks_to);
        free(config_path);

        return r;
}

int unit_file_link(
                UnitFileScope scope,
                bool runtime,
                const char *root_dir,
                char *files[],
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        LookupPaths paths;
        char **i, *config_path = NULL;
        int r, q;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);

        zero(paths);

        r = lookup_paths_init_from_scope(&paths, scope);
        if (r < 0)
                return r;

        r = get_config_path(scope, runtime, root_dir, &config_path);
        if (r < 0)
                goto finish;

        STRV_FOREACH(i, files) {
                char *path, *fn;
                struct stat st;

                fn = path_get_file_name(*i);

                if (!path_is_absolute(*i) ||
                    !unit_name_is_valid(fn, true)) {
                        if (r == 0)
                                r = -EINVAL;
                        continue;
                }

                if (lstat(*i, &st) < 0) {
                        if (r == 0)
                                r = -errno;
                        continue;
                }

                if (!S_ISREG(st.st_mode)) {
                        r = -ENOENT;
                        continue;
                }

                q = in_search_path(*i, paths.unit_path);
                if (q < 0) {
                        r = q;
                        break;
                }

                if (q > 0)
                        continue;

                path = path_make_absolute(fn, config_path);
                if (!path) {
                        r = -ENOMEM;
                        break;
                }

                if (symlink(*i, path) >= 0) {
                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);

                        free(path);
                        continue;
                }

                if (errno == EEXIST) {
                        char *dest = NULL;

                        q = readlink_and_make_absolute(path, &dest);

                        if (q < 0 && errno != ENOENT) {
                                free(path);

                                if (r == 0)
                                        r = q;

                                continue;
                        }

                        if (q >= 0 && path_equal(dest, *i)) {
                                free(dest);
                                free(path);
                                continue;
                        }

                        free(dest);

                        if (force) {
                                unlink(path);

                                if (symlink(*i, path) >= 0) {

                                        add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
                                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);

                                        free(path);
                                        continue;
                                }
                        }

                        if (r == 0)
                                r = -EEXIST;
                } else {
                        if (r == 0)
                                r = -errno;
                }

                free(path);
        }

                finish:
        lookup_paths_free(&paths);
        free(config_path);

        return r;
}

void unit_file_list_free(Hashmap *h) {
        UnitFileList *i;

        while ((i = hashmap_steal_first(h))) {
                free(i->path);
                free(i);
        }

        hashmap_free(h);
}

void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) {
        unsigned i;

        assert(changes || n_changes == 0);

        if (!changes)
                return;

        for (i = 0; i < n_changes; i++) {
                free(changes[i].path);
                free(changes[i].source);
        }

        free(changes);
}

static void install_info_free(InstallInfo *i) {
        assert(i);

        free(i->name);
        free(i->path);
        strv_free(i->aliases);
        strv_free(i->wanted_by);
        strv_free(i->required_by);
        free(i);
}

static void install_info_hashmap_free(Hashmap *m) {
        InstallInfo *i;

        if (!m)
                return;

        while ((i = hashmap_steal_first(m)))
                install_info_free(i);

        hashmap_free(m);
}

static void install_context_done(InstallContext *c) {
        assert(c);

        install_info_hashmap_free(c->will_install);
        install_info_hashmap_free(c->have_installed);

        c->will_install = c->have_installed = NULL;
}

static int install_info_add(
                InstallContext *c,
                const char *name,
                const char *path) {
        InstallInfo *i = NULL;
        int r;

        assert(c);
        assert(name || path);

        if (!name)
                name = path_get_file_name(path);

        if (!unit_name_is_valid(name, true))
                return -EINVAL;

        if (hashmap_get(c->have_installed, name) ||
            hashmap_get(c->will_install, name))
                return 0;

        r = hashmap_ensure_allocated(&c->will_install, string_hash_func, string_compare_func);
        if (r < 0)
                return r;

        i = new0(InstallInfo, 1);
        if (!i)
                return -ENOMEM;

        i->name = strdup(name);
        if (!i->name) {
                r = -ENOMEM;
                goto fail;
        }

        if (path) {
                i->path = strdup(path);
                if (!i->path) {
                        r = -ENOMEM;
                        goto fail;
                }
        }

        r = hashmap_put(c->will_install, i->name, i);
        if (r < 0)
                goto fail;

        return 0;

fail:
        if (i)
                install_info_free(i);

        return r;
}

static int install_info_add_auto(
                InstallContext *c,
                const char *name_or_path) {

        assert(c);
        assert(name_or_path);

        if (path_is_absolute(name_or_path))
                return install_info_add(c, NULL, name_or_path);
        else
                return install_info_add(c, name_or_path, NULL);
}

static int config_parse_also(
                const char *filename,
                unsigned line,
                const char *section,
                const char *lvalue,
                int ltype,
                const char *rvalue,
                void *data,
                void *userdata) {

        char *w;
        size_t l;
        char *state;
        InstallContext *c = data;

        assert(filename);
        assert(lvalue);
        assert(rvalue);

        FOREACH_WORD_QUOTED(w, l, rvalue, state) {
                char *n;
                int r;

                n = strndup(w, l);
                if (!n)
                        return -ENOMEM;

                r = install_info_add(c, n, NULL);
                if (r < 0) {
                        free(n);
                        return r;
                }

                free(n);
        }

        return 0;
}

static int unit_file_load(
                InstallContext *c,
                InstallInfo *info,
                const char *path,
                bool allow_symlink) {

        const ConfigTableItem items[] = {
                { "Install", "Alias",      config_parse_strv, 0, &info->aliases     },
                { "Install", "WantedBy",   config_parse_strv, 0, &info->wanted_by   },
                { "Install", "RequiredBy", config_parse_strv, 0, &info->required_by },
                { "Install", "Also",       config_parse_also, 0, c                  },
                { NULL, NULL, NULL, 0, NULL }
        };

        int fd;
        FILE *f;
        int r;

        assert(c);
        assert(info);
        assert(path);

        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|(allow_symlink ? 0 : O_NOFOLLOW));
        if (fd < 0)
                return -errno;

        f = fdopen(fd, "re");
        if (!f) {
                close_nointr_nofail(fd);
                return -ENOMEM;
        }

        r = config_parse(path, f, NULL, config_item_table_lookup, (void*) items, true, info);
        fclose(f);
        if (r < 0)
                return r;

        return
                strv_length(info->aliases) +
                strv_length(info->wanted_by) +
                strv_length(info->required_by);
}

static int unit_file_search(
                InstallContext *c,
                InstallInfo *info,
                LookupPaths *paths,
                const char *root_dir,
                bool allow_symlink) {

        char **p;
        int r;

        assert(c);
        assert(info);
        assert(paths);

        if (info->path)
                return unit_file_load(c, info, info->path, allow_symlink);

        assert(info->name);

        STRV_FOREACH(p, paths->unit_path) {
                char *path = NULL;

                if (isempty(root_dir))
                        asprintf(&path, "%s/%s", *p, info->name);
                else
                        asprintf(&path, "%s/%s/%s", root_dir, *p, info->name);

                if (!path)
                        return -ENOMEM;

                r = unit_file_load(c, info, path, allow_symlink);

                if (r >= 0)
                        info->path = path;
                else {
                        if (r == -ENOENT && unit_name_is_instance(info->name)) {
                                /* unit file doesn't exist, however instance enablement was request */
                                /* we will check if it is possible to load template unit file */
                                char *template = NULL,
                                     *template_path = NULL,
                                     *template_dir = NULL;

                                template = unit_name_template(info->name);
                                if (!template) {
                                        free(path);
                                        return -ENOMEM;
                                }

                                /* we will reuse path variable since we don't need it anymore */
                                template_dir = path;
                                *(strrchr(path, '/') + 1) = '\0';

                                template_path = strjoin(template_dir, template, NULL);
                                if (!template_path) {
                                        free(path);
                                        free(template);
                                        return -ENOMEM;
                                }

                                /* let's try to load template unit */
                                r = unit_file_load(c, info, template_path, allow_symlink);
                                if (r >= 0) {
                                        info->path = strdup(template_path);
                                        if (!info->path) {
                                                free(path);
                                                free(template);
                                                free(template_path);
                                                return -ENOMEM;
                                        }
                                }

                                free(template);
                                free(template_path);
                        }
                        free(path);
                }

                if (r != -ENOENT && r != -ELOOP)
                        return r;
        }

        return -ENOENT;
}

static int unit_file_can_install(
                LookupPaths *paths,
                const char *root_dir,
                const char *name,
                bool allow_symlink) {

        InstallContext c;
        InstallInfo *i;
        int r;

        assert(paths);
        assert(name);

        zero(c);

        r = install_info_add_auto(&c, name);
        if (r < 0)
                return r;

        assert_se(i = hashmap_first(c.will_install));

        r = unit_file_search(&c, i, paths, root_dir, allow_symlink);

        if (r >= 0)
                r =
                        strv_length(i->aliases) +
                        strv_length(i->wanted_by) +
                        strv_length(i->required_by);

        install_context_done(&c);

        return r;
}

static int create_symlink(
                const char *old_path,
                const char *new_path,
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        char *dest;
        int r;

        assert(old_path);
        assert(new_path);

        mkdir_parents_label(new_path, 0755);

        if (symlink(old_path, new_path) >= 0) {
                add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
                return 0;
        }

        if (errno != EEXIST)
                return -errno;

        r = readlink_and_make_absolute(new_path, &dest);
        if (r < 0)
                return r;

        if (path_equal(dest, old_path)) {
                free(dest);
                return 0;
        }

        free(dest);

        if (!force)
                return -EEXIST;

        unlink(new_path);

        if (symlink(old_path, new_path) >= 0) {
                add_file_change(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL);
                add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
                return 0;
        }

        return -errno;
}

static int install_info_symlink_alias(
                InstallInfo *i,
                const char *config_path,
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        char **s;
        int r = 0, q;

        assert(i);
        assert(config_path);

        STRV_FOREACH(s, i->aliases) {
                char *alias_path;

                alias_path = path_make_absolute(*s, config_path);

                if (!alias_path)
                        return -ENOMEM;

                q = create_symlink(i->path, alias_path, force, changes, n_changes);
                free(alias_path);

                if (r == 0)
                        r = q;
        }

        return r;
}

static int install_info_symlink_wants(
                InstallInfo *i,
                const char *config_path,
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        char **s;
        int r = 0, q;

        assert(i);
        assert(config_path);

        STRV_FOREACH(s, i->wanted_by) {
                char *path;

                if (!unit_name_is_valid(*s, true)) {
                        r = -EINVAL;
                        continue;
                }

                if (asprintf(&path, "%s/%s.wants/%s", config_path, *s, i->name) < 0)
                        return -ENOMEM;

                q = create_symlink(i->path, path, force, changes, n_changes);
                free(path);

                if (r == 0)
                        r = q;
        }

        return r;
}

static int install_info_symlink_requires(
                InstallInfo *i,
                const char *config_path,
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        char **s;
        int r = 0, q;

        assert(i);
        assert(config_path);

        STRV_FOREACH(s, i->required_by) {
                char *path;

                if (!unit_name_is_valid(*s, true)) {
                        r = -EINVAL;
                        continue;
                }

                if (asprintf(&path, "%s/%s.requires/%s", config_path, *s, i->name) < 0)
                        return -ENOMEM;

                q = create_symlink(i->path, path, force, changes, n_changes);
                free(path);

                if (r == 0)
                        r = q;
        }

        return r;
}

static int install_info_symlink_link(
                InstallInfo *i,
                LookupPaths *paths,
                const char *config_path,
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        int r;
        char *path;

        assert(i);
        assert(paths);
        assert(config_path);
        assert(i->path);

        r = in_search_path(i->path, paths->unit_path);
        if (r != 0)
                return r;

        if (asprintf(&path, "%s/%s", config_path, i->name) < 0)
                return -ENOMEM;

        r = create_symlink(i->path, path, force, changes, n_changes);
        free(path);

        return r;
}

static int install_info_apply(
                InstallInfo *i,
                LookupPaths *paths,
                const char *config_path,
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        int r, q;

        assert(i);
        assert(paths);
        assert(config_path);

        r = install_info_symlink_alias(i, config_path, force, changes, n_changes);

        q = install_info_symlink_wants(i, config_path, force, changes, n_changes);
        if (r == 0)
                r = q;

        q = install_info_symlink_requires(i, config_path, force, changes, n_changes);
        if (r == 0)
                r = q;

        q = install_info_symlink_link(i, paths, config_path, force, changes, n_changes);
        if (r == 0)
                r = q;

        return r;
}

static int install_context_apply(
                InstallContext *c,
                LookupPaths *paths,
                const char *config_path,
                const char *root_dir,
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        InstallInfo *i;
        int r = 0, q;

        assert(c);
        assert(paths);
        assert(config_path);

        while ((i = hashmap_first(c->will_install))) {

                q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func);
                if (q < 0)
                        return q;

                assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);

                q = unit_file_search(c, i, paths, root_dir, false);
                if (q < 0) {
                        if (r >= 0)
                                r = q;

                        return r;
                } else if (r >= 0)
                        r += q;

                q = install_info_apply(i, paths, config_path, force, changes, n_changes);
                if (r >= 0 && q < 0)
                        r = q;
        }

        return r;
}

static int install_context_mark_for_removal(
                InstallContext *c,
                LookupPaths *paths,
                Set **remove_symlinks_to,
                const char *config_path,
                const char *root_dir) {

        InstallInfo *i;
        int r = 0, q;

        assert(c);
        assert(paths);
        assert(config_path);

        /* Marks all items for removal */

        while ((i = hashmap_first(c->will_install))) {

                q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func);
                if (q < 0)
                        return q;

                assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);

                q = unit_file_search(c, i, paths, root_dir, false);
                if (q < 0) {
                        if (r >= 0)
                                r = q;

                        return r;
                } else if (r >= 0)
                        r += q;

                if (unit_name_is_instance(i->name)) {
                        char *unit_file = NULL;

                        unit_file = path_get_file_name(i->path);

                        if (unit_name_is_instance(unit_file))
                                /* unit file named as instance exists, thus all symlinks pointing to it, will be removed */
                                q = mark_symlink_for_removal(remove_symlinks_to, i->name);
                        else
                                /* does not exist, thus we will mark for removal symlinks to template unit file */
                                q = mark_symlink_for_removal(remove_symlinks_to, unit_file);
                } else
                        q = mark_symlink_for_removal(remove_symlinks_to, i->name);

                if (r >= 0 && q < 0)
                        r = q;
        }

        return r;
}

int unit_file_enable(
                UnitFileScope scope,
                bool runtime,
                const char *root_dir,
                char *files[],
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        LookupPaths paths;
        InstallContext c;
        char **i, *config_path = NULL;
        int r;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);

        zero(paths);
        zero(c);

        r = lookup_paths_init_from_scope(&paths, scope);
        if (r < 0)
                return r;

        r = get_config_path(scope, runtime, root_dir, &config_path);
        if (r < 0)
                goto finish;

        STRV_FOREACH(i, files) {
                r = install_info_add_auto(&c, *i);
                if (r < 0)
                        goto finish;
        }

        /* This will return the number of symlink rules that were
        supposed to be created, not the ones actually created. This is
        useful to determine whether the passed files had any
        installation data at all. */
        r = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes);

finish:
        install_context_done(&c);
        lookup_paths_free(&paths);
        free(config_path);

        return r;
}

int unit_file_disable(
                UnitFileScope scope,
                bool runtime,
                const char *root_dir,
                char *files[],
                UnitFileChange **changes,
                unsigned *n_changes) {

        LookupPaths paths;
        InstallContext c;
        char **i, *config_path = NULL;
        Set *remove_symlinks_to = NULL;
        int r, q;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);

        zero(paths);
        zero(c);

        r = lookup_paths_init_from_scope(&paths, scope);
        if (r < 0)
                return r;

        r = get_config_path(scope, runtime, root_dir, &config_path);
        if (r < 0)
                goto finish;

        STRV_FOREACH(i, files) {
                r = install_info_add_auto(&c, *i);
                if (r < 0)
                        goto finish;
        }

        r = install_context_mark_for_removal(&c, &paths, &remove_symlinks_to, config_path, root_dir);

        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
        if (r == 0)
                r = q;

finish:
        install_context_done(&c);
        lookup_paths_free(&paths);
        set_free_free(remove_symlinks_to);
        free(config_path);

        return r;
}

int unit_file_reenable(
                UnitFileScope scope,
                bool runtime,
                const char *root_dir,
                char *files[],
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        LookupPaths paths;
        InstallContext c;
        char **i, *config_path = NULL;
        Set *remove_symlinks_to = NULL;
        int r, q;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);

        zero(paths);
        zero(c);

        r = lookup_paths_init_from_scope(&paths, scope);
        if (r < 0)
                return r;

        r = get_config_path(scope, runtime, root_dir, &config_path);
        if (r < 0)
                goto finish;

        STRV_FOREACH(i, files) {
                r = mark_symlink_for_removal(&remove_symlinks_to, *i);
                if (r < 0)
                        goto finish;

                r = install_info_add_auto(&c, *i);
                if (r < 0)
                        goto finish;
        }

        r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);

        /* Returns number of symlinks that where supposed to be installed. */
        q = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes);
        if (r == 0)
                r = q;

finish:
        lookup_paths_free(&paths);
        install_context_done(&c);
        set_free_free(remove_symlinks_to);
        free(config_path);

        return r;
}

UnitFileState unit_file_get_state(
                UnitFileScope scope,
                const char *root_dir,
                const char *name) {

        LookupPaths paths;
        UnitFileState state = _UNIT_FILE_STATE_INVALID;
        char **i, *path = NULL;
        int r;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);
        assert(name);

        zero(paths);

        if (root_dir && scope != UNIT_FILE_SYSTEM)
                return -EINVAL;

        if (!unit_name_is_valid(name, true))
                return -EINVAL;

        r = lookup_paths_init_from_scope(&paths, scope);
        if (r < 0)
                return r;

        STRV_FOREACH(i, paths.unit_path) {
                struct stat st;

                free(path);
                path = NULL;

                if (root_dir)
                        asprintf(&path, "%s/%s/%s", root_dir, *i, name);
                else
                        asprintf(&path, "%s/%s", *i, name);

                if (!path) {
                        r = -ENOMEM;
                        goto finish;
                }

                if (lstat(path, &st) < 0) {
                        r = -errno;
                        if (errno == ENOENT)
                                continue;

                        goto finish;
                }

                if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
                        r = -ENOENT;
                        goto finish;
                }

                r = null_or_empty_path(path);
                if (r < 0 && r != -ENOENT)
                        goto finish;
                else if (r > 0) {
                        state = path_startswith(*i, "/run") ?
                                UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
                        r = 0;
                        goto finish;
                }

                r = find_symlinks_in_scope(scope, root_dir, name, &state);
                if (r < 0) {
                        goto finish;
                } else if (r > 0) {
                        r = 0;
                        goto finish;
                }

                r = unit_file_can_install(&paths, root_dir, path, true);
                if (r < 0 && errno != -ENOENT)
                        goto finish;
                else if (r > 0) {
                        state = UNIT_FILE_DISABLED;
                        r = 0;
                        goto finish;
                } else if (r == 0) {
                        state = UNIT_FILE_STATIC;
                        r = 0;
                        goto finish;
                }
        }

finish:
        lookup_paths_free(&paths);
        free(path);

        return r < 0 ? r : state;
}

int unit_file_query_preset(UnitFileScope scope, const char *name) {
        char **files, **i;
        int r;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);
        assert(name);

        if (scope == UNIT_FILE_SYSTEM)
                r = conf_files_list(&files, ".preset",
                                    "/etc/systemd/system-preset",
                                    "/usr/local/lib/systemd/system-preset",
                                    "/usr/lib/systemd/system-preset",
#ifdef HAVE_SPLIT_USR
                                    "/lib/systemd/system-preset",
#endif
                                    NULL);
        else if (scope == UNIT_FILE_GLOBAL)
                r = conf_files_list(&files, ".preset",
                                    "/etc/systemd/user-preset",
                                    "/usr/local/lib/systemd/user-preset",
                                    "/usr/lib/systemd/user-preset",
                                    NULL);
        else
                return 1;

        if (r < 0)
                return r;

        STRV_FOREACH(i, files) {
                FILE *f;

                f = fopen(*i, "re");
                if (!f) {
                        if (errno == ENOENT)
                                continue;

                        r = -errno;
                        goto finish;
                }

                for (;;) {
                        char line[LINE_MAX], *l;

                        if (!fgets(line, sizeof(line), f))
                                break;

                        l = strstrip(line);
                        if (!*l)
                                continue;

                        if (strchr(COMMENTS, *l))
                                continue;

                        if (first_word(l, "enable")) {
                                l += 6;
                                l += strspn(l, WHITESPACE);

                                if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
                                        r = 1;
                                        fclose(f);
                                        goto finish;
                                }
                        } else if (first_word(l, "disable")) {
                                l += 7;
                                l += strspn(l, WHITESPACE);

                                if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
                                        r = 0;
                                        fclose(f);
                                        goto finish;
                                }
                        } else
                                log_debug("Couldn't parse line '%s'", l);
                }

                fclose(f);
        }

        /* Default is "enable" */
        r = 1;

finish:
        strv_free(files);

        return r;
}

int unit_file_preset(
                UnitFileScope scope,
                bool runtime,
                const char *root_dir,
                char *files[],
                bool force,
                UnitFileChange **changes,
                unsigned *n_changes) {

        LookupPaths paths;
        InstallContext plus, minus;
        char **i, *config_path = NULL;
        Set *remove_symlinks_to = NULL;
        int r, q;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);

        zero(paths);
        zero(plus);
        zero(minus);

        r = lookup_paths_init_from_scope(&paths, scope);
        if (r < 0)
                return r;

        r = get_config_path(scope, runtime, root_dir, &config_path);
        if (r < 0)
                goto finish;

        STRV_FOREACH(i, files) {

                if (!unit_name_is_valid(*i, true)) {
                        r = -EINVAL;
                        goto finish;
                }

                r = unit_file_query_preset(scope, *i);
                if (r < 0)
                        goto finish;

                if (r)
                        r = install_info_add_auto(&plus, *i);
                else
                        r = install_info_add_auto(&minus, *i);

                if (r < 0)
                        goto finish;
        }

        r = install_context_mark_for_removal(&minus, &paths, &remove_symlinks_to, config_path, root_dir);

        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
        if (r == 0)
                r = q;

        /* Returns number of symlinks that where supposed to be installed. */
        q = install_context_apply(&plus, &paths, config_path, root_dir, force, changes, n_changes);
        if (r == 0)
                r = q;

finish:
        lookup_paths_free(&paths);
        install_context_done(&plus);
        install_context_done(&minus);
        set_free_free(remove_symlinks_to);
        free(config_path);

        return r;
}

int unit_file_get_list(
                UnitFileScope scope,
                const char *root_dir,
                Hashmap *h) {

        LookupPaths paths;
        char **i, *buf = NULL;
        DIR *d = NULL;
        int r;

        assert(scope >= 0);
        assert(scope < _UNIT_FILE_SCOPE_MAX);
        assert(h);

        zero(paths);

        if (root_dir && scope != UNIT_FILE_SYSTEM)
                return -EINVAL;

        r = lookup_paths_init_from_scope(&paths, scope);
        if (r < 0)
                return r;

        STRV_FOREACH(i, paths.unit_path) {
                const char *units_dir;

                free(buf);
                buf = NULL;

                if (root_dir) {
                        if (asprintf(&buf, "%s/%s", root_dir, *i) < 0) {
                                r = -ENOMEM;
                                goto finish;
                        }
                        units_dir = buf;
                } else
                        units_dir = *i;

                if (d)
                        closedir(d);

                d = opendir(units_dir);
                if (!d) {
                        if (errno == ENOENT)
                                continue;

                        r = -errno;
                        goto finish;
                }

                for (;;) {
                        struct dirent *de;
                        union dirent_storage buffer;
                        UnitFileList *f;

                        r = readdir_r(d, &buffer.de, &de);
                        if (r != 0) {
                                r = -r;
                                goto finish;
                        }

                        if (!de)
                                break;

                        if (ignore_file(de->d_name))
                                continue;

                        if (!unit_name_is_valid(de->d_name, true))
                                continue;

                        if (hashmap_get(h, de->d_name))
                                continue;

                        r = dirent_ensure_type(d, de);
                        if (r < 0) {
                                if (r == -ENOENT)
                                        continue;

                                goto finish;
                        }

                        if (de->d_type != DT_LNK && de->d_type != DT_REG)
                                continue;

                        f = new0(UnitFileList, 1);
                        if (!f) {
                                r = -ENOMEM;
                                goto finish;
                        }

                        f->path = path_make_absolute(de->d_name, units_dir);
                        if (!f->path) {
                                free(f);
                                r = -ENOMEM;
                                goto finish;
                        }

                        r = null_or_empty_path(f->path);
                        if (r < 0 && r != -ENOENT) {
                                free(f->path);
                                free(f);
                                goto finish;
                        } else if (r > 0) {
                                f->state =
                                        path_startswith(*i, "/run") ?
                                        UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
                                goto found;
                        }

                        r = find_symlinks_in_scope(scope, root_dir, de->d_name, &f->state);
                        if (r < 0) {
                                free(f->path);
                                free(f);
                                goto finish;
                        } else if (r > 0) {
                                f->state = UNIT_FILE_ENABLED;
                                goto found;
                        }

                        r = unit_file_can_install(&paths, root_dir, f->path, true);
                        if (r == -EINVAL ||  /* Invalid setting? */
                            r == -EBADMSG || /* Invalid format? */
                            r == -ENOENT     /* Included file not found? */)
                                f->state = UNIT_FILE_INVALID;
                        else if (r < 0) {
                                free(f->path);
                                free(f);
                                goto finish;
                        } else if (r > 0)
                                f->state = UNIT_FILE_DISABLED;
                        else
                                f->state = UNIT_FILE_STATIC;

                found:
                        r = hashmap_put(h, path_get_file_name(f->path), f);
                        if (r < 0) {
                                free(f->path);
                                free(f);
                                goto finish;
                        }
                }
        }

finish:
        lookup_paths_free(&paths);
        free(buf);

        if (d)
                closedir(d);

        return r;
}

static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = {
        [UNIT_FILE_ENABLED] = "enabled",
        [UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtime",
        [UNIT_FILE_LINKED] = "linked",
        [UNIT_FILE_LINKED_RUNTIME] = "linked-runtime",
        [UNIT_FILE_MASKED] = "masked",
        [UNIT_FILE_MASKED_RUNTIME] = "masked-runtime",
        [UNIT_FILE_STATIC] = "static",
        [UNIT_FILE_DISABLED] = "disabled",
        [UNIT_FILE_INVALID] = "invalid",
};

DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState);

static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = {
        [UNIT_FILE_SYMLINK] = "symlink",
        [UNIT_FILE_UNLINK] = "unlink",
};

DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType);
