+ 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);
+
+ 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;
+ }
+
+ 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;