X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Finstall.c;h=cfbd50ead954c93e518f19d05be6ef7f6402f5fe;hp=a05002d073e805ab1b11e3f693d07963fd47903c;hb=dad503169b2665ecfd3f5bfb3c936897e44ecca7;hpb=b4f10a5e8956d26f0bc6b9aef12846b57caee08b diff --git a/src/install.c b/src/install.c index a05002d07..cfbd50ead 100644 --- a/src/install.c +++ b/src/install.c @@ -1,9 +1,9 @@ -/*-*- Mode: C; c-basic-offset: 8 -*-*/ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ /*** This file is part of systemd. - Copyright 2010 Lennart Poettering + Copyright 2011 Lennart Poettering systemd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -11,7 +11,7 @@ (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 + WITHOUT ANY WARRANTY; without even the implied warranty . ***/ -#include -#include -#include #include +#include #include +#include +#include -#include "log.h" -#include "path-lookup.h" #include "util.h" -#include "macro.h" +#include "hashmap.h" +#include "set.h" +#include "path-lookup.h" #include "strv.h" +#include "unit-name.h" +#include "install.h" #include "conf-parser.h" -#include "dbus-common.h" -#include "sd-daemon.h" - -static bool arg_force = false; - -static enum { - WHERE_SYSTEM, - WHERE_SESSION, - WHERE_GLOBAL, -} arg_where = WHERE_SYSTEM; - -static enum { - ACTION_INVALID, - ACTION_ENABLE, - ACTION_DISABLE, - ACTION_TEST -} arg_action = ACTION_INVALID; - -static enum { - REALIZE_NO, /* Don't reload/start/stop or anything */ - REALIZE_RELOAD, /* Only reload daemon config, don't stop/start */ - REALIZE_MINIMAL, /* Only shutdown/restart if running. */ - REALIZE_MAYBE, /* Start if WantedBy= suggests */ - REALIZE_YES /* Start unconditionally */ -} arg_realize = REALIZE_NO; typedef struct { char *name; @@ -65,840 +42,1913 @@ typedef struct { char **wanted_by; } InstallInfo; -Hashmap *will_install = NULL, *have_installed = NULL; - -static int help(void) { - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Install init system units.\n\n" - " -h --help Show this help\n" - " --force Override existing links\n" - " --system Install into system\n" - " --session Install into session\n" - " --global Install into all sessions\n" - " --realize[=MODE] Start/stop/restart unit after installation\n" - " Takes 'no', 'minimal', 'maybe' or 'yes'\n\n" - "Commands:\n" - " enable [NAME...] Enable one or more units\n" - " disable [NAME...] Disable one or more units\n" - " test [NAME...] Test whether any of the specified units are enabled\n", - program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { +typedef struct { + Hashmap *will_install; + Hashmap *have_installed; +} InstallContext; - enum { - ARG_SESSION = 0x100, - ARG_SYSTEM, - ARG_GLOBAL, - ARG_FORCE, - ARG_REALIZE - }; +static int lookup_paths_init_from_scope(LookupPaths *paths, UnitFileScope scope) { + assert(paths); + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "session", no_argument, NULL, ARG_SESSION }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "global", no_argument, NULL, ARG_GLOBAL }, - { "force", no_argument, NULL, ARG_FORCE }, - { "realize", optional_argument, NULL, ARG_REALIZE }, - { NULL, 0, NULL, 0 } - }; + zero(*paths); - int c; + return lookup_paths_init(paths, + scope == UNIT_FILE_SYSTEM ? MANAGER_SYSTEM : MANAGER_USER, + scope == UNIT_FILE_USER); +} - assert(argc >= 1); - assert(argv); +static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) { + char *p = NULL; + int r; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(ret); - switch (c) { + switch (scope) { - case 'h': - help(); - return 0; + case UNIT_FILE_SYSTEM: - case ARG_SESSION: - arg_where = WHERE_SESSION; - break; + if (root_dir && runtime) + return -EINVAL; - case ARG_SYSTEM: - arg_where = WHERE_SYSTEM; - break; + 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); - case ARG_GLOBAL: - arg_where = WHERE_GLOBAL; - break; + break; - case ARG_FORCE: - arg_force = true; - break; + case UNIT_FILE_GLOBAL: - case ARG_REALIZE: - - if (!optarg) - arg_realize = REALIZE_MAYBE; - else if (streq(optarg, "no")) - arg_realize = REALIZE_NO; - else if (streq(optarg, "minimal")) - arg_realize = REALIZE_MINIMAL; - else if (streq(optarg, "maybe")) - arg_realize = REALIZE_MAYBE; - else if (streq(optarg, "yes")) - arg_realize = REALIZE_YES; - else { - log_error("Invalid --realize argument %s", optarg); - return -EINVAL; - } + if (root_dir) + return -EINVAL; - break; + if (runtime) + p = strdup("/run/systemd/user"); + else + p = strdup(USER_CONFIG_UNIT_PATH); + break; - case '?': - return -EINVAL; + case UNIT_FILE_USER: - default: - log_error("Unknown option code %c", c); + if (root_dir || runtime) return -EINVAL; - } - } - - if (optind >= argc) { - help(); - return -EINVAL; - } - if (streq(argv[optind], "enable")) - arg_action = ACTION_ENABLE; - else if (streq(argv[optind], "disable")) - arg_action = ACTION_DISABLE; - else if (streq(argv[optind], "test")) - arg_action = ACTION_TEST; - else { - log_error("Unknown verb %s.", argv[optind]); - return -EINVAL; - } + r = user_config_home(&p); + if (r <= 0) + return r < 0 ? r : -ENOENT; - optind++; + break; - if (optind >= argc) { - log_error("Missing unit name."); - return -EINVAL; + default: + assert_not_reached("Bad scope"); } + if (!p) + return -ENOMEM; - return 1; + *ret = p; + return 0; } -static void install_info_free(InstallInfo *i) { - assert(i); - - free(i->name); - free(i->path); - strv_free(i->aliases); - strv_free(i->wanted_by); - free(i); -} +static int add_file_change( + UnitFileChange **changes, + unsigned *n_changes, + UnitFileChangeType type, + const char *path, + const char *source) { -static void install_info_hashmap_free(Hashmap *m) { - InstallInfo *i; + UnitFileChange *c; + unsigned i; - while ((i = hashmap_steal_first(m))) - install_info_free(i); + assert(path); + assert(!changes == !n_changes); - hashmap_free(m); -} + if (!changes) + return 0; -static bool unit_name_valid(const char *name) { + c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange)); + if (!c) + return -ENOMEM; - /* This is a minimal version of unit_name_valid() from - * unit-name.c */ + *changes = c; + i = *n_changes; - if (!*name) - return false; + c[i].type = type; + c[i].path = strdup(path); + if (!c[i].path) + return -ENOMEM; - if (ignore_file(name)) - return false; + if (source) { + c[i].source = strdup(source); + if (!c[i].source) { + free(c[i].path); + return -ENOMEM; + } + } else + c[i].source = NULL; - return true; + *n_changes = i+1; + return 0; } -static int install_info_add(const char *name) { - InstallInfo *i; +static int mark_symlink_for_removal( + Set **remove_symlinks_to, + const char *p) { + + char *n; int r; - if (!unit_name_valid(name)) - return -EINVAL; + assert(p); - if (hashmap_get(have_installed, name) || - hashmap_get(will_install, name)) - return 0; + r = set_ensure_allocated(remove_symlinks_to, string_hash_func, string_compare_func); + if (r < 0) + return r; - if (!(i = new0(InstallInfo, 1))) { - r = -ENOMEM; - goto fail; - } + n = strdup(p); + if (!n) + return -ENOMEM; - if (!(i->name = strdup(name))) { - r = -ENOMEM; - goto fail; - } + path_kill_slashes(n); - if ((r = hashmap_put(will_install, i->name, i)) < 0) - goto fail; + r = set_put(*remove_symlinks_to, n); + if (r < 0) { + free(n); + return r == -EEXIST ? 0 : r; + } return 0; - -fail: - if (i) - install_info_free(i); - - return r; } -static int daemon_reload(DBusConnection *bus) { - DBusMessage *m = NULL, *reply = NULL; - DBusError error; - int r; - - assert(bus); - - dbus_error_init(&error); - - if (!(m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Reload"))) { - log_error("Could not allocate message."); - return -ENOMEM; +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) { + + int r = 0; + DIR *d; + struct dirent buffer, *de; + + 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; } - if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { - log_error("Failed to reload configuration: %s", error.message); - r = -EIO; - goto finish; - } + rewinddir(d); - r = 0; + for (;;) { + int k; -finish: - if (m) - dbus_message_unref(m); + k = readdir_r(d, &buffer, &de); + if (k != 0) { + r = -errno; + break; + } - if (reply) - dbus_message_unref(reply); + if (!de) + break; - dbus_error_free(&error); + if (ignore_file(de->d_name)) + continue; - return r; -} + dirent_ensure_type(d, de); -static int install_info_run(DBusConnection *bus, InstallInfo *i) { - DBusMessage *m = NULL, *reply = NULL; - DBusError error; - int r; - const char *mode = "replace"; + if (de->d_type == DT_DIR) { + int nfd, q; + char *p; - assert(bus); - assert(i); + nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (nfd < 0) { + if (errno == ENOENT) + continue; - dbus_error_init(&error); + if (r == 0) + r = -errno; + continue; + } - if (arg_action == ACTION_ENABLE) { + p = path_make_absolute(de->d_name, path); + if (!p) { + close_nointr_nofail(nfd); + r = -ENOMEM; + break; + } - if (arg_realize == REALIZE_MAYBE) { - char **k; - bool yes_please = false; + /* 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); + free(p); - STRV_FOREACH(k, i->wanted_by) { - DBusMessageIter sub, iter; + if (r == 0) + r = q; - const char *path, *state; - const char *interface = "org.freedesktop.systemd1.Unit"; - const char *property = "ActiveState"; + } else if (de->d_type == DT_LNK) { + char *p, *dest; + int q; + bool found; - if (!(m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnit"))) { - log_error("Could not allocate message."); - r = -ENOMEM; - goto finish; - } + p = path_make_absolute(de->d_name, path); + if (!p) { + r = -ENOMEM; + break; + } - if (!dbus_message_append_args(m, - DBUS_TYPE_STRING, k, - DBUS_TYPE_INVALID)) { - log_error("Could not append arguments to message."); - r = -ENOMEM; - goto finish; - } + q = readlink_and_canonicalize(p, &dest); + if (q < 0) { + free(p); - if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { - /* Hmm, this unit doesn't exist, let's try the next one */ - dbus_message_unref(m); - m = NULL; + if (q == -ENOENT) continue; - } - - if (!dbus_message_get_args(reply, &error, - DBUS_TYPE_OBJECT_PATH, &path, - DBUS_TYPE_INVALID)) { - log_error("Failed to parse reply: %s", error.message); - r = -EIO; - goto finish; - } - - dbus_message_unref(m); - if (!(m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - path, - "org.freedesktop.DBus.Properties", - "Get"))) { - log_error("Could not allocate message."); - r = -ENOMEM; - goto finish; - } - if (!dbus_message_append_args(m, - DBUS_TYPE_STRING, &interface, - DBUS_TYPE_STRING, &property, - DBUS_TYPE_INVALID)) { - log_error("Could not append arguments to message."); - r = -ENOMEM; - goto finish; - } + if (r == 0) + r = q; + continue; + } - dbus_message_unref(reply); - if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { - log_error("Failed to issue method call: %s", error.message); - r = -EIO; - goto finish; - } + found = + set_get(remove_symlinks_to, dest) || + set_get(remove_symlinks_to, file_name_from_path(dest)); - if (!dbus_message_iter_init(reply, &iter) || - dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { - log_error("Failed to parse reply."); - r = -EIO; - goto finish; - } + if (found) { - dbus_message_iter_recurse(&iter, &sub); + if (unlink(p) < 0 && errno != ENOENT) { - if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { - log_error("Failed to parse reply."); - r = -EIO; - goto finish; - } + if (r == 0) + r = -errno; + } else { + rmdir_parents(p, config_path); + path_kill_slashes(p); - dbus_message_iter_get_basic(&sub, &state); + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, p, NULL); - dbus_message_unref(m); - dbus_message_unref(reply); - m = reply = NULL; + if (!set_get(remove_symlinks_to, p)) { - if (streq(state, "active") || - startswith(state, "reloading") || - startswith(state, "activating")) { - yes_please = true; - break; + q = mark_symlink_for_removal(&remove_symlinks_to, p); + if (q < 0) { + if (r == 0) + r = q; + } else + *deleted = true; + } } } - if (!yes_please) { - r = 0; - goto finish; - } + free(p); + free(dest); } + } - if (!(m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - arg_realize == REALIZE_MINIMAL ? "TryRestartUnit" : "RestartUnit"))) { - log_error("Could not allocate message."); - r = -ENOMEM; - goto finish; - } + closedir(d); - if (!dbus_message_append_args(m, - DBUS_TYPE_STRING, &i->name, - DBUS_TYPE_STRING, &mode, - DBUS_TYPE_INVALID)) { - log_error("Could not append arguments to message."); - r = -ENOMEM; - goto finish; - } + return r; +} +static int remove_marked_symlinks( + Set *remove_symlinks_to, + const char *config_path, + UnitFileChange **changes, + unsigned *n_changes) { - } else if (arg_action == ACTION_DISABLE) { + int fd, r = 0; + bool deleted; - if (!(m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StopUnit"))) { - log_error("Could not allocate message."); - r = -ENOMEM; - goto finish; - } + assert(config_path); - if (!dbus_message_append_args(m, - DBUS_TYPE_STRING, &i->name, - DBUS_TYPE_STRING, &mode, - DBUS_TYPE_INVALID)) { - log_error("Could not append arguments to message."); - r = -ENOMEM; - goto finish; - } - } + if (set_size(remove_symlinks_to) <= 0) + return 0; - if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { - log_error("Failed to reload configuration: %s", error.message); - r = -EIO; - goto finish; - } + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) + return -errno; - r = 0; + do { + int q, cfd; + deleted = false; -finish: - if (m) - dbus_message_unref(m); + cfd = dup(fd); + if (cfd < 0) { + r = -errno; + break; + } - if (reply) - dbus_message_unref(reply); + /* 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); + if (r == 0) + r = q; + } while (deleted); - dbus_error_free(&error); + close_nointr_nofail(fd); return r; } -static int config_parse_also( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - const char *rvalue, - void *data, - void *userdata) { - - char *w; - size_t l; - char *state; +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; + struct dirent buffer, *de; + + 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; + } - assert(filename); - assert(lvalue); - assert(rvalue); + for (;;) { + int k; - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - char *n; - int r; + k = readdir_r(d, &buffer, &de); + if (k != 0) { + r = -errno; + break; + } - if (!(n = strndup(w, l))) - return -ENOMEM; + if (!de) + break; - r = install_info_add(n); - free(n); + if (ignore_file(de->d_name)) + continue; - if (r < 0) - return r; - } + dirent_ensure_type(d, de); - return 0; -} + if (de->d_type == DT_DIR) { + int nfd, q; + char *p; -static int create_symlink(const char *old_path, const char *new_path) { - int r; + nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (nfd < 0) { + if (errno == ENOENT) + continue; - assert(old_path); - assert(new_path); + if (r == 0) + r = -errno; + continue; + } - if (arg_action == ACTION_ENABLE) { - char *dest; + p = path_make_absolute(de->d_name, path); + if (!p) { + close_nointr_nofail(nfd); + r = -ENOMEM; + break; + } - mkdir_parents(new_path, 0755); + /* 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 (symlink(old_path, new_path) >= 0) - return 0; + if (q > 0) { + r = 1; + break; + } - if (errno != EEXIST) { - log_error("Cannot link %s to %s: %m", old_path, new_path); - return -errno; - } + if (r == 0) + r = q; - if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) { + } else if (de->d_type == DT_LNK) { + char *p, *dest; + bool found_path, found_dest, b = false; + int q; - if (errno == EINVAL) { - log_error("Cannot link %s to %s, file exists already and is not a symlink.", old_path, new_path); - return -EEXIST; + /* Acquire symlink name */ + p = path_make_absolute(de->d_name, path); + if (!p) { + r = -ENOMEM; + break; } - log_error("readlink() failed: %s", strerror(-r)); - return r; - } + /* Acquire symlink destination */ + q = readlink_and_canonicalize(p, &dest); + if (q < 0) { + free(p); - if (streq(dest, old_path)) { - free(dest); - return 0; - } + if (q == -ENOENT) + continue; - if (!arg_force) { - log_error("Cannot link %s to %s, symlink exists already and points to %s.", old_path, new_path, dest); - free(dest); - return -EEXIST; - } + if (r == 0) + r = q; + continue; + } - free(dest); - unlink(new_path); + /* 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); - if (symlink(old_path, new_path) >= 0) - return 0; + /* 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(file_name_from_path(dest), name); - log_error("Cannot link %s to %s: %m", old_path, new_path); - return -errno; + free(dest); - } else if (arg_action == ACTION_DISABLE) { - char *dest; + if (found_path && found_dest) { + char *t; - if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) { - if (errno == ENOENT) - return 0; + /* Filter out same name links in the main + * config path */ + t = path_make_absolute(name, config_path); + if (!t) { + free(p); + r = -ENOMEM; + break; + } - if (errno == EINVAL) { - log_warning("File %s not a symlink, ignoring.", old_path); - return 0; + b = path_equal(t, p); + free(t); } - log_error("readlink() failed: %s", strerror(-r)); - return r; - } + free(p); - if (!streq(dest, old_path)) { - log_warning("File %s not a symlink to %s but points to %s, ignoring.", new_path, old_path, dest); - free(dest); - return 0; + if (b) + *same_name_link = true; + else if (found_path || found_dest) { + r = 1; + break; + } } + } - free(dest); - if (unlink(new_path) >= 0) - return 0; + closedir(d); + + return r; +} + +static int find_symlinks( + const char *name, + const char *config_path, + bool *same_name_link) { + + int fd; - log_error("Cannot unlink %s: %m", new_path); + 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) return -errno; - } else if (arg_action == ACTION_TEST) { - char *dest; + /* 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 *path; + bool same_name_link_runtime = false, same_name_link = false; - if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) { + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); - if (errno == ENOENT || errno == EINVAL) - return 0; + if (scope == UNIT_FILE_SYSTEM || scope == UNIT_FILE_GLOBAL) { - log_error("readlink() failed: %s", strerror(-r)); + /* First look in runtime config path */ + r = get_config_path(scope, true, root_dir, &path); + if (r < 0) return r; - } - if (streq(dest, old_path)) { - free(dest); - return 1; + r = find_symlinks(name, path, &same_name_link_runtime); + free(path); + + if (r < 0) + return r; + else if (r > 0) { + *state = UNIT_FILE_ENABLED_RUNTIME; + return r; } + } - return 0; + /* 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); + free(path); + + if (r < 0) + return r; + else if (r > 0) { + *state = UNIT_FILE_ENABLED; + return r; } - assert_not_reached("Unknown action."); + /* 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; } -static int install_info_symlink_alias(InstallInfo *i, const char *config_path) { - char **s; - char *alias_path = NULL; +int unit_file_mask( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **i, *prefix; int r; - assert(i); + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); - STRV_FOREACH(s, i->aliases) { + r = get_config_path(scope, runtime, root_dir, &prefix); + if (r < 0) + return r; - if (!unit_name_valid(*s)) { - log_error("Invalid name %s.", *s); - r = -EINVAL; - goto finish; + STRV_FOREACH(i, files) { + char *path; + + if (!unit_name_is_valid_no_type(*i, true)) { + if (r == 0) + r = -EINVAL; + continue; } - free(alias_path); - if (!(alias_path = path_make_absolute(*s, config_path))) { - log_error("Out of memory"); + path = path_make_absolute(*i, prefix); + if (!path) { r = -ENOMEM; - goto finish; + break; } - if ((r = create_symlink(i->path, alias_path)) != 0) - goto finish; + if (symlink("/dev/null", path) >= 0) { + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null"); - if (arg_action == ACTION_DISABLE) - rmdir_parents(alias_path, config_path); - } + free(path); + continue; + } - r = 0; + if (errno == EEXIST) { -finish: - free(alias_path); + if (null_or_empty_path(path) > 0) { + free(path); + continue; + } - return r; -} + if (force) { + unlink(path); -static int install_info_symlink_wants(InstallInfo *i, const char *config_path) { - char **s; - char *alias_path = NULL; - int r; + if (symlink("/dev/null", path) >= 0) { - assert(i); + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null"); - STRV_FOREACH(s, i->wanted_by) { - if (!unit_name_valid(*s)) { - log_error("Invalid name %s.", *s); - r = -EINVAL; - goto finish; + free(path); + continue; + } + } + + if (r == 0) + r = -EEXIST; + } else { + if (r == 0) + r = -errno; } - free(alias_path); - alias_path = NULL; + free(path); + } + + free(prefix); + + 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; - if (asprintf(&alias_path, "%s/%s.wants/%s", config_path, *s, i->name) < 0) { - log_error("Out of memory"); + 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_no_type(*i, true)) { + if (r == 0) + r = -EINVAL; + continue; + } + + path = path_make_absolute(*i, config_path); + if (!path) { r = -ENOMEM; - goto finish; + break; } - if ((r = create_symlink(i->path, alias_path)) != 0) - goto finish; + 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; - if (arg_action == ACTION_DISABLE) - rmdir_parents(alias_path, config_path); + free(path); } - r = 0; finish: - free(alias_path); + q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); + if (r == 0) + r = q; + + set_free_free(remove_symlinks_to); + free(config_path); return r; } -static int install_info_apply(LookupPaths *paths, InstallInfo *i, const char *config_path) { +int unit_file_link( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { - const ConfigItem items[] = { - { "Alias", config_parse_strv, &i->aliases, "Install" }, - { "WantedBy", config_parse_strv, &i->wanted_by, "Install" }, - { "Also", config_parse_also, NULL, "Install" }, + LookupPaths paths; + char **i, *config_path = NULL; + int r, q; - { NULL, NULL, NULL, NULL } - }; + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); - char **p; - char *filename = NULL; - FILE *f = NULL; - int r; + zero(paths); - assert(paths); - assert(i); + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; - STRV_FOREACH(p, paths->unit_path) { + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + goto finish; - if (!(filename = path_make_absolute(i->name, *p))) { - log_error("Out of memory"); - return -ENOMEM; + STRV_FOREACH(i, files) { + char *path, *fn; + struct stat st; + + fn = file_name_from_path(*i); + + if (!path_is_absolute(*i) || + !unit_name_is_valid_no_type(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; } - if ((f = fopen(filename, "re"))) + q = in_search_path(*i, paths.unit_path); + if (q < 0) { + r = q; break; + } - free(filename); - filename = NULL; + if (q > 0) + continue; - if (errno != ENOENT) { - log_error("Failed to open %s: %m", filename); - return -errno; + path = path_make_absolute(fn, config_path); + if (!path) { + r = -ENOMEM; + break; } - } - if (!f) { - log_error("Couldn't find %s.", i->name); - return -ENOENT; - } + if (symlink(*i, path) >= 0) { + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i); - i->path = filename; + free(path); + continue; + } - if ((r = config_parse(filename, f, NULL, items, true, i)) < 0) { - fclose(f); - return r; - } + if (errno == EEXIST) { + char *dest = NULL; - fclose(f); + q = readlink_and_make_absolute(path, &dest); - if ((r = install_info_symlink_alias(i, config_path)) != 0) - return r; + if (q < 0 && errno != ENOENT) { + free(path); - if ((r = install_info_symlink_wants(i, config_path)) != 0) - return r; + if (r == 0) + r = q; - return 0; -} + continue; + } -static char *get_config_path(void) { + if (q >= 0 && path_equal(dest, *i)) { + free(dest); + free(path); + continue; + } + + free(dest); - switch (arg_where) { + if (force) { + unlink(path); - case WHERE_SYSTEM: - return strdup(SYSTEM_CONFIG_UNIT_PATH); + if (symlink(*i, path) >= 0) { - case WHERE_GLOBAL: - return strdup(SESSION_CONFIG_UNIT_PATH); + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i); - case WHERE_SESSION: { - char *p; + free(path); + continue; + } + } - if (session_config_home(&p) < 0) - return NULL; + if (r == 0) + r = -EEXIST; + } else { + if (r == 0) + r = -errno; + } - return p; + free(path); } - default: - assert_not_reached("Unknown config 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); } -static int do_run(void) { - DBusConnection *bus = NULL; - DBusError error; - int r, q; - Iterator i; - InstallInfo *j; +void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) { + unsigned i; - dbus_error_init(&error); + assert(changes || n_changes == 0); - if (arg_realize == REALIZE_NO) - return 0; + if (!changes) + return; - if (arg_where == WHERE_GLOBAL) { - log_warning("Warning: --realize has no effect with --global."); - return 0; + for (i = 0; i < n_changes; i++) { + free(changes[i].path); + free(changes[i].source); } - if (arg_action != ACTION_ENABLE && arg_action != ACTION_DISABLE) { - log_warning("Warning: --realize has no effect with test."); + 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); + 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 = file_name_from_path(path); + + if (!unit_name_is_valid_no_type(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 (arg_where == WHERE_SYSTEM && sd_booted() <= 0) { - log_info("systemd is not running, --realize has not effect."); - return 0; + if (path) { + i->path = strdup(path); + if (!i->path) { + r = -ENOMEM; + goto fail; + } } - if (arg_where == WHERE_SYSTEM && running_in_chroot() > 0) { - log_info("Running in a chroot() environment, --realize has no effect."); - return 0; + 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); } - if ((r = bus_connect(arg_where == WHERE_SESSION ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, NULL, &error)) < 0) { - log_error("Failed to get D-Bus connection: %s", error.message); - goto finish; + 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", "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 = 0; + r = config_parse(path, f, NULL, config_item_table_lookup, (void*) items, true, info); + fclose(f); + if (r < 0) + return r; - if (arg_action == ACTION_ENABLE) - if ((r = daemon_reload(bus)) < 0) - goto finish; + return strv_length(info->aliases) + strv_length(info->wanted_by); +} - if (arg_realize != REALIZE_RELOAD) { - HASHMAP_FOREACH(j, have_installed, i) - if ((q = install_info_run(bus, j)) < 0) - r = q; +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 + free(path); + + if (r != -ENOENT && r != -ELOOP) + return r; } - if (arg_action == ACTION_DISABLE) - if ((q = daemon_reload(bus)) < 0) - r = q; + return -ENOENT; +} -finish: - if (bus) - dbus_connection_unref(bus); +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); - dbus_error_free(&error); + install_context_done(&c); - dbus_shutdown(); return r; } -int main(int argc, char *argv[]) { - int r, retval = 1, j; - LookupPaths paths; - InstallInfo *i; - char *config_path = NULL; +static int create_symlink( + const char *old_path, + const char *new_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { - zero(paths); + char *dest; + int r; - log_parse_environment(); + assert(old_path); + assert(new_path); - if ((r = parse_argv(argc, argv)) < 0) - goto finish; - else if (r == 0) { - retval = 0; - goto finish; - } + mkdir_parents(new_path, 0755); - if ((r = lookup_paths_init(&paths, arg_where == WHERE_SYSTEM ? MANAGER_SYSTEM : MANAGER_SESSION)) < 0) { - log_error("Failed to determine lookup paths: %s", strerror(-r)); - goto finish; + if (symlink(old_path, new_path) >= 0) { + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); + return 0; } - if (!(config_path = get_config_path())) { - log_error("Failed to determine config path"); - goto finish; - } + if (errno != EEXIST) + return -errno; - will_install = hashmap_new(string_hash_func, string_compare_func); - have_installed = hashmap_new(string_hash_func, string_compare_func); + r = readlink_and_make_absolute(new_path, &dest); + if (r < 0) + return r; - if (!will_install || !have_installed) { - log_error("Failed to allocate unit sets."); - goto finish; + if (path_equal(dest, old_path)) { + free(dest); + return 0; } - for (j = optind; j < argc; j++) - if ((r = install_info_add(argv[j])) < 0) - goto finish; - - while ((i = hashmap_first(will_install))) { - assert_se(hashmap_move_one(have_installed, will_install, i->name) == 0); + free(dest); - if ((r = install_info_apply(&paths, i, config_path)) != 0) { + if (force) + return -EEXIST; - if (r < 0) - goto finish; + unlink(new_path); - /* In test mode and found something */ - retval = 0; - goto finish; - } + 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; } - if (do_run() < 0) - goto finish; + return -errno; +} - retval = arg_action == ACTION_TEST ? 1 : 0; +static int install_info_symlink_alias( + InstallInfo *i, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { -finish: - install_info_hashmap_free(will_install); - install_info_hashmap_free(have_installed); + char **s; + int r = 0, q; - lookup_paths_free(&paths); + assert(i); + assert(config_path); - free(config_path); + STRV_FOREACH(s, i->aliases) { + char *alias_path; - return retval; -} + 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_no_type(*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_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_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; + + 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 hat 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); + 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); + + /* 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_no_type(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) { + if (errno == ENOENT) + continue; + + r = -errno; + 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", + "/lib/systemd/system.preset", + 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_no_type(*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); + 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) { + struct dirent buffer, *de; + 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 (;;) { + UnitFileList *f; + + r = readdir_r(d, &buffer, &de); + if (r != 0) { + r = -r; + goto finish; + } + + if (!de) + break; + + if (ignore_file(de->d_name)) + continue; + + if (!unit_name_is_valid_no_type(de->d_name, true)) + continue; + + if (hashmap_get(h, de->d_name)) + continue; + + r = dirent_ensure_type(d, de); + if (r < 0) { + if (errno == 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) + goto found; + + r = unit_file_can_install(&paths, root_dir, f->path, true); + if (r < 0) { + free(f->path); + free(f); + goto finish; + } else if (r > 0) { + f->state = UNIT_FILE_DISABLED; + goto found; + } else { + f->state = UNIT_FILE_STATIC; + goto found; + } + + free(f->path); + free(f); + continue; + + found: + r = hashmap_put(h, file_name_from_path(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-runtie", + [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" +}; + +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);