/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ /*** This file is part of systemd. Copyright 2011 Lennart Poettering systemd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty . ***/ #include #include #include #include #include #include "util.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" typedef struct { char *name; char *path; char **aliases; char **wanted_by; } InstallInfo; typedef struct { Hashmap *will_install; Hashmap *have_installed; } InstallContext; static int lookup_paths_init_from_scope(LookupPaths *paths, UnitFileScope scope) { assert(paths); assert(scope >= 0); assert(scope < _UNIT_FILE_SCOPE_MAX); zero(*paths); return lookup_paths_init(paths, scope == UNIT_FILE_SYSTEM ? MANAGER_SYSTEM : MANAGER_USER, scope == UNIT_FILE_USER); } static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) { char *p = NULL; int r; assert(scope >= 0); assert(scope < _UNIT_FILE_SCOPE_MAX); assert(ret); switch (scope) { case UNIT_FILE_SYSTEM: if (root_dir && runtime) return -EINVAL; if (runtime) p = strdup("/run/systemd/system"); else if (root_dir) asprintf(&p, "%s/%s", root_dir, SYSTEM_CONFIG_UNIT_PATH); else p = strdup(SYSTEM_CONFIG_UNIT_PATH); break; case UNIT_FILE_GLOBAL: if (root_dir) return -EINVAL; if (runtime) p = strdup("/run/systemd/user"); else p = strdup(USER_CONFIG_UNIT_PATH); break; case UNIT_FILE_USER: if (root_dir || runtime) return -EINVAL; r = user_config_home(&p); if (r <= 0) return r < 0 ? r : -ENOENT; break; default: assert_not_reached("Bad scope"); } if (!p) return -ENOMEM; *ret = p; return 0; } static int add_file_change( UnitFileChange **changes, unsigned *n_changes, UnitFileChangeType type, const char *path, const char *source) { UnitFileChange *c; unsigned i; assert(type >= 0); assert(type < _UNIT_FILE_CHANGE_TYPE_MAX); assert(path); assert(!changes == !n_changes); if (!changes) return 0; c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange)); if (!c) return -ENOMEM; *changes = c; i = *n_changes; c[i].type = type; c[i].path = strdup(path); if (!c[i].path) return -ENOMEM; if (source) { c[i].source = strdup(source); if (!c[i].source) { free(c[i].path); return -ENOMEM; } } else c[i].source = NULL; *n_changes = i+1; return 0; } static int mark_symlink_for_removal( Set **remove_symlinks_to, const char *p) { char *n; int r; assert(p); r = set_ensure_allocated(remove_symlinks_to, string_hash_func, string_compare_func); if (r < 0) return r; n = strdup(p); if (!n) return -ENOMEM; path_kill_slashes(n); r = set_put(*remove_symlinks_to, n); if (r < 0) { free(n); return r == -EEXIST ? 0 : r; } return 0; } static int remove_marked_symlinks_fd( Set *remove_symlinks_to, int fd, const char *path, const char *config_path, bool *deleted, UnitFileChange **changes, unsigned *n_changes) { 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; } rewinddir(d); for (;;) { int k; k = readdir_r(d, &buffer, &de); if (k != 0) { r = -errno; break; } if (!de) break; if (ignore_file(de->d_name)) continue; dirent_ensure_type(d, de); if (de->d_type == DT_DIR) { int nfd, q; char *p; nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); if (nfd < 0) { if (errno == ENOENT) continue; if (r == 0) r = -errno; continue; } p = path_make_absolute(de->d_name, path); if (!p) { close_nointr_nofail(nfd); r = -ENOMEM; break; } /* This will close nfd, regardless whether it succeeds or not */ q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, deleted, changes, n_changes); free(p); if (r == 0) r = q; } else if (de->d_type == DT_LNK) { char *p, *dest; int q; bool found; p = path_make_absolute(de->d_name, path); if (!p) { r = -ENOMEM; break; } q = readlink_and_canonicalize(p, &dest); if (q < 0) { free(p); if (q == -ENOENT) continue; if (r == 0) r = q; continue; } found = set_get(remove_symlinks_to, dest) || set_get(remove_symlinks_to, file_name_from_path(dest)); if (found) { if (unlink(p) < 0 && errno != ENOENT) { if (r == 0) r = -errno; } else { rmdir_parents(p, config_path); path_kill_slashes(p); add_file_change(changes, n_changes, UNIT_FILE_UNLINK, p, NULL); if (!set_get(remove_symlinks_to, p)) { q = mark_symlink_for_removal(&remove_symlinks_to, p); if (q < 0) { if (r == 0) r = q; } else *deleted = true; } } } free(p); free(dest); } } closedir(d); return r; } static int remove_marked_symlinks( Set *remove_symlinks_to, const char *config_path, UnitFileChange **changes, unsigned *n_changes) { int fd, r = 0; bool deleted; assert(config_path); if (set_size(remove_symlinks_to) <= 0) return 0; fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); if (fd < 0) return -errno; do { int q, cfd; deleted = false; cfd = dup(fd); if (cfd < 0) { r = -errno; break; } /* This takes possession of cfd and closes it */ q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &deleted, changes, n_changes); if (r == 0) r = q; } while (deleted); close_nointr_nofail(fd); return r; } static int find_symlinks_fd( const char *name, int fd, const char *path, const char *config_path, bool *same_name_link) { int r = 0; DIR *d; 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; } for (;;) { int k; k = readdir_r(d, &buffer, &de); if (k != 0) { r = -errno; break; } if (!de) break; if (ignore_file(de->d_name)) continue; dirent_ensure_type(d, de); if (de->d_type == DT_DIR) { int nfd, q; char *p; nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); if (nfd < 0) { if (errno == ENOENT) continue; if (r == 0) r = -errno; continue; } p = path_make_absolute(de->d_name, path); if (!p) { close_nointr_nofail(nfd); r = -ENOMEM; break; } /* This will close nfd, regardless whether it succeeds or not */ q = find_symlinks_fd(name, nfd, p, config_path, same_name_link); free(p); if (q > 0) { r = 1; break; } if (r == 0) r = q; } else if (de->d_type == DT_LNK) { char *p, *dest; bool found_path, found_dest, b = false; int q; /* Acquire symlink name */ p = path_make_absolute(de->d_name, path); if (!p) { r = -ENOMEM; break; } /* Acquire symlink destination */ q = readlink_and_canonicalize(p, &dest); if (q < 0) { free(p); if (q == -ENOENT) continue; if (r == 0) r = q; continue; } /* Check if the symlink itself matches what we * are looking for */ if (path_is_absolute(name)) found_path = path_equal(p, name); else found_path = streq(de->d_name, name); /* Check if what the symlink points to * matches what we are looking for */ if (path_is_absolute(name)) found_dest = path_equal(dest, name); else found_dest = streq(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); free(dest); 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; } } /* 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; } 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); 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 ConfigItem items[] = { { "Alias", config_parse_strv, 0, &info->aliases, "Install" }, { "WantedBy", config_parse_strv, 0, &info->wanted_by, "Install" }, { "Also", config_parse_also, 0, c, "Install" }, { NULL, NULL, 0, NULL, 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, 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; 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; } 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); 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; 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 if (r == 0) { 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);