Merge pull request #20321 from bluca/state_dir_symlink

core: add [State|Runtime|Cache|Logs]DirectorySymlink
This commit is contained in:
Lennart Poettering
2021-10-28 20:52:56 +02:00
committed by GitHub
15 changed files with 795 additions and 147 deletions
+233 -49
View File
@@ -1943,26 +1943,29 @@ static int build_environment(
}
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
_cleanup_free_ char *pre = NULL, *joined = NULL;
_cleanup_free_ char *joined = NULL;
const char *n;
if (!p->prefix[t])
continue;
if (strv_isempty(c->directories[t].paths))
if (c->directories[t].n_items == 0)
continue;
n = exec_directory_env_name_to_string(t);
if (!n)
continue;
pre = strjoin(p->prefix[t], "/");
if (!pre)
return -ENOMEM;
for (size_t i = 0; i < c->directories[t].n_items; i++) {
_cleanup_free_ char *prefixed = NULL;
joined = strv_join_full(c->directories[t].paths, ":", pre, true);
if (!joined)
return -ENOMEM;
prefixed = path_join(p->prefix[t], c->directories[t].items[i].path);
if (!prefixed)
return -ENOMEM;
if (!strextend_with_separator(&joined, ":", prefixed))
return -ENOMEM;
}
x = strjoin(n, "=", joined);
if (!x)
@@ -2078,15 +2081,15 @@ bool exec_needs_mount_namespace(
if (params && !params->prefix[t])
continue;
if (!strv_isempty(context->directories[t].paths))
if (context->directories[t].n_items > 0)
return true;
}
}
if (context->dynamic_user &&
(!strv_isempty(context->directories[EXEC_DIRECTORY_STATE].paths) ||
!strv_isempty(context->directories[EXEC_DIRECTORY_CACHE].paths) ||
!strv_isempty(context->directories[EXEC_DIRECTORY_LOGS].paths)))
(context->directories[EXEC_DIRECTORY_STATE].n_items > 0 ||
context->directories[EXEC_DIRECTORY_CACHE].n_items > 0 ||
context->directories[EXEC_DIRECTORY_LOGS].n_items > 0))
return true;
if (context->log_namespace)
@@ -2268,12 +2271,43 @@ static bool exec_directory_is_private(const ExecContext *context, ExecDirectoryT
return true;
}
static int create_many_symlinks(const char *root, const char *source, char **symlinks) {
_cleanup_free_ char *src_abs = NULL;
char **dst;
int r;
assert(source);
src_abs = path_join(root, source);
if (!src_abs)
return -ENOMEM;
STRV_FOREACH(dst, symlinks) {
_cleanup_free_ char *dst_abs = NULL;
dst_abs = path_join(root, *dst);
if (!dst_abs)
return -ENOMEM;
r = mkdir_parents_label(dst_abs, 0755);
if (r < 0)
return r;
r = symlink_idempotent(src_abs, dst_abs, true);
if (r < 0)
return r;
}
return 0;
}
static int setup_exec_directory(
const ExecContext *context,
const ExecParameters *params,
uid_t uid,
gid_t gid,
ExecDirectoryType type,
bool needs_mount_namespace,
int *exit_status) {
static const int exit_status_table[_EXEC_DIRECTORY_TYPE_MAX] = {
@@ -2283,7 +2317,6 @@ static int setup_exec_directory(
[EXEC_DIRECTORY_LOGS] = EXIT_LOGS_DIRECTORY,
[EXEC_DIRECTORY_CONFIGURATION] = EXIT_CONFIGURATION_DIRECTORY,
};
char **rt;
int r;
assert(context);
@@ -2301,10 +2334,10 @@ static int setup_exec_directory(
gid = 0;
}
STRV_FOREACH(rt, context->directories[type].paths) {
for (size_t i = 0; i < context->directories[type].n_items; i++) {
_cleanup_free_ char *p = NULL, *pp = NULL;
p = path_join(params->prefix[type], *rt);
p = path_join(params->prefix[type], context->directories[type].items[i].path);
if (!p) {
r = -ENOMEM;
goto fail;
@@ -2351,7 +2384,7 @@ static int setup_exec_directory(
if (r < 0)
goto fail;
if (!path_extend(&pp, *rt)) {
if (!path_extend(&pp, context->directories[type].items[i].path)) {
r = -ENOMEM;
goto fail;
}
@@ -2384,7 +2417,9 @@ static int setup_exec_directory(
goto fail;
}
/* And link it up from the original place */
/* And link it up from the original place. Note that if a mount namespace is going to be
* used, then this symlink remains on the host, and a new one for the child namespace will
* be created later. */
r = symlink_idempotent(pp, p, true);
if (r < 0)
goto fail;
@@ -2407,7 +2442,7 @@ static int setup_exec_directory(
if (r < 0)
goto fail;
q = path_join(params->prefix[type], "private", *rt);
q = path_join(params->prefix[type], "private", context->directories[type].items[i].path);
if (!q) {
r = -ENOMEM;
goto fail;
@@ -2460,7 +2495,7 @@ static int setup_exec_directory(
if (((st.st_mode ^ context->directories[type].mode) & 07777) != 0)
log_warning("%s \'%s\' already exists but the mode is different. "
"(File system: %o %sMode: %o)",
exec_directory_type_to_string(type), *rt,
exec_directory_type_to_string(type), context->directories[type].items[i].path,
st.st_mode & 07777, exec_directory_type_to_string(type), context->directories[type].mode & 07777);
continue;
@@ -2483,6 +2518,17 @@ static int setup_exec_directory(
goto fail;
}
/* If we are not going to run in a namespace, set up the symlinks - otherwise
* they are set up later, to allow configuring empty var/run/etc. */
if (!needs_mount_namespace)
for (size_t i = 0; i < context->directories[type].n_items; i++) {
r = create_many_symlinks(params->prefix[type],
context->directories[type].items[i].path,
context->directories[type].items[i].symlinks);
if (r < 0)
goto fail;
}
return 0;
fail:
@@ -3032,7 +3078,7 @@ static int compile_bind_mounts(
if (!params->prefix[t])
continue;
n += strv_length(context->directories[t].paths);
n += context->directories[t].n_items;
}
if (n <= 0) {
@@ -3073,12 +3119,10 @@ static int compile_bind_mounts(
}
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
char **suffix;
if (!params->prefix[t])
continue;
if (strv_isempty(context->directories[t].paths))
if (context->directories[t].n_items == 0)
continue;
if (exec_directory_is_private(context, t) &&
@@ -3100,13 +3144,13 @@ static int compile_bind_mounts(
goto finish;
}
STRV_FOREACH(suffix, context->directories[t].paths) {
for (size_t i = 0; i < context->directories[t].n_items; i++) {
char *s, *d;
if (exec_directory_is_private(context, t))
s = path_join(params->prefix[t], "private", *suffix);
s = path_join(params->prefix[t], "private", context->directories[t].items[i].path);
else
s = path_join(params->prefix[t], *suffix);
s = path_join(params->prefix[t], context->directories[t].items[i].path);
if (!s) {
r = -ENOMEM;
goto finish;
@@ -3117,7 +3161,7 @@ static int compile_bind_mounts(
/* When RootDirectory= or RootImage= are set, then the symbolic link to the private
* directory is not created on the root directory. So, let's bind-mount the directory
* on the 'non-private' place. */
d = path_join(params->prefix[t], *suffix);
d = path_join(params->prefix[t], context->directories[t].items[i].path);
else
d = strdup(s);
if (!d) {
@@ -3150,6 +3194,61 @@ finish:
return r;
}
/* ret_symlinks will contain a list of pairs src:dest that describes
* the symlinks to create later on. For example, the symlinks needed
* to safely give private directories to DynamicUser=1 users. */
static int compile_symlinks(
const ExecContext *context,
const ExecParameters *params,
char ***ret_symlinks) {
_cleanup_strv_free_ char **symlinks = NULL;
int r;
assert(context);
assert(params);
assert(ret_symlinks);
for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) {
for (size_t i = 0; i < context->directories[dt].n_items; i++) {
_cleanup_free_ char *private_path = NULL, *path = NULL;
char **symlink;
STRV_FOREACH(symlink, context->directories[dt].items[i].symlinks) {
_cleanup_free_ char *src_abs = NULL, *dst_abs = NULL;
src_abs = path_join(params->prefix[dt], context->directories[dt].items[i].path);
dst_abs = path_join(params->prefix[dt], *symlink);
if (!src_abs || !dst_abs)
return -ENOMEM;
r = strv_consume_pair(&symlinks, TAKE_PTR(src_abs), TAKE_PTR(dst_abs));
if (r < 0)
return r;
}
if (!exec_directory_is_private(context, dt))
continue;
private_path = path_join(params->prefix[dt], "private", context->directories[dt].items[i].path);
if (!private_path)
return -ENOMEM;
path = path_join(params->prefix[dt], context->directories[dt].items[i].path);
if (!path)
return -ENOMEM;
r = strv_consume_pair(&symlinks, TAKE_PTR(private_path), TAKE_PTR(path));
if (r < 0)
return r;
}
}
*ret_symlinks = TAKE_PTR(symlinks);
return 0;
}
static bool insist_on_sandboxing(
const ExecContext *context,
const char *root_dir,
@@ -3196,7 +3295,7 @@ static int apply_mount_namespace(
const ExecRuntime *runtime,
char **error_path) {
_cleanup_strv_free_ char **empty_directories = NULL;
_cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL;
const char *tmp_dir = NULL, *var_tmp_dir = NULL;
const char *root_dir = NULL, *root_image = NULL;
_cleanup_free_ char *creds_path = NULL, *incoming_dir = NULL, *propagate_dir = NULL;
@@ -3219,6 +3318,11 @@ static int apply_mount_namespace(
if (r < 0)
return r;
/* Symlinks for exec dirs are set up after other mounts, before they are made read-only. */
r = compile_symlinks(context, params, &symlinks);
if (r < 0)
return r;
needs_sandboxing = (params->flags & EXEC_APPLY_SANDBOXING) && !(command_flags & EXEC_COMMAND_FULLY_PRIVILEGED);
if (needs_sandboxing) {
/* The runtime struct only contains the parent of the private /tmp,
@@ -3302,6 +3406,7 @@ static int apply_mount_namespace(
needs_sandboxing ? context->exec_paths : NULL,
needs_sandboxing ? context->no_exec_paths : NULL,
empty_directories,
symlinks,
bind_mounts,
n_bind_mounts,
context->temporary_filesystems,
@@ -3636,21 +3741,19 @@ static int compile_suggested_paths(const ExecContext *c, const ExecParameters *p
* directories. */
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
char **i;
if (t == EXEC_DIRECTORY_CONFIGURATION)
continue;
if (!p->prefix[t])
continue;
STRV_FOREACH(i, c->directories[t].paths) {
for (size_t i = 0; i < c->directories[t].n_items; i++) {
char *e;
if (exec_directory_is_private(c, t))
e = path_join(p->prefix[t], "private", *i);
e = path_join(p->prefix[t], "private", c->directories[t].items[i].path);
else
e = path_join(p->prefix[t], *i);
e = path_join(p->prefix[t], c->directories[t].items[i].path);
if (!e)
return -ENOMEM;
@@ -4170,8 +4273,10 @@ static int exec_child(
}
}
needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime);
for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) {
r = setup_exec_directory(context, params, uid, gid, dt, exit_status);
r = setup_exec_directory(context, params, uid, gid, dt, needs_mount_namespace, exit_status);
if (r < 0)
return log_unit_error_errno(unit, r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]);
}
@@ -4358,7 +4463,6 @@ static int exec_child(
log_unit_warning(unit, "PrivateIPC=yes is configured, but the kernel does not support IPC namespaces, ignoring.");
}
needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime);
if (needs_mount_namespace) {
_cleanup_free_ char *error_path = NULL;
@@ -5031,7 +5135,7 @@ void exec_context_done(ExecContext *c) {
c->address_families = set_free(c->address_families);
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++)
c->directories[t].paths = strv_free(c->directories[t].paths);
exec_directory_done(&c->directories[t]);
c->log_level_max = -1;
@@ -5053,26 +5157,39 @@ void exec_context_done(ExecContext *c) {
}
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {
char **i;
assert(c);
if (!runtime_prefix)
return 0;
STRV_FOREACH(i, c->directories[EXEC_DIRECTORY_RUNTIME].paths) {
for (size_t i = 0; i < c->directories[EXEC_DIRECTORY_RUNTIME].n_items; i++) {
_cleanup_free_ char *p = NULL;
if (exec_directory_is_private(c, EXEC_DIRECTORY_RUNTIME))
p = path_join(runtime_prefix, "private", *i);
p = path_join(runtime_prefix, "private", c->directories[EXEC_DIRECTORY_RUNTIME].items[i].path);
else
p = path_join(runtime_prefix, *i);
p = path_join(runtime_prefix, c->directories[EXEC_DIRECTORY_RUNTIME].items[i].path);
if (!p)
return -ENOMEM;
/* We execute this synchronously, since we need to be sure this is gone when we start the
* service next. */
(void) rm_rf(p, REMOVE_ROOT);
char **symlink;
STRV_FOREACH(symlink, c->directories[EXEC_DIRECTORY_RUNTIME].items[i].symlinks) {
_cleanup_free_ char *symlink_abs = NULL;
if (exec_directory_is_private(c, EXEC_DIRECTORY_RUNTIME))
symlink_abs = path_join(runtime_prefix, "private", *symlink);
else
symlink_abs = path_join(runtime_prefix, *symlink);
if (!symlink_abs)
return -ENOMEM;
(void) unlink(symlink_abs);
}
}
return 0;
@@ -5484,8 +5601,12 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) {
fprintf(f, "%s%sMode: %04o\n", prefix, exec_directory_type_to_string(dt), c->directories[dt].mode);
STRV_FOREACH(d, c->directories[dt].paths)
fprintf(f, "%s%s: %s\n", prefix, exec_directory_type_to_string(dt), *d);
for (size_t i = 0; i < c->directories[dt].n_items; i++) {
fprintf(f, "%s%s: %s\n", prefix, exec_directory_type_to_string(dt), c->directories[dt].items[i].path);
STRV_FOREACH(d, c->directories[dt].items[i].symlinks)
fprintf(f, "%s%s: %s:%s\n", prefix, exec_directory_type_symlink_to_string(dt), c->directories[dt].items[i].path, *d);
}
}
fprintf(f, "%sTimeoutCleanSec: %s\n", prefix, FORMAT_TIMESPAN(c->timeout_clean_usec, USEC_PER_SEC));
@@ -5959,18 +6080,16 @@ int exec_context_get_clean_directories(
assert(ret);
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
char **i;
if (!FLAGS_SET(mask, 1U << t))
continue;
if (!prefix[t])
continue;
STRV_FOREACH(i, c->directories[t].paths) {
for (size_t i = 0; i < c->directories[t].n_items; i++) {
char *j;
j = path_join(prefix[t], *i);
j = path_join(prefix[t], c->directories[t].items[i].path);
if (!j)
return -ENOMEM;
@@ -5980,7 +6099,18 @@ int exec_context_get_clean_directories(
/* Also remove private directories unconditionally. */
if (t != EXEC_DIRECTORY_CONFIGURATION) {
j = path_join(prefix[t], "private", *i);
j = path_join(prefix[t], "private", c->directories[t].items[i].path);
if (!j)
return -ENOMEM;
r = strv_consume(&l, j);
if (r < 0)
return r;
}
char **symlink;
STRV_FOREACH(symlink, c->directories[t].items[i].symlinks) {
j = path_join(prefix[t], *symlink);
if (!j)
return -ENOMEM;
@@ -6002,7 +6132,7 @@ int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret) {
assert(ret);
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++)
if (!strv_isempty(c->directories[t].paths))
if (c->directories[t].n_items > 0)
mask |= 1U << t;
*ret = mask;
@@ -6680,6 +6810,49 @@ ExecLoadCredential *exec_load_credential_free(ExecLoadCredential *lc) {
return mfree(lc);
}
void exec_directory_done(ExecDirectory *d) {
if (!d)
return;
for (size_t i = 0; i < d->n_items; i++) {
free(d->items[i].path);
strv_free(d->items[i].symlinks);
}
d->items = mfree(d->items);
d->n_items = 0;
d->mode = 0755;
}
int exec_directory_add(ExecDirectoryItem **d, size_t *n, const char *path, char **symlinks) {
_cleanup_strv_free_ char **s = NULL;
_cleanup_free_ char *p = NULL;
assert(d);
assert(n);
assert(path);
p = strdup(path);
if (!p)
return -ENOMEM;
if (symlinks) {
s = strv_copy(symlinks);
if (!s)
return -ENOMEM;
}
if (!GREEDY_REALLOC(*d, *n + 1))
return -ENOMEM;
(*d)[(*n) ++] = (ExecDirectoryItem) {
.path = TAKE_PTR(p),
.symlinks = TAKE_PTR(s),
};
return 0;
}
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, ExecSetCredential, exec_set_credential_free);
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_load_credential_hash_ops, char, string_hash_func, string_compare_func, ExecLoadCredential, exec_load_credential_free);
@@ -6740,6 +6913,17 @@ static const char* const exec_directory_type_table[_EXEC_DIRECTORY_TYPE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(exec_directory_type, ExecDirectoryType);
/* This table maps ExecDirectoryType to the symlink setting it is configured with in the unit */
static const char* const exec_directory_type_symlink_table[_EXEC_DIRECTORY_TYPE_MAX] = {
[EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectorySymlink",
[EXEC_DIRECTORY_STATE] = "StateDirectorySymlink",
[EXEC_DIRECTORY_CACHE] = "CacheDirectorySymlink",
[EXEC_DIRECTORY_LOGS] = "LogsDirectorySymlink",
[EXEC_DIRECTORY_CONFIGURATION] = "ConfigurationDirectorySymlink",
};
DEFINE_STRING_TABLE_LOOKUP(exec_directory_type_symlink, ExecDirectoryType);
/* And this table maps ExecDirectoryType too, but to a generic term identifying the type of resource. This
* one is supposed to be generic enough to be used for unit types that don't use ExecContext and per-unit
* directories, specifically .timer units with their timestamp touch file. */