+
+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) {
+ r = -errno;
+ if (errno == ENOENT)
+ continue;
+
+ goto finish;
+ }
+
+ if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
+ r = -ENOENT;
+ goto finish;
+ }
+
+ r = null_or_empty_path(path);
+ if (r < 0 && r != -ENOENT)
+ goto finish;
+ else if (r > 0) {
+ state = path_startswith(*i, "/run") ?
+ UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
+ r = 0;
+ goto finish;
+ }
+
+ r = find_symlinks_in_scope(scope, root_dir, name, &state);
+ if (r < 0) {
+ goto finish;
+ } else if (r > 0) {
+ r = 0;
+ goto finish;
+ }
+
+ r = unit_file_can_install(&paths, root_dir, path, true);
+ if (r < 0 && errno != -ENOENT)
+ goto finish;
+ else if (r > 0) {
+ state = UNIT_FILE_DISABLED;
+ r = 0;
+ goto finish;
+ } else if (r == 0) {
+ state = UNIT_FILE_STATIC;
+ r = 0;
+ goto finish;
+ }
+ }
+
+finish:
+ lookup_paths_free(&paths);
+ free(path);
+
+ return r < 0 ? r : state;
+}
+
+int unit_file_query_preset(UnitFileScope scope, const char *name) {
+ char **files, **i;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(name);
+
+ if (scope == UNIT_FILE_SYSTEM)
+ r = conf_files_list(&files, ".preset",
+ "/etc/systemd/system.preset",
+ "/usr/local/lib/systemd/system.preset",
+ "/usr/lib/systemd/system.preset",
+ "/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);