/*-*- 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 Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 "mkdir.h"
#include "hashmap.h"
#include "set.h"
#include "path-util.h"
#include "path-lookup.h"
#include "strv.h"
#include "unit-name.h"
#include "install.h"
#include "conf-parser.h"
#include "conf-files.h"
#include "specifier.h"
#include "install-printf.h"
#include "special.h"
typedef struct {
Hashmap *will_install;
Hashmap *have_installed;
} InstallContext;
#define _cleanup_install_context_done_ _cleanup_(install_context_done)
static int in_search_path(const char *path, char **search) {
_cleanup_free_ char *parent = NULL;
int r;
assert(path);
r = path_get_parent(path, &parent);
if (r < 0)
return r;
return strv_contains(search, parent);
}
static int lookup_paths_init_from_scope(LookupPaths *paths,
UnitFileScope scope,
const char *root_dir) {
assert(paths);
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
zero(*paths);
return lookup_paths_init(paths,
scope == UNIT_FILE_SYSTEM ? SYSTEMD_SYSTEM : SYSTEMD_USER,
scope == UNIT_FILE_USER,
root_dir,
NULL, NULL, NULL);
}
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 (runtime)
p = path_join(root_dir, "/run/systemd/system", NULL);
else
p = path_join(root_dir, SYSTEM_CONFIG_UNIT_PATH, NULL);
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(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;
path_kill_slashes(c[i].path);
if (source) {
c[i].source = strdup(source);
if (!c[i].source) {
free(c[i].path);
return -ENOMEM;
}
path_kill_slashes(c[i].path);
} 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_consume(*remove_symlinks_to, n);
if (r < 0)
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,
char** instance_whitelist) {
_cleanup_closedir_ DIR *d = NULL;
int r = 0;
assert(remove_symlinks_to);
assert(fd >= 0);
assert(path);
assert(config_path);
assert(deleted);
d = fdopendir(fd);
if (!d) {
safe_close(fd);
return -errno;
}
rewinddir(d);
for (;;) {
struct dirent *de;
errno = 0;
de = readdir(d);
if (!de && errno != 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;
_cleanup_free_ char *p = NULL;
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) {
safe_close(nfd);
return -ENOMEM;
}
/* 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, instance_whitelist);
if (q < 0 && r == 0)
r = q;
} else if (de->d_type == DT_LNK) {
_cleanup_free_ char *p = NULL, *dest = NULL;
int q;
bool found;
if (!unit_name_is_valid(de->d_name, TEMPLATE_VALID))
continue;
if (unit_name_is_instance(de->d_name) &&
instance_whitelist &&
!strv_contains(instance_whitelist, de->d_name)) {
_cleanup_free_ char *w;
/* OK, the file is not listed directly
* in the whitelist, so let's check if
* the template of it might be
* listed. */
w = unit_name_template(de->d_name);
if (!w)
return -ENOMEM;
if (!strv_contains(instance_whitelist, w))
continue;
}
p = path_make_absolute(de->d_name, path);
if (!p)
return -ENOMEM;
q = readlink_and_canonicalize(p, &dest);
if (q < 0) {
if (q == -ENOENT)
continue;
if (r == 0)
r = q;
continue;
}
found =
set_get(remove_symlinks_to, dest) ||
set_get(remove_symlinks_to, basename(dest));
if (!found)
continue;
if (unlink(p) < 0 && errno != ENOENT) {
if (r == 0)
r = -errno;
continue;
}
path_kill_slashes(p);
rmdir_parents(p, config_path);
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;
}
}
}
return r;
}
static int remove_marked_symlinks(
Set *remove_symlinks_to,
const char *config_path,
UnitFileChange **changes,
unsigned *n_changes,
char** instance_whitelist) {
_cleanup_close_ int fd = -1;
int 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 = fcntl(fd, F_DUPFD_CLOEXEC, 3);
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, instance_whitelist);
if (r == 0)
r = q;
} while (deleted);
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;
_cleanup_closedir_ DIR *d = NULL;
assert(name);
assert(fd >= 0);
assert(path);
assert(config_path);
assert(same_name_link);
d = fdopendir(fd);
if (!d) {
safe_close(fd);
return -errno;
}
for (;;) {
struct dirent *de;
errno = 0;
de = readdir(d);
if (!de && errno != 0)
return -errno;
if (!de)
return r;
if (ignore_file(de->d_name))
continue;
dirent_ensure_type(d, de);
if (de->d_type == DT_DIR) {
int nfd, q;
_cleanup_free_ char *p = NULL;
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) {
safe_close(nfd);
return -ENOMEM;
}
/* This will close nfd, regardless whether it succeeds or not */
q = find_symlinks_fd(name, nfd, p, config_path, same_name_link);
if (q > 0)
return 1;
if (r == 0)
r = q;
} else if (de->d_type == DT_LNK) {
_cleanup_free_ char *p = NULL, *dest = NULL;
bool found_path, found_dest, b = false;
int q;
/* Acquire symlink name */
p = path_make_absolute(de->d_name, path);
if (!p)
return -ENOMEM;
/* Acquire symlink destination */
q = readlink_and_canonicalize(p, &dest);
if (q < 0) {
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(basename(dest), name);
if (found_path && found_dest) {
_cleanup_free_ char *t = NULL;
/* Filter out same name links in the main
* config path */
t = path_make_absolute(name, config_path);
if (!t)
return -ENOMEM;
b = path_equal(t, p);
}
if (b)
*same_name_link = true;
else if (found_path || found_dest)
return 1;
}
}
}
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) {
if (errno == ENOENT)
return 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;
_cleanup_free_ char *path2 = NULL;
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) {
_cleanup_free_ char *path = NULL;
/* 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);
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, &path2);
if (r < 0)
return r;
r = find_symlinks(name, path2, &same_name_link);
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;
_cleanup_free_ char *prefix = NULL;
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) {
_cleanup_free_ char *path = NULL;
if (!unit_name_is_valid(*i, TEMPLATE_VALID)) {
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");
continue;
}
if (errno == EEXIST) {
if (null_or_empty_path(path) > 0)
continue;
if (force) {
if (symlink_atomic("/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");
continue;
}
}
if (r == 0)
r = -EEXIST;
} else {
if (r == 0)
r = -errno;
}
}
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(*i, TEMPLATE_VALID)) {
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, files);
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) {
_cleanup_lookup_paths_free_ LookupPaths paths = {};
char **i;
_cleanup_free_ char *config_path = NULL;
int r, q;
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
r = lookup_paths_init_from_scope(&paths, scope, root_dir);
if (r < 0)
return r;
r = get_config_path(scope, runtime, root_dir, &config_path);
if (r < 0)
return r;
STRV_FOREACH(i, files) {
_cleanup_free_ char *path = NULL;
char *fn;
struct stat st;
fn = basename(*i);
if (!path_is_absolute(*i) ||
!unit_name_is_valid(fn, TEMPLATE_VALID)) {
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)
return q;
if (q > 0)
continue;
path = path_make_absolute(fn, config_path);
if (!path)
return -ENOMEM;
if (symlink(*i, path) >= 0) {
add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);
continue;
}
if (errno == EEXIST) {
_cleanup_free_ char *dest = NULL;
q = readlink_and_make_absolute(path, &dest);
if (q < 0 && errno != ENOENT) {
if (r == 0)
r = q;
continue;
}
if (q >= 0 && path_equal(dest, *i))
continue;
if (force) {
if (symlink_atomic(*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);
continue;
}
}
if (r == 0)
r = -EEXIST;
} else {
if (r == 0)
r = -errno;
}
}
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);
strv_free(i->required_by);
free(i->default_instance);
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 = basename(path);
if (!unit_name_is_valid(name, TEMPLATE_VALID))
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 *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
size_t l;
const char *word, *state;
InstallContext *c = data;
assert(filename);
assert(lvalue);
assert(rvalue);
FOREACH_WORD_QUOTED(word, l, rvalue, state) {
_cleanup_free_ char *n;
int r;
n = strndup(word, l);
if (!n)
return -ENOMEM;
r = install_info_add(c, n, NULL);
if (r < 0)
return r;
}
if (!isempty(state))
log_syntax(unit, LOG_ERR, filename, line, EINVAL,
"Trailing garbage, ignoring.");
return 0;
}
static int config_parse_user(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
InstallInfo *i = data;
char *printed;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
r = install_full_printf(i, rvalue, &printed);
if (r < 0)
return r;
free(i->user);
i->user = printed;
return 0;
}
static int config_parse_default_instance(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
InstallInfo *i = data;
char *printed;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
r = install_full_printf(i, rvalue, &printed);
if (r < 0)
return r;
if (!unit_instance_is_valid(printed))
return -EINVAL;
free(i->default_instance);
i->default_instance = printed;
return 0;
}
static int unit_file_load(
InstallContext *c,
InstallInfo *info,
const char *path,
const char *root_dir,
bool allow_symlink) {
const ConfigTableItem items[] = {
{ "Install", "Alias", config_parse_strv, 0, &info->aliases },
{ "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by },
{ "Install", "RequiredBy", config_parse_strv, 0, &info->required_by },
{ "Install", "DefaultInstance", config_parse_default_instance, 0, info },
{ "Install", "Also", config_parse_also, 0, c },
{ "Exec", "User", config_parse_user, 0, info },
{}
};
_cleanup_fclose_ FILE *f = NULL;
int fd, r;
assert(c);
assert(info);
assert(path);
if (!isempty(root_dir))
path = strappenda(root_dir, "/", 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) {
safe_close(fd);
return -ENOMEM;
}
r = config_parse(NULL, path, f,
NULL,
config_item_table_lookup, items,
true, true, false, info);
if (r < 0)
return r;
return
(int) strv_length(info->aliases) +
(int) strv_length(info->wanted_by) +
(int) strv_length(info->required_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, root_dir, allow_symlink);
assert(info->name);
STRV_FOREACH(p, paths->unit_path) {
_cleanup_free_ char *path = NULL;
path = strjoin(*p, "/", info->name, NULL);
if (!path)
return -ENOMEM;
r = unit_file_load(c, info, path, root_dir, allow_symlink);
if (r >= 0) {
info->path = path;
path = NULL;
return r;
}
if (r != -ENOENT && r != -ELOOP)
return r;
}
if (unit_name_is_instance(info->name)) {
/* Unit file doesn't exist, however instance
* enablement was requested. We will check if it is
* possible to load template unit file. */
_cleanup_free_ char *template = NULL;
template = unit_name_template(info->name);
if (!template)
return -ENOMEM;
STRV_FOREACH(p, paths->unit_path) {
_cleanup_free_ char *path = NULL;
path = strjoin(*p, "/", template, NULL);
if (!path)
return -ENOMEM;
r = unit_file_load(c, info, path, root_dir, allow_symlink);
if (r >= 0) {
info->path = path;
path = NULL;
return r;
}
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) {
_cleanup_install_context_done_ InstallContext c = {};
InstallInfo *i;
int r;
assert(paths);
assert(name);
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 =
(int) strv_length(i->aliases) +
(int) strv_length(i->wanted_by) +
(int) strv_length(i->required_by);
return r;
}
static int create_symlink(
const char *old_path,
const char *new_path,
bool force,
UnitFileChange **changes,
unsigned *n_changes) {
_cleanup_free_ char *dest = NULL;
int r;
assert(old_path);
assert(new_path);
mkdir_parents_label(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))
return 0;
if (!force)
return -EEXIST;
r = symlink_atomic(old_path, new_path);
if (r < 0)
return r;
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;
}
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) {
_cleanup_free_ char *alias_path = NULL, *dst = NULL;
q = install_full_printf(i, *s, &dst);
if (q < 0)
return q;
alias_path = path_make_absolute(dst, config_path);
if (!alias_path)
return -ENOMEM;
q = create_symlink(i->path, alias_path, force, changes, n_changes);
if (r == 0)
r = q;
}
return r;
}
static int install_info_symlink_wants(
InstallInfo *i,
const char *config_path,
char **list,
const char *suffix,
bool force,
UnitFileChange **changes,
unsigned *n_changes) {
_cleanup_free_ char *buf = NULL;
const char *n;
char **s;
int r = 0, q;
assert(i);
assert(config_path);
if (unit_name_is_template(i->name)) {
/* Don't install any symlink if there's no default
* instance configured */
if (!i->default_instance)
return 0;
buf = unit_name_replace_instance(i->name, i->default_instance);
if (!buf)
return -ENOMEM;
n = buf;
} else
n = i->name;
STRV_FOREACH(s, list) {
_cleanup_free_ char *path = NULL, *dst = NULL;
q = install_full_printf(i, *s, &dst);
if (q < 0)
return q;
if (!unit_name_is_valid(dst, TEMPLATE_VALID)) {
r = -EINVAL;
continue;
}
path = strjoin(config_path, "/", dst, suffix, n, NULL);
if (!path)
return -ENOMEM;
q = create_symlink(i->path, path, force, changes, n_changes);
if (r == 0)
r = q;
}
return r;
}
static int install_info_symlink_link(
InstallInfo *i,
LookupPaths *paths,
const char *config_path,
const char *root_dir,
bool force,
UnitFileChange **changes,
unsigned *n_changes) {
_cleanup_free_ char *path = NULL;
int r;
assert(i);
assert(paths);
assert(config_path);
assert(i->path);
r = in_search_path(i->path, paths->unit_path);
if (r != 0)
return r;
path = strjoin(config_path, "/", i->name, NULL);
if (!path)
return -ENOMEM;
return create_symlink(i->path, path, force, changes, n_changes);
}
static int install_info_apply(
InstallInfo *i,
LookupPaths *paths,
const char *config_path,
const char *root_dir,
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, i->wanted_by, ".wants/", force, changes, n_changes);
if (r == 0)
r = q;
q = install_info_symlink_wants(i, config_path, i->required_by, ".requires/", force, changes, n_changes);
if (r == 0)
r = q;
q = install_info_symlink_link(i, paths, config_path, root_dir, 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, root_dir, 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 == -ENOENT) {
/* do nothing */
} else if (q < 0) {
if (r >= 0)
r = q;
return r;
} else if (r >= 0)
r += q;
if (unit_name_is_instance(i->name)) {
char *unit_file;
if (i->path) {
unit_file = basename(i->path);
if (unit_name_is_instance(unit_file))
/* unit file named as instance exists, thus all symlinks
* pointing to it will be removed */
q = mark_symlink_for_removal(remove_symlinks_to, i->name);
else
/* does not exist, thus we will mark for removal symlinks
* to template unit file */
q = mark_symlink_for_removal(remove_symlinks_to, unit_file);
} else {
/* If i->path is not set, it means that we didn't actually find
* the unit file. But we can still remove symlinks to the
* nonexistent template. */
unit_file = unit_name_template(i->name);
if (!unit_file)
return log_oom();
q = mark_symlink_for_removal(remove_symlinks_to, unit_file);
free(unit_file);
}
} else
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) {
_cleanup_lookup_paths_free_ LookupPaths paths = {};
_cleanup_install_context_done_ InstallContext c = {};
char **i;
_cleanup_free_ char *config_path = NULL;
int r;
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
r = lookup_paths_init_from_scope(&paths, scope, root_dir);
if (r < 0)
return r;
r = get_config_path(scope, runtime, root_dir, &config_path);
if (r < 0)
return r;
STRV_FOREACH(i, files) {
r = install_info_add_auto(&c, *i);
if (r < 0)
return r;
}
/* 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 had any
installation data at all. */
return install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes);
}
int unit_file_disable(
UnitFileScope scope,
bool runtime,
const char *root_dir,
char **files,
UnitFileChange **changes,
unsigned *n_changes) {
_cleanup_lookup_paths_free_ LookupPaths paths = {};
_cleanup_install_context_done_ InstallContext c = {};
char **i;
_cleanup_free_ char *config_path = NULL;
_cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
int r, q;
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
r = lookup_paths_init_from_scope(&paths, scope, root_dir);
if (r < 0)
return r;
r = get_config_path(scope, runtime, root_dir, &config_path);
if (r < 0)
return r;
STRV_FOREACH(i, files) {
r = install_info_add_auto(&c, *i);
if (r < 0)
return r;
}
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, files);
if (r == 0)
r = q;
return r;
}
int unit_file_reenable(
UnitFileScope scope,
bool runtime,
const char *root_dir,
char **files,
bool force,
UnitFileChange **changes,
unsigned *n_changes) {
int r;
r = unit_file_disable(scope, runtime, root_dir, files,
changes, n_changes);
if (r < 0)
return r;
return unit_file_enable(scope, runtime, root_dir, files, force,
changes, n_changes);
}
int unit_file_set_default(
UnitFileScope scope,
const char *root_dir,
const char *file,
bool force,
UnitFileChange **changes,
unsigned *n_changes) {
_cleanup_lookup_paths_free_ LookupPaths paths = {};
_cleanup_install_context_done_ InstallContext c = {};
_cleanup_free_ char *config_path = NULL;
char *path;
int r;
InstallInfo *i = NULL;
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
assert(file);
if (unit_name_to_type(file) != UNIT_TARGET)
return -EINVAL;
r = lookup_paths_init_from_scope(&paths, scope, root_dir);
if (r < 0)
return r;
r = get_config_path(scope, false, root_dir, &config_path);
if (r < 0)
return r;
r = install_info_add_auto(&c, file);
if (r < 0)
return r;
assert_se(i = hashmap_first(c.will_install));
r = unit_file_search(&c, i, &paths, root_dir, false);
if (r < 0)
return r;
path = strappenda(config_path, "/" SPECIAL_DEFAULT_TARGET);
r = create_symlink(i->path, path, force, changes, n_changes);
if (r < 0)
return r;
return 0;
}
int unit_file_get_default(
UnitFileScope scope,
const char *root_dir,
char **name) {
_cleanup_lookup_paths_free_ LookupPaths paths = {};
char **p;
int r;
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
assert(name);
r = lookup_paths_init_from_scope(&paths, scope, root_dir);
if (r < 0)
return r;
STRV_FOREACH(p, paths.unit_path) {
_cleanup_free_ char *path = NULL, *tmp = NULL;
char *n;
path = path_join(root_dir, *p, SPECIAL_DEFAULT_TARGET);
if (!path)
return -ENOMEM;
r = readlink_malloc(path, &tmp);
if (r == -ENOENT)
continue;
else if (r == -EINVAL)
/* not a symlink */
n = strdup(SPECIAL_DEFAULT_TARGET);
else if (r < 0)
return r;
else
n = strdup(basename(tmp));
if (!n)
return -ENOMEM;
*name = n;
return 0;
}
return -ENOENT;
}
UnitFileState unit_file_get_state(
UnitFileScope scope,
const char *root_dir,
const char *name) {
_cleanup_lookup_paths_free_ LookupPaths paths = {};
UnitFileState state = _UNIT_FILE_STATE_INVALID;
char **i;
_cleanup_free_ char *path = NULL;
int r;
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
assert(name);
if (root_dir && scope != UNIT_FILE_SYSTEM)
return -EINVAL;
if (!unit_name_is_valid(name, TEMPLATE_VALID))
return -EINVAL;
r = lookup_paths_init_from_scope(&paths, scope, root_dir);
if (r < 0)
return r;
STRV_FOREACH(i, paths.unit_path) {
struct stat st;
char *partial;
free(path);
path = NULL;
path = path_join(root_dir, *i, name);
if (!path)
return -ENOMEM;
if (root_dir)
partial = path + strlen(root_dir);
else
partial = path;
/*
* Search for a unit file in our default paths, to
* be sure, that there are no broken symlinks.
*/
if (lstat(path, &st) < 0) {
r = -errno;
if (errno != ENOENT)
return r;
if (!unit_name_is_instance(name))
continue;
} else {
if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
return -ENOENT;
r = null_or_empty_path(path);
if (r < 0 && r != -ENOENT)
return r;
else if (r > 0) {
state = path_startswith(*i, "/run") ?
UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
return state;
}
}
r = find_symlinks_in_scope(scope, root_dir, name, &state);
if (r < 0)
return r;
else if (r > 0)
return state;
r = unit_file_can_install(&paths, root_dir, partial, true);
if (r < 0 && errno != ENOENT)
return r;
else if (r > 0)
return UNIT_FILE_DISABLED;
else if (r == 0)
return UNIT_FILE_STATIC;
}
return r < 0 ? r : state;
}
int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name) {
_cleanup_strv_free_ char **files = NULL;
char **p;
int r;
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
assert(name);
if (scope == UNIT_FILE_SYSTEM)
r = conf_files_list(&files, ".preset", root_dir,
"/etc/systemd/system-preset",
"/usr/local/lib/systemd/system-preset",
"/usr/lib/systemd/system-preset",
#ifdef HAVE_SPLIT_USR
"/lib/systemd/system-preset",
#endif
NULL);
else if (scope == UNIT_FILE_GLOBAL)
r = conf_files_list(&files, ".preset", root_dir,
"/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(p, files) {
_cleanup_fclose_ FILE *f;
f = fopen(*p, "re");
if (!f) {
if (errno == ENOENT)
continue;
return -errno;
}
for (;;) {
char line[LINE_MAX], *l;
if (!fgets(line, sizeof(line), f))
break;
l = strstrip(line);
if (!*l)
continue;
if (strchr(COMMENTS "\n", *l))
continue;
if (first_word(l, "enable")) {
l += 6;
l += strspn(l, WHITESPACE);
if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
log_debug("Preset file says enable %s.", name);
return 1;
}
} else if (first_word(l, "disable")) {
l += 7;
l += strspn(l, WHITESPACE);
if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
log_debug("Preset file says disable %s.", name);
return 0;
}
} else
log_debug("Couldn't parse line '%s'", l);
}
}
/* Default is "enable" */
log_debug("Preset file doesn't say anything about %s, enabling.", name);
return 1;
}
int unit_file_preset(
UnitFileScope scope,
bool runtime,
const char *root_dir,
char **files,
UnitFilePresetMode mode,
bool force,
UnitFileChange **changes,
unsigned *n_changes) {
_cleanup_install_context_done_ InstallContext plus = {}, minus = {};
_cleanup_lookup_paths_free_ LookupPaths paths = {};
_cleanup_free_ char *config_path = NULL;
char **i;
int r, q;
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
assert(mode < _UNIT_FILE_PRESET_MAX);
r = lookup_paths_init_from_scope(&paths, scope, root_dir);
if (r < 0)
return r;
r = get_config_path(scope, runtime, root_dir, &config_path);
if (r < 0)
return r;
STRV_FOREACH(i, files) {
if (!unit_name_is_valid(*i, TEMPLATE_VALID))
return -EINVAL;
r = unit_file_query_preset(scope, root_dir, *i);
if (r < 0)
return r;
if (r && mode != UNIT_FILE_PRESET_DISABLE_ONLY)
r = install_info_add_auto(&plus, *i);
else if (!r && mode != UNIT_FILE_PRESET_ENABLE_ONLY)
r = install_info_add_auto(&minus, *i);
else
r = 0;
if (r < 0)
return r;
}
r = 0;
if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) {
_cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
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, files);
if (r == 0)
r = q;
}
if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) {
/* 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;
}
return r;
}
int unit_file_preset_all(
UnitFileScope scope,
bool runtime,
const char *root_dir,
UnitFilePresetMode mode,
bool force,
UnitFileChange **changes,
unsigned *n_changes) {
_cleanup_install_context_done_ InstallContext plus = {}, minus = {};
_cleanup_lookup_paths_free_ LookupPaths paths = {};
_cleanup_free_ char *config_path = NULL;
char **i;
int r, q;
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
assert(mode < _UNIT_FILE_PRESET_MAX);
r = lookup_paths_init_from_scope(&paths, scope, root_dir);
if (r < 0)
return r;
r = get_config_path(scope, runtime, root_dir, &config_path);
if (r < 0)
return r;
STRV_FOREACH(i, paths.unit_path) {
_cleanup_closedir_ DIR *d = NULL;
_cleanup_free_ char *units_dir;
units_dir = path_join(root_dir, *i, NULL);
if (!units_dir)
return -ENOMEM;
d = opendir(units_dir);
if (!d) {
if (errno == ENOENT)
continue;
return -errno;
}
for (;;) {
struct dirent *de;
errno = 0;
de = readdir(d);
if (!de && errno != 0)
return -errno;
if (!de)
break;
if (ignore_file(de->d_name))
continue;
if (!unit_name_is_valid(de->d_name, TEMPLATE_VALID))
continue;
dirent_ensure_type(d, de);
if (de->d_type != DT_REG)
continue;
r = unit_file_query_preset(scope, root_dir, de->d_name);
if (r < 0)
return r;
if (r && mode != UNIT_FILE_PRESET_DISABLE_ONLY)
r = install_info_add_auto(&plus, de->d_name);
else if (!r && mode != UNIT_FILE_PRESET_ENABLE_ONLY)
r = install_info_add_auto(&minus, de->d_name);
else
r = 0;
if (r < 0)
return r;
}
}
r = 0;
if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) {
_cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
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, NULL);
if (r == 0)
r = q;
}
if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) {
q = install_context_apply(&plus, &paths, config_path, root_dir, force, changes, n_changes);
if (r == 0)
r = q;
}
return r;
}
static void unitfilelist_free(UnitFileList **f) {
if (!*f)
return;
free((*f)->path);
free(*f);
}
#define _cleanup_unitfilelist_free_ _cleanup_(unitfilelist_free)
int unit_file_get_list(
UnitFileScope scope,
const char *root_dir,
Hashmap *h) {
_cleanup_lookup_paths_free_ LookupPaths paths = {};
char **i;
int r;
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
assert(h);
if (root_dir && scope != UNIT_FILE_SYSTEM)
return -EINVAL;
if (root_dir) {
r = access(root_dir, F_OK);
if (r < 0)
return -errno;
}
r = lookup_paths_init_from_scope(&paths, scope, root_dir);
if (r < 0)
return r;
STRV_FOREACH(i, paths.unit_path) {
_cleanup_closedir_ DIR *d = NULL;
_cleanup_free_ char *units_dir;
units_dir = path_join(root_dir, *i, NULL);
if (!units_dir)
return -ENOMEM;
d = opendir(units_dir);
if (!d) {
if (errno == ENOENT)
continue;
return -errno;
}
for (;;) {
_cleanup_unitfilelist_free_ UnitFileList *f = NULL;
struct dirent *de;
errno = 0;
de = readdir(d);
if (!de && errno != 0)
return -errno;
if (!de)
break;
if (ignore_file(de->d_name))
continue;
if (!unit_name_is_valid(de->d_name, TEMPLATE_VALID))
continue;
if (hashmap_get(h, de->d_name))
continue;
dirent_ensure_type(d, de);
if (!IN_SET(de->d_type, DT_LNK, DT_REG))
continue;
f = new0(UnitFileList, 1);
if (!f)
return -ENOMEM;
f->path = path_make_absolute(de->d_name, units_dir);
if (!f->path)
return -ENOMEM;
r = null_or_empty_path(f->path);
if (r < 0 && r != -ENOENT)
return r;
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)
return r;
else if (r > 0) {
f->state = UNIT_FILE_ENABLED;
goto found;
}
r = unit_file_can_install(&paths, root_dir, f->path, true);
if (r == -EINVAL || /* Invalid setting? */
r == -EBADMSG || /* Invalid format? */
r == -ENOENT /* Included file not found? */)
f->state = UNIT_FILE_INVALID;
else if (r < 0)
return r;
else if (r > 0)
f->state = UNIT_FILE_DISABLED;
else
f->state = UNIT_FILE_STATIC;
found:
r = hashmap_put(h, basename(f->path), f);
if (r < 0)
return r;
f = NULL; /* prevent cleanup */
}
}
return r;
}
static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = {
[UNIT_FILE_ENABLED] = "enabled",
[UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtime",
[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",
[UNIT_FILE_INVALID] = "invalid",
};
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);
static const char* const unit_file_preset_mode_table[_UNIT_FILE_PRESET_MAX] = {
[UNIT_FILE_PRESET_FULL] = "full",
[UNIT_FILE_PRESET_ENABLE_ONLY] = "enable-only",
[UNIT_FILE_PRESET_DISABLE_ONLY] = "disable-only",
};
DEFINE_STRING_TABLE_LOOKUP(unit_file_preset_mode, UnitFilePresetMode);