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
+153 -9
View File
@@ -1091,6 +1091,70 @@ static int property_get_extension_images(
return sd_bus_message_close_container(reply);
}
static int bus_property_get_exec_dir(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
ExecDirectory *d = userdata;
int r;
assert(bus);
assert(d);
assert(property);
assert(reply);
r = sd_bus_message_open_container(reply, 'a', "s");
if (r < 0)
return r;
for (size_t i = 0; i < d->n_items; i++) {
r = sd_bus_message_append_basic(reply, 's', d->items[i].path);
if (r < 0)
return r;
}
return sd_bus_message_close_container(reply);
}
static int bus_property_get_exec_dir_symlink(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
ExecDirectory *d = userdata;
int r;
assert(bus);
assert(d);
assert(property);
assert(reply);
r = sd_bus_message_open_container(reply, 'a', "(sst)");
if (r < 0)
return r;
for (size_t i = 0; i < d->n_items; i++) {
char **dst;
STRV_FOREACH(dst, d->items[i].symlinks) {
r = sd_bus_message_append(reply, "(sst)", d->items[i].path, *dst, 0 /* flags, unused for now */);
if (r < 0)
return r;
}
}
return sd_bus_message_close_container(reply);
}
const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1224,17 +1288,21 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("Personality", "s", property_get_personality, offsetof(ExecContext, personality), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LockPersonality", "b", bus_property_get_bool, offsetof(ExecContext, lock_personality), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectoryPreserve", "s", property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME].mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectory", "as", NULL, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME].paths), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StateDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StateDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE].mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StateDirectory", "as", NULL, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE].paths), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StateDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CacheDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CacheDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE].mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CacheDirectory", "as", NULL, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE].paths), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CacheDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LogsDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LogsDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS].mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LogsDirectory", "as", NULL, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS].paths), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LogsDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ConfigurationDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION].mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ConfigurationDirectory", "as", NULL, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION].paths), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ConfigurationDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TimeoutCleanUSec", "t", bus_property_get_usec, offsetof(ExecContext, timeout_clean_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MemoryDenyWriteExecute", "b", bus_property_get_bool, offsetof(ExecContext, memory_deny_write_execute), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictRealtime", "b", bus_property_get_bool, offsetof(ExecContext, restrict_realtime), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -3294,14 +3362,17 @@ int bus_exec_context_set_transient_property(
d = c->directories + i;
if (strv_isempty(l)) {
d->paths = strv_free(d->paths);
exec_directory_done(d);
unit_write_settingf(u, flags, name, "%s=", name);
} else {
_cleanup_free_ char *joined = NULL;
char **source;
r = strv_extend_strv(&d->paths, l, true);
if (r < 0)
return r;
STRV_FOREACH(source, l) {
r = exec_directory_add(&d->items, &d->n_items, *source, NULL);
if (r < 0)
return log_oom();
}
joined = unit_concat_strv(l, UNIT_ESCAPE_SPECIFIERS);
if (!joined)
@@ -3711,6 +3782,79 @@ int bus_exec_context_set_transient_property(
extension_images = mount_image_free_many(extension_images, &n_extension_images);
return 1;
} else if (STR_IN_SET(name, "StateDirectorySymlink", "RuntimeDirectorySymlink", "CacheDirectorySymlink", "LogsDirectorySymlink")) {
char *source, *destination;
ExecDirectory *directory;
uint64_t symlink_flags; /* No flags for now, reserved for future uses. */
ExecDirectoryType i;
assert_se((i = exec_directory_type_symlink_from_string(name)) >= 0);
directory = c->directories + i;
r = sd_bus_message_enter_container(message, 'a', "(sst)");
if (r < 0)
return r;
while ((r = sd_bus_message_read(message, "(sst)", &source, &destination, &symlink_flags)) > 0) {
if (!path_is_valid(source))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not valid.", source);
if (path_is_absolute(source))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is absolute.", source);
if (!path_is_normalized(source))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not normalized.", source);
if (!path_is_valid(destination))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not valid.", destination);
if (path_is_absolute(destination))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is absolute.", destination);
if (!path_is_normalized(destination))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not normalized.", destination);
if (symlink_flags != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be zero.");
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
_cleanup_free_ char *destination_escaped = NULL, *source_escaped = NULL;
ExecDirectoryItem *item = NULL;
/* Adding new directories is supported from both *DirectorySymlink methods and the
* older ones, so try to find an existing configuration first and create it if it's
* not there yet. */
for (size_t j = 0; j < directory->n_items; ++j)
if (path_equal(source, directory->items[j].path)) {
item = &directory->items[j];
break;
}
if (item)
r = strv_extend(&item->symlinks, destination);
else
r = exec_directory_add(&directory->items, &directory->n_items, source, STRV_MAKE(destination));
if (r < 0)
return r;
/* Need to store them in the unit with the escapes, so that they can be parsed again */
source_escaped = xescape(source, ":");
destination_escaped = xescape(destination, ":");
if (!source_escaped || !destination_escaped)
return -ENOMEM;
unit_write_settingf(
u, flags|UNIT_ESCAPE_SPECIFIERS, exec_directory_type_to_string(i),
"%s=%s:%s",
exec_directory_type_to_string(i),
source_escaped,
destination_escaped);
}
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
return 1;
}
return 0;
+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. */
+13 -1
View File
@@ -132,9 +132,15 @@ typedef enum ExecDirectoryType {
_EXEC_DIRECTORY_TYPE_INVALID = -EINVAL,
} ExecDirectoryType;
typedef struct ExecDirectoryItem {
char *path;
char **symlinks;
} ExecDirectoryItem;
typedef struct ExecDirectory {
char **paths;
mode_t mode;
size_t n_items;
ExecDirectoryItem *items;
} ExecDirectory;
typedef enum ExecCleanMask {
@@ -479,6 +485,9 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSetCredential*, exec_set_credential_free);
ExecLoadCredential *exec_load_credential_free(ExecLoadCredential *lc);
DEFINE_TRIVIAL_CLEANUP_FUNC(ExecLoadCredential*, exec_load_credential_free);
void exec_directory_done(ExecDirectory *d);
int exec_directory_add(ExecDirectoryItem **d, size_t *n, const char *path, char **symlinks);
extern const struct hash_ops exec_set_credential_hash_ops;
extern const struct hash_ops exec_load_credential_hash_ops;
@@ -500,6 +509,9 @@ ExecKeyringMode exec_keyring_mode_from_string(const char *s) _pure_;
const char* exec_directory_type_to_string(ExecDirectoryType i) _const_;
ExecDirectoryType exec_directory_type_from_string(const char *s) _pure_;
const char* exec_directory_type_symlink_to_string(ExecDirectoryType i) _const_;
ExecDirectoryType exec_directory_type_symlink_from_string(const char *s) _pure_;
const char* exec_resource_type_to_string(ExecDirectoryType i) _const_;
ExecDirectoryType exec_resource_type_from_string(const char *s) _pure_;
+5 -5
View File
@@ -131,15 +131,15 @@
{{type}}.Personality, config_parse_personality, 0, offsetof({{type}}, exec_context.personality)
{{type}}.RuntimeDirectoryPreserve, config_parse_runtime_preserve_mode, 0, offsetof({{type}}, exec_context.runtime_directory_preserve_mode)
{{type}}.RuntimeDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME].mode)
{{type}}.RuntimeDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME].paths)
{{type}}.RuntimeDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME])
{{type}}.StateDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE].mode)
{{type}}.StateDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE].paths)
{{type}}.StateDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE])
{{type}}.CacheDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CACHE].mode)
{{type}}.CacheDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CACHE].paths)
{{type}}.CacheDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CACHE])
{{type}}.LogsDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_LOGS].mode)
{{type}}.LogsDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_LOGS].paths)
{{type}}.LogsDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_LOGS])
{{type}}.ConfigurationDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].mode)
{{type}}.ConfigurationDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].paths)
{{type}}.ConfigurationDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION])
{{type}}.SetCredential, config_parse_set_credential, 0, offsetof({{type}}, exec_context)
{{type}}.SetCredentialEncrypted, config_parse_set_credential, 1, offsetof({{type}}, exec_context)
{{type}}.LoadCredential, config_parse_load_credential, 0, offsetof({{type}}, exec_context)
+51 -12
View File
@@ -4601,7 +4601,7 @@ int config_parse_exec_directories(
void *data,
void *userdata) {
char***rt = data;
ExecDirectory *ed = data;
const Unit *u = userdata;
int r;
@@ -4612,45 +4612,84 @@ int config_parse_exec_directories(
if (isempty(rvalue)) {
/* Empty assignment resets the list */
*rt = strv_free(*rt);
exec_directory_done(ed);
return 0;
}
for (const char *p = rvalue;;) {
_cleanup_free_ char *word = NULL, *k = NULL;
_cleanup_free_ char *tuple = NULL;
r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE);
r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Invalid syntax, ignoring: %s", rvalue);
"Invalid syntax %s=%s, ignoring: %m", lvalue, rvalue);
return 0;
}
if (r == 0)
return 0;
r = unit_path_printf(u, word, &k);
_cleanup_free_ char *src = NULL, *dest = NULL;
const char *q = tuple;
r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &src, &dest, NULL);
if (r == -ENOMEM)
return log_oom();
if (r <= 0) {
log_syntax(unit, LOG_WARNING, filename, line, r ?: SYNTHETIC_ERRNO(EINVAL),
"Invalid syntax in %s=, ignoring: %s", lvalue, tuple);
return 0;
}
_cleanup_free_ char *sresolved = NULL;
r = unit_path_printf(u, src, &sresolved);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to resolve unit specifiers in \"%s\", ignoring: %m", word);
"Failed to resolve unit specifiers in \"%s\", ignoring: %m", src);
continue;
}
r = path_simplify_and_warn(k, PATH_CHECK_RELATIVE, unit, filename, line, lvalue);
r = path_simplify_and_warn(sresolved, PATH_CHECK_RELATIVE, unit, filename, line, lvalue);
if (r < 0)
continue;
if (path_startswith(k, "private")) {
if (path_startswith(sresolved, "private")) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"%s= path can't be 'private', ignoring assignment: %s", lvalue, word);
"%s= path can't be 'private', ignoring assignment: %s", lvalue, tuple);
continue;
}
r = strv_push(rt, k);
/* For State and Runtime directories we support an optional destination parameter, which
* will be used to create a symlink to the source. */
_cleanup_strv_free_ char **symlinks = NULL;
if (!isempty(dest)) {
_cleanup_free_ char *dresolved = NULL;
if (streq(lvalue, "ConfigurationDirectory")) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Destination parameter is not supported for ConfigurationDirectory, ignoring: %s", tuple);
continue;
}
r = unit_path_printf(u, dest, &dresolved);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to resolve unit specifiers in \"%s\", ignoring: %m", dest);
continue;
}
r = path_simplify_and_warn(dresolved, PATH_CHECK_RELATIVE, unit, filename, line, lvalue);
if (r < 0)
continue;
r = strv_consume(&symlinks, TAKE_PTR(dresolved));
if (r < 0)
return log_oom();
}
r = exec_directory_add(&ed->items, &ed->n_items, sresolved, symlinks);
if (r < 0)
return log_oom();
k = NULL;
}
}
+35 -1
View File
@@ -1586,11 +1586,36 @@ static void normalize_mounts(const char *root_directory, MountEntry *mounts, siz
drop_nop(mounts, n_mounts);
}
static int create_symlinks_from_tuples(const char *root, char **strv_symlinks) {
char **src, **dst;
int r;
STRV_FOREACH_PAIR(src, dst, strv_symlinks) {
_cleanup_free_ char *src_abs = NULL, *dst_abs = NULL;
src_abs = path_join(root, *src);
dst_abs = path_join(root, *dst);
if (!src_abs || !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 apply_mounts(
const char *root,
const NamespaceInfo *ns_info,
MountEntry *mounts,
size_t *n_mounts,
char **exec_dir_symlinks,
char **error_path) {
_cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
@@ -1657,6 +1682,14 @@ static int apply_mounts(
normalize_mounts(root, mounts, n_mounts);
}
/* Now that all filesystems have been set up, but before the
* read-only switches are flipped, create the exec dirs symlinks.
* Note that when /var/lib is not empty/tmpfs, these symlinks will already
* exist, which means this will be a no-op. */
r = create_symlinks_from_tuples(root, exec_dir_symlinks);
if (r < 0)
return r;
/* Create a deny list we can pass to bind_mount_recursive() */
deny_list = new(char*, (*n_mounts)+1);
if (!deny_list)
@@ -1820,6 +1853,7 @@ int setup_namespace(
char** exec_paths,
char** no_exec_paths,
char** empty_directories,
char** exec_dir_symlinks,
const BindMount *bind_mounts,
size_t n_bind_mounts,
const TemporaryFileSystem *temporary_filesystems,
@@ -2261,7 +2295,7 @@ int setup_namespace(
(void) base_filesystem_create(root, UID_INVALID, GID_INVALID);
/* Now make the magic happen */
r = apply_mounts(root, ns_info, mounts, &n_mounts, error_path);
r = apply_mounts(root, ns_info, mounts, &n_mounts, exec_dir_symlinks, error_path);
if (r < 0)
goto finish;
+1
View File
@@ -121,6 +121,7 @@ int setup_namespace(
char **exec_paths,
char **no_exec_paths,
char **empty_directories,
char **exec_dir_symlinks,
const BindMount *bind_mounts,
size_t n_bind_mounts,
const TemporaryFileSystem *temporary_filesystems,
+5 -6
View File
@@ -1253,11 +1253,10 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
if (!u->manager->prefix[dt])
continue;
char **dp;
STRV_FOREACH(dp, c->directories[dt].paths) {
for (size_t i = 0; i < c->directories[dt].n_items; i++) {
_cleanup_free_ char *p = NULL;
p = path_join(u->manager->prefix[dt], *dp);
p = path_join(u->manager->prefix[dt], c->directories[dt].items[i].path);
if (!p)
return -ENOMEM;
@@ -1272,9 +1271,9 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
/* For the following three directory types we need write access, and /var/ is possibly on the root
* fs. Hence order after systemd-remount-fs.service, to ensure things are writable. */
if (!strv_isempty(c->directories[EXEC_DIRECTORY_STATE].paths) ||
!strv_isempty(c->directories[EXEC_DIRECTORY_CACHE].paths) ||
!strv_isempty(c->directories[EXEC_DIRECTORY_LOGS].paths)) {
if (c->directories[EXEC_DIRECTORY_STATE].n_items > 0 ||
c->directories[EXEC_DIRECTORY_CACHE].n_items > 0 ||
c->directories[EXEC_DIRECTORY_LOGS].n_items > 0) {
r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_REMOUNT_FS_SERVICE, true, UNIT_DEPENDENCY_FILE);
if (r < 0)
return r;
+120 -4
View File
@@ -23,6 +23,7 @@
#include "libmount-util.h"
#include "locale-util.h"
#include "log.h"
#include "macro.h"
#include "missing_fs.h"
#include "mountpoint-util.h"
#include "nsflags.h"
@@ -970,10 +971,6 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
"ExecPaths",
"NoExecPaths",
"ExecSearchPath",
"RuntimeDirectory",
"StateDirectory",
"CacheDirectory",
"LogsDirectory",
"ConfigurationDirectory",
"SupplementaryGroups",
"SystemCallArchitectures"))
@@ -1926,6 +1923,125 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
return 1;
}
if (STR_IN_SET(field, "StateDirectory", "RuntimeDirectory", "CacheDirectory", "LogsDirectory")) {
_cleanup_strv_free_ char **symlinks = NULL, **sources = NULL;
const char *p = eq;
/* Adding new directories is supported from both *DirectorySymlink methods and the
* older ones, so first parse the input, and if we are given a new-style src:dst
* tuple use the new method, else use the old one. */
for (;;) {
_cleanup_free_ char *tuple = NULL, *source = NULL, *destination = NULL;
r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE);
if (r < 0)
return log_error_errno(r, "Failed to parse argument: %m");
if (r == 0)
break;
const char *t = tuple;
r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL);
if (r <= 0)
return log_error_errno(r ?: SYNTHETIC_ERRNO(EINVAL), "Failed to parse argument: %m");
path_simplify(source);
if (isempty(destination)) {
r = strv_extend(&sources, TAKE_PTR(source));
if (r < 0)
return bus_log_create_error(r);
} else {
path_simplify(destination);
r = strv_consume_pair(&symlinks, TAKE_PTR(source), TAKE_PTR(destination));
if (r < 0)
return log_oom();
}
}
if (!strv_isempty(sources)) {
r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'v', "as");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, sources);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
}
/* For State and Runtime directories we support an optional destination parameter, which
* will be used to create a symlink to the source. But it is new so we cannot change the
* old DBUS signatures, so append a new message type. */
if (!strv_isempty(symlinks)) {
const char *symlink_field;
r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
if (r < 0)
return bus_log_create_error(r);
if (streq(field, "StateDirectory"))
symlink_field = "StateDirectorySymlink";
else if (streq(field, "RuntimeDirectory"))
symlink_field = "RuntimeDirectorySymlink";
else if (streq(field, "CacheDirectory"))
symlink_field = "CacheDirectorySymlink";
else if (streq(field, "LogsDirectory"))
symlink_field = "LogsDirectorySymlink";
else
assert_not_reached();
r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, symlink_field);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'v', "a(sst)");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'a', "(sst)");
if (r < 0)
return bus_log_create_error(r);
char **source, **destination;
STRV_FOREACH_PAIR(source, destination, symlinks) {
r = sd_bus_message_append(m, "(sst)", *source, *destination, 0);
if (r < 0)
return bus_log_create_error(r);
}
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
}
return 1;
}
return 0;
}
+18
View File
@@ -1773,6 +1773,24 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
if (r < 0)
return bus_log_parse_error(r);
return 1;
} else if (STR_IN_SET(name, "StateDirectorySymlink", "RuntimeDirectorySymlink", "CacheDirectorySymlink", "LogsDirectorySymlink")) {
const char *a, *p;
uint64_t symlink_flags;
r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sst)");
if (r < 0)
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(m, "(sst)", &a, &p, &symlink_flags)) > 0)
bus_print_property_valuef(name, expected_value, flags, "%s:%s", a, p);
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_exit_container(m);
if (r < 0)
return bus_log_parse_error(r);
return 1;
}
+1
View File
@@ -166,6 +166,7 @@ static void test_protect_kernel_logs(void) {
NULL,
NULL,
NULL,
NULL,
NULL, 0,
NULL, 0,
NULL, 0,
+1
View File
@@ -83,6 +83,7 @@ int main(int argc, char *argv[]) {
(char **) writable,
(char **) readonly,
(char **) inaccessible,
NULL,
(char **) exec,
(char **) no_exec,
NULL,