X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Finstall.c;h=1fb1f9d5800e7fc3ccad1b6c222be0058bfdbd18;hp=751d52b6480ade3b7d5f90b7dad105a34d9cda17;hb=d380a3bcd14376ed72286e78dbcc871b7d6d2151;hpb=246756ca928adaee98edd0e67712e81d8f3255a6 diff --git a/src/install.c b/src/install.c index 751d52b64..1fb1f9d58 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 +#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 bool arg_all = false; -static bool arg_verbose = false; - -static enum { - WHERE_SYSTEM, - WHERE_SESSION, - WHERE_GLOBAL, -} arg_where = WHERE_SYSTEM; - -static enum { - ACTION_INVALID, - ACTION_ENABLE, - ACTION_DISABLE, - ACTION_REALIZE, - 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; @@ -70,615 +42,250 @@ typedef struct { char **wanted_by; } InstallInfo; -static Hashmap *will_install = NULL, *have_installed = NULL; -static Set *remove_symlinks_to = 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" - " --verbose Show what is being done as it is done\n" - " --system Install into system\n" - " --session Install into session\n" - " --global Install into all sessions\n" - " --all When disabling, remove all symlinks, not\n" - " just those listed in the [Install] section\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" - " realize [NAME...] Test whether any of the specified units are enabled\n" - " and the start/stop/restart units accordingly\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[]) { - - enum { - ARG_SESSION = 0x100, - ARG_SYSTEM, - ARG_GLOBAL, - ARG_FORCE, - ARG_REALIZE, - ARG_ALL - }; - - 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 }, - { "all", no_argument, NULL, ARG_ALL }, - { "verbose", no_argument, NULL, 'v' }, - { NULL, 0, NULL, 0 } - }; - - int c; - bool realize_switch = false; - - assert(argc >= 1); - assert(argv); - - while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0) { - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_SESSION: - arg_where = WHERE_SESSION; - break; - - case ARG_SYSTEM: - arg_where = WHERE_SYSTEM; - break; - - case ARG_GLOBAL: - arg_where = WHERE_GLOBAL; - break; - - case ARG_FORCE: - arg_force = true; - break; - - case ARG_REALIZE: - - realize_switch = true; - - 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 if (streq(optarg, "reload")) - arg_realize = REALIZE_RELOAD; - else { - log_error("Invalid --realize argument %s", optarg); - return -EINVAL; - } - - break; - - case ARG_ALL: - arg_all = true; - break; - - case 'v': - arg_verbose = true; - break; - - case '?': - return -EINVAL; - - default: - log_error("Unknown option code %c", c); - 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 if (streq(argv[optind], "realize")) { - arg_action = ACTION_REALIZE; - - if (!realize_switch) - arg_realize = REALIZE_MAYBE; - } else { - log_error("Unknown verb %s.", argv[optind]); - return -EINVAL; - } - - optind++; +typedef struct { + Hashmap *will_install; + Hashmap *have_installed; +} InstallContext; - if (optind >= argc) { - log_error("Missing unit name."); - return -EINVAL; - } +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 1; + return lookup_paths_init(paths, + scope == UNIT_FILE_SYSTEM ? MANAGER_SYSTEM : MANAGER_USER, + scope == UNIT_FILE_USER); } -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 get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) { + char *p = NULL; + int r; -static void install_info_hashmap_free(Hashmap *m) { - InstallInfo *i; + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(ret); - while ((i = hashmap_steal_first(m))) - install_info_free(i); + switch (scope) { - hashmap_free(m); -} + case UNIT_FILE_SYSTEM: -static bool unit_name_valid(const char *name) { + 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); - /* This is a minimal version of unit_name_valid() from - * unit-name.c */ + break; - if (!*name) - return false; + case UNIT_FILE_GLOBAL: - if (ignore_file(name)) - return false; + if (root_dir) + return -EINVAL; - return true; -} + if (runtime) + p = strdup("/run/systemd/user"); + else + p = strdup(USER_CONFIG_UNIT_PATH); + break; -static int install_info_add(const char *name) { - InstallInfo *i; - int r; + case UNIT_FILE_USER: - if (!unit_name_valid(name)) - return -EINVAL; + if (root_dir || runtime) + return -EINVAL; - if (hashmap_get(have_installed, name) || - hashmap_get(will_install, name)) - return 0; + r = user_config_home(&p); + if (r <= 0) + return r < 0 ? r : -ENOENT; - if (!(i = new0(InstallInfo, 1))) { - r = -ENOMEM; - goto fail; - } + break; - if (!(i->name = strdup(name))) { - r = -ENOMEM; - goto fail; + default: + assert_not_reached("Bad scope"); } - if ((r = hashmap_put(will_install, i->name, i)) < 0) - goto fail; + if (!p) + return -ENOMEM; + *ret = p; 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; +static int add_file_change( + UnitFileChange **changes, + unsigned *n_changes, + UnitFileChangeType type, + const char *path, + const char *source) { - assert(bus); + UnitFileChange *c; + unsigned i; - dbus_error_init(&error); + assert(path); + assert(!changes == !n_changes); - if (arg_verbose) - log_info("Reloading daemon configuration."); + if (!changes) + return 0; - if (!(m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Reload"))) { - log_error("Could not allocate message."); + c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange)); + if (!c) return -ENOMEM; - } - - 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; - } - - r = 0; - -finish: - if (m) - dbus_message_unref(m); - - if (reply) - dbus_message_unref(reply); - - dbus_error_free(&error); - - return r; -} - -static int install_info_run(DBusConnection *bus, InstallInfo *i, bool enabled) { - DBusMessage *m = NULL, *reply = NULL; - DBusError error; - int r; - const char *mode = "replace"; - - assert(bus); - assert(i); - - dbus_error_init(&error); - - if (arg_action == ACTION_ENABLE || - (arg_action == ACTION_REALIZE && enabled)) { - - if (arg_realize == REALIZE_MAYBE) { - char **k; - bool yes_please = false; - - STRV_FOREACH(k, i->wanted_by) { - DBusMessageIter sub, iter; - - const char *path, *state; - const char *interface = "org.freedesktop.systemd1.Unit"; - const char *property = "ActiveState"; - - 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; - } - - 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; - } - - 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; - 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; - } - - 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; - } - - 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; - } - - dbus_message_iter_recurse(&iter, &sub); - - if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { - log_error("Failed to parse reply."); - r = -EIO; - goto finish; - } - - dbus_message_iter_get_basic(&sub, &state); - - dbus_message_unref(m); - dbus_message_unref(reply); - m = reply = NULL; - - if (streq(state, "active") || - startswith(state, "reloading") || - startswith(state, "activating")) { - yes_please = true; - break; - } - } - - if (!yes_please) { - r = 0; - goto finish; - } - } - - if (arg_verbose) - log_info("Restarting %s.", i->name); - - 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; - } - - 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; - } + *changes = c; + i = *n_changes; + c[i].type = type; + c[i].path = strdup(path); + if (!c[i].path) + return -ENOMEM; - } else if (arg_action == ACTION_DISABLE || - (arg_action == ACTION_REALIZE && !enabled)) { - - if (arg_verbose) - log_info("Stopping %s.", i->name); - - 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; - } - - 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 (source) { + c[i].source = strdup(source); + if (!c[i].source) { + free(c[i].path); + return -ENOMEM; } } else - assert_not_reached("install_info_run() called but nothing to do?"); - - if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { - log_error("Failed to realize unit: %s", error.message); - r = -EIO; - goto finish; - } - - r = 0; - -finish: - if (m) - dbus_message_unref(m); - - if (reply) - dbus_message_unref(reply); - - dbus_error_free(&error); - - 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; - - assert(filename); - assert(lvalue); - assert(rvalue); - - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - char *n; - int r; - - if (!(n = strndup(w, l))) - return -ENOMEM; - - r = install_info_add(n); - free(n); - - if (r < 0) - return r; - } + c[i].source = NULL; + *n_changes = i+1; return 0; } -static int mark_symlink_for_removal(const char *p) { +static int mark_symlink_for_removal( + Set **remove_symlinks_to, + const char *p) { + char *n; int r; assert(p); - assert(path_is_absolute(p)); - if (!remove_symlinks_to) - return 0; + r = set_ensure_allocated(remove_symlinks_to, string_hash_func, string_compare_func); + if (r < 0) + return r; - if (!(n = canonicalize_file_name(p))) - if (!(n = strdup(p))) - return -ENOMEM; + n = strdup(p); + if (!n) + return -ENOMEM; path_kill_slashes(n); - if ((r = set_put(remove_symlinks_to, n)) < 0) { + r = set_put(*remove_symlinks_to, n); + if (r < 0) { free(n); - return r; + return r == -EEXIST ? 0 : r; } return 0; } -static int remove_marked_symlinks_fd(int fd, const char *config_path, const char *root, bool *deleted) { +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 *de; + struct dirent buffer, *de; + assert(remove_symlinks_to); assert(fd >= 0); - assert(root); + assert(path); + assert(config_path); assert(deleted); - if (!(d = fdopendir(fd))) + d = fdopendir(fd); + if (!d) { + close_nointr_nofail(fd); return -errno; + } rewinddir(d); - while ((de = readdir(d))) { - bool is_dir = false, is_link = false; - - if (ignore_file(de->d_name)) - continue; + for (;;) { + int k; - if (de->d_type == DT_LNK) - is_link = true; - else if (de->d_type == DT_DIR) - is_dir = true; - else if (de->d_type == DT_UNKNOWN) { - struct stat st; - - if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - log_error("Failed to stat %s/%s: %m", root, de->d_name); + k = readdir_r(d, &buffer, &de); + if (k != 0) { + r = -errno; + break; + } - if (r == 0) - r = -errno; - continue; - } + if (!de) + break; - is_link = S_ISLNK(st.st_mode); - is_dir = S_ISDIR(st.st_mode); - } else + if (ignore_file(de->d_name)) continue; - if (is_dir) { + dirent_ensure_type(d, de); + + if (de->d_type == DT_DIR) { int nfd, q; char *p; - if ((nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW)) < 0) { - log_error("Failed to open %s/%s: %m", root, de->d_name); + 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; } - if (asprintf(&p, "%s/%s", root, de->d_name) < 0) { - log_error("Failed to allocate directory string."); + p = path_make_absolute(de->d_name, path); + if (!p) { close_nointr_nofail(nfd); r = -ENOMEM; break; } - q = remove_marked_symlinks_fd(nfd, config_path, p, deleted); + /* 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); - close_nointr_nofail(nfd); if (r == 0) - q = r; + r = q; - } else if (is_link) { - char *p, *dest, *c; + } else if (de->d_type == DT_LNK) { + char *p, *dest; int q; + bool found; - if (asprintf(&p, "%s/%s", root, de->d_name) < 0) { - log_error("Failed to allocate symlink string."); + p = path_make_absolute(de->d_name, path); + if (!p) { r = -ENOMEM; break; } - if ((q = readlink_and_make_absolute(p, &dest)) < 0) { - log_error("Cannot read symlink %s: %s", p, strerror(-q)); + q = readlink_and_canonicalize(p, &dest); + if (q < 0) { free(p); + if (q == -ENOENT) + continue; + if (r == 0) r = q; continue; } - if ((c = canonicalize_file_name(dest))) { - /* This might fail if the destination - * is already removed */ - - free(dest); - dest = c; - } - - path_kill_slashes(dest); - if (set_get(remove_symlinks_to, dest)) { + found = + set_get(remove_symlinks_to, dest) || + set_get(remove_symlinks_to, file_name_from_path(dest)); - if (arg_verbose) - log_info("rm '%s'", p); + if (found) { - if (unlink(p) < 0) { - log_error("Cannot unlink symlink %s: %m", p); + if (unlink(p) < 0 && errno != ENOENT) { if (r == 0) r = -errno; @@ -686,9 +293,12 @@ static int remove_marked_symlinks_fd(int fd, const char *config_path, const char 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)) { - if ((r = mark_symlink_for_removal(p)) < 0) { + q = mark_symlink_for_removal(&remove_symlinks_to, p); + if (q < 0) { if (r == 0) r = q; } else @@ -702,10 +312,17 @@ static int remove_marked_symlinks_fd(int fd, const char *config_path, const char } } + closedir(d); + return r; } -static int remove_marked_symlinks(const char *config_path) { +static int remove_marked_symlinks( + Set *remove_symlinks_to, + const char *config_path, + UnitFileChange **changes, + unsigned *n_changes) { + int fd, r = 0; bool deleted; @@ -714,17 +331,24 @@ static int remove_marked_symlinks(const char *config_path) { if (set_size(remove_symlinks_to) <= 0) return 0; - if ((fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW)) < 0) + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) return -errno; do { - int q; + int q, cfd; deleted = false; - if ((q = remove_marked_symlinks_fd(fd, config_path, config_path, &deleted)) < 0) { - if (r == 0) - r = q; + 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); + if (r == 0) + r = q; } while (deleted); close_nointr_nofail(fd); @@ -732,434 +356,1598 @@ static int remove_marked_symlinks(const char *config_path) { return r; } -static int create_symlink(const char *old_path, const char *new_path) { - int r; +static int find_symlinks_fd( + const char *name, + int fd, + const char *path, + const char *config_path, + bool *same_name_link) { - assert(old_path); - assert(new_path); + int r = 0; + DIR *d; + struct dirent buffer, *de; - if (arg_action == ACTION_ENABLE) { - char *dest; + assert(name); + assert(fd >= 0); + assert(path); + assert(config_path); + assert(same_name_link); - mkdir_parents(new_path, 0755); + d = fdopendir(fd); + if (!d) { + close_nointr_nofail(fd); + return -errno; + } - if (symlink(old_path, new_path) >= 0) - return 0; + for (;;) { + int k; - if (errno != EEXIST) { - log_error("Cannot link %s to %s: %m", old_path, new_path); - return -errno; + k = readdir_r(d, &buffer, &de); + if (k != 0) { + r = -errno; + break; } - if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) { + 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 (errno == EINVAL) { - log_error("Cannot link %s to %s, file exists already and is not a symlink.", old_path, new_path); - return -EEXIST; + if (q > 0) { + r = 1; + break; } - log_error("readlink() failed: %s", strerror(-r)); + 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(file_name_from_path(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) + 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 *path; + 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); + free(path); + + if (r < 0) + return r; + else if (r > 0) { + *state = UNIT_FILE_ENABLED_RUNTIME; return r; } + } - if (streq(dest, old_path)) { - free(dest); - 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; + } + + /* 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, *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 *path; + + if (!unit_name_is_valid_no_type(*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"); + + free(path); + continue; + } + + if (errno == EEXIST) { + + if (null_or_empty_path(path) > 0) { + free(path); + 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"); + + free(path); + continue; + } + } + + if (r == 0) + r = -EEXIST; + } else { + if (r == 0) + r = -errno; + } + + 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; + + 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; } - if (!arg_force) { - log_error("Cannot link %s to %s, symlink exists already and points to %s.", old_path, new_path, dest); + 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); + 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 = 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; + } + + 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); - return -EEXIST; + + 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); + 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 (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", "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); +} + +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; + } + + 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); + + 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(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_no_type(*s, true)) { + r = -EINVAL; + continue; } - free(dest); - unlink(new_path); + 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; - if (arg_verbose) - log_info("ln -s '%s' '%s'", old_path, new_path); + return r; + } else if (r >= 0) + r += q; - if (symlink(old_path, new_path) >= 0) - return 0; + q = install_info_apply(i, paths, config_path, force, changes, n_changes); + if (r >= 0 && q < 0) + r = q; + } - log_error("Cannot link %s to %s: %m", old_path, new_path); - return -errno; + return r; +} - } else if (arg_action == ACTION_DISABLE) { - char *dest; +static int install_context_mark_for_removal( + InstallContext *c, + LookupPaths *paths, + Set **remove_symlinks_to, + const char *config_path, + const char *root_dir) { - if ((r = mark_symlink_for_removal(old_path)) < 0) - return r; + InstallInfo *i; + int r = 0, q; - if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) { - if (errno == ENOENT) - return 0; + assert(c); + assert(paths); + assert(config_path); - if (errno == EINVAL) { - log_warning("File %s not a symlink, ignoring.", old_path); - return 0; - } + /* Marks all items for removal */ - log_error("readlink() failed: %s", strerror(-r)); - return r; - } + while ((i = hashmap_first(c->will_install))) { - 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; - } + 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); - free(dest); + q = unit_file_search(c, i, paths, root_dir, false); + if (q < 0) { + if (r >= 0) + r = q; - if ((r = mark_symlink_for_removal(new_path)) < 0) return r; + } else if (r >= 0) + r += q; - if (arg_verbose) - log_info("rm '%s'", new_path); + q = mark_symlink_for_removal(remove_symlinks_to, i->name); + if (r >= 0 && q < 0) + r = q; + } - if (unlink(new_path) >= 0) - return 0; + return r; +} - log_error("Cannot unlink %s: %m", new_path); - return -errno; +int unit_file_enable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { - } else if (arg_action == ACTION_TEST || arg_action == ACTION_REALIZE) { - char *dest; + LookupPaths paths; + InstallContext c; + char **i, *config_path = NULL; + int r; - if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) { + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); - if (errno == ENOENT || errno == EINVAL) - return 0; + zero(paths); + zero(c); - log_error("readlink() failed: %s", strerror(-r)); - return r; - } + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; - if (streq(dest, old_path)) { - free(dest); - return 1; - } + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + goto finish; - return 0; + STRV_FOREACH(i, files) { + r = install_info_add_auto(&c, *i); + if (r < 0) + goto finish; } - assert_not_reached("Unknown action."); + /* 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; } -static int install_info_symlink_alias(InstallInfo *i, const char *config_path) { - char **s; - char *alias_path = NULL; - int r; +int unit_file_disable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + UnitFileChange **changes, + unsigned *n_changes) { - assert(i); + LookupPaths paths; + InstallContext c; + char **i, *config_path = NULL; + Set *remove_symlinks_to = NULL; + int r, q; - STRV_FOREACH(s, i->aliases) { + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); - if (!unit_name_valid(*s)) { - log_error("Invalid name %s.", *s); - r = -EINVAL; - goto finish; - } + zero(paths); + zero(c); - free(alias_path); - if (!(alias_path = path_make_absolute(*s, config_path))) { - log_error("Out of memory"); - r = -ENOMEM; - goto finish; - } + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; - if ((r = create_symlink(i->path, alias_path)) != 0) - goto finish; + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + goto finish; - if (arg_action == ACTION_DISABLE) - rmdir_parents(alias_path, config_path); + STRV_FOREACH(i, files) { + r = install_info_add_auto(&c, *i); + if (r < 0) + goto finish; } - r = 0; + 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: - free(alias_path); + install_context_done(&c); + lookup_paths_free(&paths); + set_free_free(remove_symlinks_to); + free(config_path); return r; } -static int install_info_symlink_wants(InstallInfo *i, const char *config_path) { - char **s; - char *alias_path = NULL; - int r; +int unit_file_reenable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { - assert(i); + LookupPaths paths; + InstallContext c; + char **i, *config_path = NULL; + Set *remove_symlinks_to = NULL; + int r, q; - STRV_FOREACH(s, i->wanted_by) { - if (!unit_name_valid(*s)) { - log_error("Invalid name %s.", *s); - r = -EINVAL; - goto finish; - } + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); - free(alias_path); - alias_path = NULL; + zero(paths); + zero(c); - if (asprintf(&alias_path, "%s/%s.wants/%s", config_path, *s, i->name) < 0) { - log_error("Out of memory"); - r = -ENOMEM; - goto finish; - } + 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; - if ((r = create_symlink(i->path, alias_path)) != 0) + STRV_FOREACH(i, files) { + r = mark_symlink_for_removal(&remove_symlinks_to, *i); + if (r < 0) goto finish; - if (arg_action == ACTION_DISABLE) - rmdir_parents(alias_path, config_path); + r = install_info_add_auto(&c, *i); + if (r < 0) + goto finish; } - r = 0; + 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: - free(alias_path); + lookup_paths_free(&paths); + install_context_done(&c); + set_free_free(remove_symlinks_to); + free(config_path); return r; } -static int install_info_apply(LookupPaths *paths, InstallInfo *i, const char *config_path) { +UnitFileState unit_file_get_state( + UnitFileScope scope, + const char *root_dir, + const char *name) { - 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; + UnitFileState state = _UNIT_FILE_STATE_INVALID; + char **i, *path = NULL; + int r; - { NULL, NULL, NULL, NULL } - }; + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); - char **p; - char *filename = NULL; - FILE *f = NULL; - int r; + zero(paths); - assert(paths); - assert(i); + if (root_dir && scope != UNIT_FILE_SYSTEM) + return -EINVAL; - STRV_FOREACH(p, paths->unit_path) { - int fd; + if (!unit_name_is_valid_no_type(name, true)) + return -EINVAL; - if (!(filename = path_make_absolute(i->name, *p))) { - log_error("Out of memory"); - return -ENOMEM; + 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; } - /* Ensure that we don't follow symlinks */ - if ((fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOCTTY)) >= 0) - if ((f = fdopen(fd, "re"))) - break; + if (lstat(path, &st) < 0) { + if (errno == ENOENT) + continue; - if (errno == ELOOP) { - log_error("Refusing to operate on symlinks, please pass unit names or absolute paths to unit files."); - free(filename); - return -errno; + r = -errno; + goto finish; } - if (errno != ENOENT) { - log_error("Failed to open %s: %m", filename); - free(filename); - return -errno; + if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) { + r = -ENOENT; + goto finish; } - free(filename); - filename = NULL; - } + 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; + } - if (!f) { - log_error("Couldn't find %s.", i->name); - return -ENOENT; + 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; + } } - i->path = filename; +finish: + lookup_paths_free(&paths); + free(path); - if ((r = config_parse(filename, f, NULL, items, true, i)) < 0) { - fclose(f); - return r; - } + return r < 0 ? r : state; +} - fclose(f); +int unit_file_query_preset(UnitFileScope scope, const char *name) { + char **files, **i; + int r; - if ((r = install_info_symlink_alias(i, config_path)) != 0) + 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; - if ((r = install_info_symlink_wants(i, config_path)) != 0) - return r; + STRV_FOREACH(i, files) { + FILE *f; - if ((r = mark_symlink_for_removal(filename)) < 0) - return r; + f = fopen(*i, "re"); + if (!f) { + if (errno == ENOENT) + continue; - if ((r = remove_marked_symlinks(config_path)) < 0) - return r; + r = -errno; + goto finish; + } - return 0; -} + for (;;) { + char line[LINE_MAX], *l; -static char *get_config_path(void) { + if (!fgets(line, sizeof(line), f)) + break; - switch (arg_where) { + l = strstrip(line); + if (!*l) + continue; - case WHERE_SYSTEM: - return strdup(SYSTEM_CONFIG_UNIT_PATH); + if (strchr(COMMENTS, *l)) + continue; - case WHERE_GLOBAL: - return strdup(SESSION_CONFIG_UNIT_PATH); + if (first_word(l, "enable")) { + l += 6; + l += strspn(l, WHITESPACE); - case WHERE_SESSION: { - char *p; + 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 (session_config_home(&p) < 0) - return NULL; + if (fnmatch(l, name, FNM_NOESCAPE) == 0) { + r = 0; + fclose(f); + goto finish; + } + } else + log_debug("Couldn't parse line '%s'", l); + } - return p; + fclose(f); } - default: - assert_not_reached("Unknown config path."); - } -} + /* Default is "enable" */ + r = 1; -static int do_realize(bool enabled) { - DBusConnection *bus = NULL; - DBusError error; - int r, q; - Iterator i; - InstallInfo *j; +finish: + strv_free(files); - dbus_error_init(&error); + return r; +} - if (arg_realize == REALIZE_NO) - return 0; +int unit_file_preset( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { - if (arg_where == WHERE_GLOBAL) { - log_warning("Warning: --realize has no effect with --global."); - return 0; - } + LookupPaths paths; + InstallContext plus, minus; + char **i, *config_path = NULL; + Set *remove_symlinks_to = NULL; + int r, q; - if (arg_action == ACTION_TEST) { - log_warning("Warning: --realize has no effect with test."); - return 0; - } + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); - if (arg_where == WHERE_SYSTEM && sd_booted() <= 0) { - log_info("systemd is not running, --realize has no effect."); - return 0; - } + zero(paths); + zero(plus); + zero(minus); - if (arg_where == WHERE_SYSTEM && running_in_chroot() > 0) { - log_info("Running in a chroot() environment, --realize has no effect."); - return 0; - } + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; - 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); + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) goto finish; - } - r = 0; + STRV_FOREACH(i, files) { + + if (!unit_name_is_valid_no_type(*i, true)) { + r = -EINVAL; + goto finish; + } - if (arg_action == ACTION_ENABLE || arg_action == ACTION_REALIZE) - if ((r = daemon_reload(bus)) < 0) + r = unit_file_query_preset(scope, *i); + if (r < 0) goto finish; - if (arg_realize != REALIZE_RELOAD) { - HASHMAP_FOREACH(j, have_installed, i) - if ((q = install_info_run(bus, j, enabled)) < 0) - r = q; + if (r) + r = install_info_add_auto(&plus, *i); + else + r = install_info_add_auto(&minus, *i); + + if (r < 0) + goto finish; } - if (arg_action == ACTION_DISABLE) - if ((q = daemon_reload(bus)) < 0) - r = q; + r = install_context_mark_for_removal(&minus, &paths, &remove_symlinks_to, config_path, root_dir); -finish: - if (bus) { - dbus_connection_close(bus); - dbus_connection_unref(bus); - } + q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); + if (r == 0) + r = q; - dbus_error_free(&error); + /* 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); - dbus_shutdown(); return r; } -int main(int argc, char *argv[]) { - int r, retval = 1, j; +int unit_file_get_list( + UnitFileScope scope, + const char *root_dir, + Hashmap *h) { + LookupPaths paths; - InstallInfo *i; - char *config_path = NULL; + char **i, *buf = NULL; + DIR *d = NULL; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(h); zero(paths); - log_parse_environment(); + if (root_dir && scope != UNIT_FILE_SYSTEM) + return -EINVAL; - if ((r = parse_argv(argc, argv)) < 0) - goto finish; - else if (r == 0) { - retval = 0; - goto finish; - } + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; - 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; - } + STRV_FOREACH(i, paths.unit_path) { + struct dirent buffer, *de; + const char *units_dir; - if (!(config_path = get_config_path())) { - log_error("Failed to determine config path"); - goto finish; - } + 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; - will_install = hashmap_new(string_hash_func, string_compare_func); - have_installed = hashmap_new(string_hash_func, string_compare_func); + if (d) + closedir(d); - if (!will_install || !have_installed) { - log_error("Failed to allocate unit sets."); - goto finish; - } + d = opendir(units_dir); + if (!d) { + if (errno == ENOENT) + continue; - if (arg_all) - if (!(remove_symlinks_to = set_new(string_hash_func, string_compare_func))) { - log_error("Failed to allocate symlink sets."); + r = -errno; goto finish; } - for (j = optind; j < argc; j++) - if ((r = install_info_add(argv[j])) < 0) - 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; - while ((i = hashmap_first(will_install))) { - assert_se(hashmap_move_one(have_installed, will_install, i->name) == 0); + goto finish; + } - if ((r = install_info_apply(&paths, i, config_path)) != 0) { + if (de->d_type != DT_LNK && de->d_type != DT_REG) + continue; - if (r < 0) + f = new0(UnitFileList, 1); + if (!f) { + r = -ENOMEM; goto finish; + } - /* In test mode and found something */ - retval = 0; - break; - } - } + f->path = path_make_absolute(de->d_name, units_dir); + if (!f->path) { + free(f); + r = -ENOMEM; + goto finish; + } - if (do_realize(!retval) < 0) - 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; + } -finish: - install_info_hashmap_free(will_install); - install_info_hashmap_free(have_installed); + 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; - set_free_free(remove_symlinks_to); + 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); - free(config_path); + if (d) + closedir(d); - return retval; + 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);