Systemd/src/shared/path-lookup.c
Lennart Poettering 45639f1be5 core: never remove "transient" and "control" directories from unit search path
This changes the unit search path logic to never drop the transient and
control directories from the unit search path. This is necessary as we
add new entries to both during runtime, due to the "systemctl
set-property" and transient unit logic.

Previously, the "transient" directory was created during early boot to
deal with this, but the "control" directories were not covered like
that. Creating the control directories early at boot is not possible
however, as /etc might be read-only then, and we do define a persistent
control directory. Hence, let's create these dirs on-demand when we need
them, and make sure the search path clean-up logic never drops them from
the search path even if they are initially missing.

(Also, always create these paths properly labelled)
2017-11-29 12:34:12 +01:00

873 lines
26 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
Copyright 2010 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 of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "alloc-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "install.h"
#include "log.h"
#include "macro.h"
#include "mkdir.h"
#include "path-lookup.h"
#include "path-util.h"
#include "rm-rf.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "user-util.h"
#include "util.h"
static int user_runtime_dir(char **ret, const char *suffix) {
const char *e;
char *j;
assert(ret);
assert(suffix);
e = getenv("XDG_RUNTIME_DIR");
if (!e)
return -ENXIO;
j = strappend(e, suffix);
if (!j)
return -ENOMEM;
*ret = j;
return 0;
}
static int user_config_dir(char **ret, const char *suffix) {
const char *e;
char *j;
int r;
assert(ret);
e = getenv("XDG_CONFIG_HOME");
if (e)
j = strappend(e, suffix);
else {
_cleanup_free_ char *home = NULL;
r = get_home_dir(&home);
if (r < 0)
return r;
j = strjoin(home, "/.config", suffix);
}
if (!j)
return -ENOMEM;
*ret = j;
return 0;
}
static int user_data_dir(char **ret, const char *suffix) {
const char *e;
char *j;
int r;
assert(ret);
assert(suffix);
/* We don't treat /etc/xdg/systemd here as the spec
* suggests because we assume that is a link to
* /etc/systemd/ anyway. */
e = getenv("XDG_DATA_HOME");
if (e)
j = strappend(e, suffix);
else {
_cleanup_free_ char *home = NULL;
r = get_home_dir(&home);
if (r < 0)
return r;
j = strjoin(home, "/.local/share", suffix);
}
if (!j)
return -ENOMEM;
*ret = j;
return 1;
}
static const char* const user_data_unit_paths[] = {
"/usr/local/lib/systemd/user",
"/usr/local/share/systemd/user",
USER_DATA_UNIT_PATH,
"/usr/lib/systemd/user",
"/usr/share/systemd/user",
NULL
};
static const char* const user_config_unit_paths[] = {
USER_CONFIG_UNIT_PATH,
"/etc/systemd/user",
NULL
};
static char** user_dirs(
const char *persistent_config,
const char *runtime_config,
const char *generator,
const char *generator_early,
const char *generator_late,
const char *transient,
const char *persistent_control,
const char *runtime_control) {
_cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL;
_cleanup_free_ char *data_home = NULL;
_cleanup_strv_free_ char **res = NULL;
const char *e;
char **tmp;
int r;
/* Implement the mechanisms defined in
*
* http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
*
* We look in both the config and the data dirs because we
* want to encourage that distributors ship their unit files
* as data, and allow overriding as configuration.
*/
e = getenv("XDG_CONFIG_DIRS");
if (e) {
config_dirs = strv_split(e, ":");
if (!config_dirs)
return NULL;
}
r = user_data_dir(&data_home, "/systemd/user");
if (r < 0 && r != -ENXIO)
return NULL;
e = getenv("XDG_DATA_DIRS");
if (e)
data_dirs = strv_split(e, ":");
else
data_dirs = strv_new("/usr/local/share",
"/usr/share",
NULL);
if (!data_dirs)
return NULL;
/* Now merge everything we found. */
if (strv_extend(&res, persistent_control) < 0)
return NULL;
if (strv_extend(&res, runtime_control) < 0)
return NULL;
if (strv_extend(&res, transient) < 0)
return NULL;
if (strv_extend(&res, generator_early) < 0)
return NULL;
if (strv_extend_strv_concat(&res, config_dirs, "/systemd/user") < 0)
return NULL;
if (strv_extend(&res, persistent_config) < 0)
return NULL;
if (strv_extend_strv(&res, (char**) user_config_unit_paths, false) < 0)
return NULL;
if (strv_extend(&res, runtime_config) < 0)
return NULL;
if (strv_extend(&res, generator) < 0)
return NULL;
if (strv_extend(&res, data_home) < 0)
return NULL;
if (strv_extend_strv_concat(&res, data_dirs, "/systemd/user") < 0)
return NULL;
if (strv_extend_strv(&res, (char**) user_data_unit_paths, false) < 0)
return NULL;
if (strv_extend(&res, generator_late) < 0)
return NULL;
if (path_strv_make_absolute_cwd(res) < 0)
return NULL;
tmp = res;
res = NULL;
return tmp;
}
bool path_is_user_data_dir(const char *path) {
assert(path);
return strv_contains((char**) user_data_unit_paths, path);
}
bool path_is_user_config_dir(const char *path) {
assert(path);
return strv_contains((char**) user_config_unit_paths, path);
}
static int acquire_generator_dirs(
UnitFileScope scope,
const char *tempdir,
char **generator,
char **generator_early,
char **generator_late) {
_cleanup_free_ char *x = NULL, *y = NULL, *z = NULL;
const char *prefix;
assert(generator);
assert(generator_early);
assert(generator_late);
assert(IN_SET(scope, UNIT_FILE_SYSTEM, UNIT_FILE_USER, UNIT_FILE_GLOBAL));
if (scope == UNIT_FILE_GLOBAL)
return -EOPNOTSUPP;
if (tempdir)
prefix = tempdir;
else if (scope == UNIT_FILE_SYSTEM)
prefix = "/run/systemd";
else if (scope == UNIT_FILE_USER) {
const char *e;
e = getenv("XDG_RUNTIME_DIR");
if (!e)
return -ENXIO;
prefix = strjoina(e, "/systemd");
}
x = strappend(prefix, "/generator");
if (!x)
return -ENOMEM;
y = strappend(prefix, "/generator.early");
if (!y)
return -ENOMEM;
z = strappend(prefix, "/generator.late");
if (!z)
return -ENOMEM;
*generator = x;
*generator_early = y;
*generator_late = z;
x = y = z = NULL;
return 0;
}
static int acquire_transient_dir(
UnitFileScope scope,
const char *tempdir,
char **ret) {
char *transient;
assert(ret);
assert(IN_SET(scope, UNIT_FILE_SYSTEM, UNIT_FILE_USER, UNIT_FILE_GLOBAL));
if (scope == UNIT_FILE_GLOBAL)
return -EOPNOTSUPP;
if (tempdir)
transient = strjoin(tempdir, "/transient");
else if (scope == UNIT_FILE_SYSTEM)
transient = strdup("/run/systemd/transient");
else
return user_runtime_dir(ret, "/systemd/transient");
if (!transient)
return -ENOMEM;
*ret = transient;
return 0;
}
static int acquire_config_dirs(UnitFileScope scope, char **persistent, char **runtime) {
_cleanup_free_ char *a = NULL, *b = NULL;
int r;
assert(persistent);
assert(runtime);
switch (scope) {
case UNIT_FILE_SYSTEM:
a = strdup(SYSTEM_CONFIG_UNIT_PATH);
b = strdup("/run/systemd/system");
break;
case UNIT_FILE_GLOBAL:
a = strdup(USER_CONFIG_UNIT_PATH);
b = strdup("/run/systemd/user");
break;
case UNIT_FILE_USER:
r = user_config_dir(&a, "/systemd/user");
if (r < 0 && r != -ENXIO)
return r;
r = user_runtime_dir(runtime, "/systemd/user");
if (r < 0) {
if (r != -ENXIO)
return r;
/* If XDG_RUNTIME_DIR is not set, don't consider that fatal, simply initialize the runtime
* directory to NULL */
*runtime = NULL;
}
*persistent = a;
a = NULL;
return 0;
default:
assert_not_reached("Hmm, unexpected scope value.");
}
if (!a || !b)
return -ENOMEM;
*persistent = a;
*runtime = b;
a = b = NULL;
return 0;
}
static int acquire_control_dirs(UnitFileScope scope, char **persistent, char **runtime) {
_cleanup_free_ char *a = NULL;
int r;
assert(persistent);
assert(runtime);
switch (scope) {
case UNIT_FILE_SYSTEM: {
_cleanup_free_ char *b = NULL;
a = strdup("/etc/systemd/system.control");
if (!a)
return -ENOMEM;
b = strdup("/run/systemd/system.control");
if (!b)
return -ENOMEM;
*runtime = b;
b = NULL;
break;
}
case UNIT_FILE_USER:
r = user_config_dir(&a, "/systemd/system.control");
if (r < 0 && r != -ENXIO)
return r;
r = user_runtime_dir(runtime, "/systemd/system.control");
if (r < 0) {
if (r != -ENXIO)
return r;
/* If XDG_RUNTIME_DIR is not set, don't consider this fatal, simply initialize the directory to
* NULL */
*runtime = NULL;
}
break;
case UNIT_FILE_GLOBAL:
return -EOPNOTSUPP;
default:
assert_not_reached("Hmm, unexpected scope value.");
}
*persistent = a;
a = NULL;
return 0;
}
static int patch_root_prefix(char **p, const char *root_dir) {
char *c;
assert(p);
if (!*p)
return 0;
c = prefix_root(root_dir, *p);
if (!c)
return -ENOMEM;
free(*p);
*p = c;
return 0;
}
static int patch_root_prefix_strv(char **l, const char *root_dir) {
char **i;
int r;
if (!root_dir)
return 0;
STRV_FOREACH(i, l) {
r = patch_root_prefix(i, root_dir);
if (r < 0)
return r;
}
return 0;
}
int lookup_paths_init(
LookupPaths *p,
UnitFileScope scope,
LookupPathsFlags flags,
const char *root_dir) {
_cleanup_(rmdir_and_freep) char *tempdir = NULL;
_cleanup_free_ char
*root = NULL,
*persistent_config = NULL, *runtime_config = NULL,
*generator = NULL, *generator_early = NULL, *generator_late = NULL,
*transient = NULL,
*persistent_control = NULL, *runtime_control = NULL;
bool append = false; /* Add items from SYSTEMD_UNIT_PATH before normal directories */
_cleanup_strv_free_ char **paths = NULL;
const char *e;
int r;
assert(p);
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
if (!isempty(root_dir) && !path_equal(root_dir, "/")) {
if (scope == UNIT_FILE_USER)
return -EINVAL;
r = is_dir(root_dir, true);
if (r < 0)
return r;
if (r == 0)
return -ENOTDIR;
root = strdup(root_dir);
if (!root)
return -ENOMEM;
}
if (flags & LOOKUP_PATHS_TEMPORARY_GENERATED) {
r = mkdtemp_malloc("/tmp/systemd-temporary-XXXXXX", &tempdir);
if (r < 0)
return log_error_errno(r, "Failed to create temporary directory: %m");
}
/* Note: when XDG_RUNTIME_DIR is not set this will not return -ENXIO, but simply set runtime_config to NULL */
r = acquire_config_dirs(scope, &persistent_config, &runtime_config);
if (r < 0)
return r;
if ((flags & LOOKUP_PATHS_EXCLUDE_GENERATED) == 0) {
/* Note: if XDG_RUNTIME_DIR is not set, this will fail completely with ENXIO */
r = acquire_generator_dirs(scope, tempdir,
&generator, &generator_early, &generator_late);
if (r < 0 && !IN_SET(r, -EOPNOTSUPP, -ENXIO))
return r;
}
/* Note: if XDG_RUNTIME_DIR is not set, this will fail completely with ENXIO */
r = acquire_transient_dir(scope, tempdir, &transient);
if (r < 0 && !IN_SET(r, -EOPNOTSUPP, -ENXIO))
return r;
/* Note: when XDG_RUNTIME_DIR is not set this will not return -ENXIO, but simply set runtime_control to NULL */
r = acquire_control_dirs(scope, &persistent_control, &runtime_control);
if (r < 0 && r != -EOPNOTSUPP)
return r;
/* First priority is whatever has been passed to us via env vars */
e = getenv("SYSTEMD_UNIT_PATH");
if (e) {
const char *k;
k = endswith(e, ":");
if (k) {
e = strndupa(e, k - e);
append = true;
}
/* FIXME: empty components in other places should be rejected. */
r = path_split_and_make_absolute(e, &paths);
if (r < 0)
return r;
}
if (!paths || append) {
/* Let's figure something out. */
_cleanup_strv_free_ char **add = NULL;
/* For the user units we include share/ in the search
* path in order to comply with the XDG basedir spec.
* For the system stuff we avoid such nonsense. OTOH
* we include /lib in the search path for the system
* stuff but avoid it for user stuff. */
switch (scope) {
case UNIT_FILE_SYSTEM:
add = strv_new(
/* If you modify this you also want to modify
* systemdsystemunitpath= in systemd.pc.in! */
STRV_IFNOTNULL(persistent_control),
STRV_IFNOTNULL(runtime_control),
STRV_IFNOTNULL(transient),
STRV_IFNOTNULL(generator_early),
persistent_config,
SYSTEM_CONFIG_UNIT_PATH,
"/etc/systemd/system",
runtime_config,
"/run/systemd/system",
STRV_IFNOTNULL(generator),
"/usr/local/lib/systemd/system",
SYSTEM_DATA_UNIT_PATH,
"/usr/lib/systemd/system",
#if HAVE_SPLIT_USR
"/lib/systemd/system",
#endif
STRV_IFNOTNULL(generator_late),
NULL);
break;
case UNIT_FILE_GLOBAL:
add = strv_new(
/* If you modify this you also want to modify
* systemduserunitpath= in systemd.pc.in, and
* the arrays in user_dirs() above! */
STRV_IFNOTNULL(persistent_control),
STRV_IFNOTNULL(runtime_control),
STRV_IFNOTNULL(transient),
STRV_IFNOTNULL(generator_early),
persistent_config,
USER_CONFIG_UNIT_PATH,
"/etc/systemd/user",
runtime_config,
"/run/systemd/user",
STRV_IFNOTNULL(generator),
"/usr/local/lib/systemd/user",
"/usr/local/share/systemd/user",
USER_DATA_UNIT_PATH,
"/usr/lib/systemd/user",
"/usr/share/systemd/user",
STRV_IFNOTNULL(generator_late),
NULL);
break;
case UNIT_FILE_USER:
add = user_dirs(persistent_config, runtime_config,
generator, generator_early, generator_late,
transient,
persistent_config, runtime_control);
break;
default:
assert_not_reached("Hmm, unexpected scope?");
}
if (!add)
return -ENOMEM;
if (paths) {
r = strv_extend_strv(&paths, add, true);
if (r < 0)
return r;
} else {
/* Small optimization: if paths is NULL (and it usually is), we can simply assign 'add' to it,
* and don't have to copy anything */
paths = add;
add = NULL;
}
}
r = patch_root_prefix(&persistent_config, root);
if (r < 0)
return r;
r = patch_root_prefix(&runtime_config, root);
if (r < 0)
return r;
r = patch_root_prefix(&generator, root);
if (r < 0)
return r;
r = patch_root_prefix(&generator_early, root);
if (r < 0)
return r;
r = patch_root_prefix(&generator_late, root);
if (r < 0)
return r;
r = patch_root_prefix(&transient, root);
if (r < 0)
return r;
r = patch_root_prefix(&persistent_control, root);
if (r < 0)
return r;
r = patch_root_prefix(&runtime_control, root);
if (r < 0)
return r;
r = patch_root_prefix_strv(paths, root);
if (r < 0)
return -ENOMEM;
p->search_path = strv_uniq(paths);
paths = NULL;
p->persistent_config = persistent_config;
p->runtime_config = runtime_config;
persistent_config = runtime_config = NULL;
p->generator = generator;
p->generator_early = generator_early;
p->generator_late = generator_late;
generator = generator_early = generator_late = NULL;
p->transient = transient;
transient = NULL;
p->persistent_control = persistent_control;
p->runtime_control = runtime_control;
persistent_control = runtime_control = NULL;
p->root_dir = root;
root = NULL;
p->temporary_dir = tempdir;
tempdir = NULL;
return 0;
}
void lookup_paths_free(LookupPaths *p) {
if (!p)
return;
p->search_path = strv_free(p->search_path);
p->persistent_config = mfree(p->persistent_config);
p->runtime_config = mfree(p->runtime_config);
p->generator = mfree(p->generator);
p->generator_early = mfree(p->generator_early);
p->generator_late = mfree(p->generator_late);
p->transient = mfree(p->transient);
p->persistent_control = mfree(p->persistent_control);
p->runtime_control = mfree(p->runtime_control);
p->root_dir = mfree(p->root_dir);
p->temporary_dir = mfree(p->temporary_dir);
}
int lookup_paths_reduce(LookupPaths *p) {
_cleanup_free_ struct stat *stats = NULL;
size_t n_stats = 0, allocated = 0;
unsigned c = 0;
int r;
assert(p);
/* Drop duplicates and non-existing directories from the search path. We figure out whether two directories are
* the same by comparing their device and inode numbers. Note one special tweak: when we have a root path set,
* we do not follow symlinks when retrieving them, because the kernel wouldn't take the root prefix into
* account when following symlinks. When we have no root path set this restriction does not apply however. */
if (!p->search_path)
return 0;
while (p->search_path[c]) {
struct stat st;
unsigned k;
/* Never strip the transient and control directories from the path */
if (path_equal_ptr(p->search_path[c], p->transient) ||
path_equal_ptr(p->search_path[c], p->persistent_control) ||
path_equal_ptr(p->search_path[c], p->runtime_control)) {
c++;
continue;
}
if (p->root_dir)
r = lstat(p->search_path[c], &st);
else
r = stat(p->search_path[c], &st);
if (r < 0) {
if (errno == ENOENT)
goto remove_item;
/* If something we don't grok happened, let's better leave it in. */
log_debug_errno(errno, "Failed to stat %s: %m", p->search_path[c]);
c++;
continue;
}
for (k = 0; k < n_stats; k++) {
if (stats[k].st_dev == st.st_dev &&
stats[k].st_ino == st.st_ino)
break;
}
if (k < n_stats) /* Is there already an entry with the same device/inode? */
goto remove_item;
if (!GREEDY_REALLOC(stats, allocated, n_stats+1))
return -ENOMEM;
stats[n_stats++] = st;
c++;
continue;
remove_item:
free(p->search_path[c]);
memmove(p->search_path + c,
p->search_path + c + 1,
(strv_length(p->search_path + c + 1) + 1) * sizeof(char*));
}
if (strv_isempty(p->search_path)) {
log_debug("Ignoring unit files.");
p->search_path = strv_free(p->search_path);
} else {
_cleanup_free_ char *t;
t = strv_join(p->search_path, "\n\t");
if (!t)
return -ENOMEM;
log_debug("Looking for unit files in (higher priority first):\n\t%s", t);
}
return 0;
}
int lookup_paths_mkdir_generator(LookupPaths *p) {
int r, q;
assert(p);
if (!p->generator || !p->generator_early || !p->generator_late)
return -EINVAL;
r = mkdir_p_label(p->generator, 0755);
q = mkdir_p_label(p->generator_early, 0755);
if (q < 0 && r >= 0)
r = q;
q = mkdir_p_label(p->generator_late, 0755);
if (q < 0 && r >= 0)
r = q;
return r;
}
void lookup_paths_trim_generator(LookupPaths *p) {
assert(p);
/* Trim empty dirs */
if (p->generator)
(void) rmdir(p->generator);
if (p->generator_early)
(void) rmdir(p->generator_early);
if (p->generator_late)
(void) rmdir(p->generator_late);
}
void lookup_paths_flush_generator(LookupPaths *p) {
assert(p);
/* Flush the generated unit files in full */
if (p->generator)
(void) rm_rf(p->generator, REMOVE_ROOT);
if (p->generator_early)
(void) rm_rf(p->generator_early, REMOVE_ROOT);
if (p->generator_late)
(void) rm_rf(p->generator_late, REMOVE_ROOT);
if (p->temporary_dir)
(void) rm_rf(p->temporary_dir, REMOVE_ROOT);
}
char **generator_binary_paths(UnitFileScope scope) {
switch (scope) {
case UNIT_FILE_SYSTEM:
return strv_new("/run/systemd/system-generators",
"/etc/systemd/system-generators",
"/usr/local/lib/systemd/system-generators",
SYSTEM_GENERATOR_PATH,
NULL);
case UNIT_FILE_GLOBAL:
case UNIT_FILE_USER:
return strv_new("/run/systemd/user-generators",
"/etc/systemd/user-generators",
"/usr/local/lib/systemd/user-generators",
USER_GENERATOR_PATH,
NULL);
default:
assert_not_reached("Hmm, unexpected scope.");
}
}