diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index f484f28a70..7ade8c3e8b 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -4847,8 +4847,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { CacheDirectorySymlink and LogsDirectorySymlink respectively implement the destination parameter of the unit files settings RuntimeDirectory, StateDirectory, CacheDirectory and LogsDirectory, - which will create a symlink of the given name to the respective directory. The messages take an unused - flags parameter, reserved for future backward-compatible changes. + which will create a symlink of the given name to the respective directory. The messages take a + flags parameter that make the directory read only: + + +#define SD_EXEC_DIRECTORY_READ_ONLY (UINT64_C(1) << 0) + ExtraFileDescriptorNames contains file descriptor names passed to the service via the ExtraFileDescriptors property in the StartTransientUnit() diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index f84204d247..d5f85ed85c 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -1561,12 +1561,18 @@ CapabilityBoundingSet=~CAP_B CAP_C configuration or lifetime guarantees, please consider using tmpfiles.d5. - RuntimeDirectory=, StateDirectory=, CacheDirectory= - and LogsDirectory= optionally support a second parameter, separated by :. - The second parameter will be interpreted as a destination path that will be created as a symlink to the directory. - The symlinks will be created after any BindPaths= or TemporaryFileSystem= - options have been set up, to make ephemeral symlinking possible. The same source can have multiple symlinks, by - using the same first parameter, but a different second parameter. + RuntimeDirectory=, StateDirectory=, + CacheDirectory= and LogsDirectory= optionally support two + more parameters, separated by :. The second parameter will be interpreted as a + destination path that will be created as a symlink to the directory. The symlinks will be created + after any BindPaths= or TemporaryFileSystem= options have been + set up, to make ephemeral symlinking possible. The same source can have multiple symlinks, by using + the same first parameter, but a different second parameter. The third parameter is a flags field, + and since v257 can take a value of ro to make the directory read only for the + service. This is also supported for ConfigurationDirectory=. If multiple symlinks + are set up, the directory will be read only if at least one is configured to be read only. To pass a + flag without a destination symlink, the second parameter can be empty, for example: + ConfigurationDirectory=foo::ro The directories defined by these options are always created under the standard paths used by systemd (/var/, /run/, /etc/, …). If the service needs diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 4f627a11e2..a9a73b599b 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -5,6 +5,7 @@ #include "af-list.h" #include "alloc-util.h" #include "bus-get-properties.h" +#include "bus-unit-util.h" #include "bus-util.h" #include "cap-list.h" #include "capability-util.h" @@ -981,11 +982,18 @@ static int property_get_exec_dir_symlink( return r; FOREACH_ARRAY(i, d->items, d->n_items) - STRV_FOREACH(dst, i->symlinks) { - r = sd_bus_message_append(reply, "(sst)", i->path, *dst, UINT64_C(0) /* flags, unused for now */); + if (strv_isempty(i->symlinks)) { + /* The old exec directory properties cannot represent flags, so list them here with no + * destination */ + r = sd_bus_message_append(reply, "(sst)", i->path, "", (uint64_t) (i->flags & _EXEC_DIRECTORY_FLAGS_PUBLIC)); if (r < 0) return r; - } + } else + STRV_FOREACH(dst, i->symlinks) { + r = sd_bus_message_append(reply, "(sst)", i->path, *dst, (uint64_t) (i->flags & _EXEC_DIRECTORY_FLAGS_PUBLIC)); + if (r < 0) + return r; + } return sd_bus_message_close_container(reply); } @@ -3444,7 +3452,7 @@ int bus_exec_context_set_transient_property( _cleanup_free_ char *joined = NULL; STRV_FOREACH(source, l) { - r = exec_directory_add(d, *source, NULL); + r = exec_directory_add(d, *source, /* symlink= */ NULL, /* flags= */ 0); if (r < 0) return log_oom(); } @@ -3862,7 +3870,7 @@ int bus_exec_context_set_transient_property( } 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. */ + uint64_t symlink_flags; ExecDirectoryType i; assert_se((i = exec_directory_type_symlink_from_string(name)) >= 0); @@ -3873,40 +3881,50 @@ int bus_exec_context_set_transient_property( return r; while ((r = sd_bus_message_read(message, "(sst)", &source, &destination, &symlink_flags)) > 0) { + if ((symlink_flags & ~_EXEC_DIRECTORY_FLAGS_PUBLIC) != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid 'flags' parameter '%" PRIu64 "'", symlink_flags); 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 (isempty(destination)) + destination = NULL; + else { + 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 (!UNIT_WRITE_FLAGS_NOOP(flags)) { _cleanup_free_ char *destination_escaped = NULL, *source_escaped = NULL; - r = exec_directory_add(directory, source, destination); + r = exec_directory_add(directory, source, destination, symlink_flags); 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) + if (!source_escaped) return -ENOMEM; + if (destination) { + destination_escaped = xescape(destination, ":"); + if (!destination_escaped) + return -ENOMEM; + } unit_write_settingf( u, flags|UNIT_ESCAPE_SPECIFIERS, exec_directory_type_to_string(i), - "%s=%s:%s", + "%s=%s%s%s%s", exec_directory_type_to_string(i), source_escaped, - destination_escaped); + destination_escaped || FLAGS_SET(symlink_flags, EXEC_DIRECTORY_READ_ONLY) ? ":" : "", + destination_escaped, + FLAGS_SET(symlink_flags, EXEC_DIRECTORY_READ_ONLY) ? ":ro" : ""); } } if (r < 0) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index feea7897ef..968e9bd7c4 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -2452,7 +2452,7 @@ static int setup_exec_directory( goto fail; } - if (!i->only_create) { + if (!FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE)) { /* And link it up from the original place. * Notes * 1) If a mount namespace is going to be used, then this symlink remains on @@ -2643,7 +2643,7 @@ static int compile_bind_mounts( continue; FOREACH_ARRAY(i, context->directories[t].items, context->directories[t].n_items) - n += !i->only_create; + n += !FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE) || FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY); } if (n <= 0) { @@ -2691,8 +2691,10 @@ static int compile_bind_mounts( _cleanup_free_ char *s = NULL, *d = NULL; /* When one of the parent directories is in the list, we cannot create the symlink - * for the child directory. See also the comments in setup_exec_directory(). */ - if (i->only_create) + * for the child directory. See also the comments in setup_exec_directory(). + * But if it needs to be read only, then we have to create a bind mount anyway to + * make it so. */ + if (FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE) && !FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY)) continue; if (exec_directory_is_private(context, t)) @@ -2718,6 +2720,7 @@ static int compile_bind_mounts( .destination = TAKE_PTR(d), .nosuid = context->dynamic_user, /* don't allow suid/sgid when DynamicUser= is on */ .recursive = true, + .read_only = FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY), }; } } @@ -2766,7 +2769,7 @@ static int compile_symlinks( if (!exec_directory_is_private(context, dt) || exec_context_with_rootfs(context) || - i->only_create) + FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE)) continue; private_path = path_join(params->prefix[dt], "private", i->path); diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 39369e22b8..6fa0b21968 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -1998,7 +1998,10 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) { if (!strextend(&value, " ", path_escaped)) return log_oom_debug(); - if (!strextend(&value, ":", yes_no(i->only_create))) + if (!strextend(&value, ":", yes_no(FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE)))) + return log_oom_debug(); + + if (!strextend(&value, ":", yes_no(FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY)))) return log_oom_debug(); STRV_FOREACH(d, i->symlinks) { @@ -2893,7 +2896,8 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { return r; for (;;) { - _cleanup_free_ char *tuple = NULL, *path = NULL, *only_create = NULL; + _cleanup_free_ char *tuple = NULL, *path = NULL, *only_create = NULL, *read_only = NULL; + ExecDirectoryFlags exec_directory_flags = 0; const char *p; /* Use EXTRACT_UNESCAPE_RELAX here, as we unescape the colons in subsequent calls */ @@ -2904,20 +2908,27 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { break; p = tuple; - r = extract_many_words(&p, ":", EXTRACT_UNESCAPE_SEPARATORS, &path, &only_create); + r = extract_many_words(&p, ":", EXTRACT_UNESCAPE_SEPARATORS, &path, &only_create, &read_only); if (r < 0) return r; if (r < 2) continue; - r = exec_directory_add(&c->directories[dt], path, NULL); - if (r < 0) - return r; - r = parse_boolean(only_create); if (r < 0) return r; - c->directories[dt].items[c->directories[dt].n_items - 1].only_create = r; + if (r > 0) + exec_directory_flags |= EXEC_DIRECTORY_ONLY_CREATE; + + r = parse_boolean(read_only); + if (r < 0) + return r; + if (r > 0) + exec_directory_flags |= EXEC_DIRECTORY_READ_ONLY; + + r = exec_directory_add(&c->directories[dt], path, /* symlink= */ NULL, exec_directory_flags); + if (r < 0) + return r; if (isempty(p)) continue; diff --git a/src/core/execute.c b/src/core/execute.c index a081c42938..1c41b39a2f 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -331,6 +331,11 @@ bool exec_needs_mount_namespace( if (exec_context_get_effective_bind_log_sockets(context)) return true; + for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) + FOREACH_ARRAY(i, context->directories[t].items, context->directories[t].n_items) + if (FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY)) + return true; + return false; } @@ -1118,7 +1123,12 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { fprintf(f, "%s%sMode: %04o\n", prefix, exec_directory_type_to_string(dt), c->directories[dt].mode); 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); + fprintf(f, + "%s%s: %s%s\n", + prefix, + exec_directory_type_to_string(dt), + c->directories[dt].items[i].path, + FLAGS_SET(c->directories[dt].items[i].flags, EXEC_DIRECTORY_READ_ONLY) ? " (ro)" : ""); 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); @@ -2731,7 +2741,7 @@ static ExecDirectoryItem *exec_directory_find(ExecDirectory *d, const char *path return NULL; } -int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink) { +int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink, ExecDirectoryFlags flags) { _cleanup_strv_free_ char **s = NULL; _cleanup_free_ char *p = NULL; ExecDirectoryItem *existing; @@ -2746,6 +2756,8 @@ int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink) if (r < 0) return r; + existing->flags |= flags; + return 0; /* existing item is updated */ } @@ -2765,6 +2777,7 @@ int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink) d->items[d->n_items++] = (ExecDirectoryItem) { .path = TAKE_PTR(p), .symlinks = TAKE_PTR(s), + .flags = flags, }; return 1; /* new item is added */ @@ -2782,7 +2795,7 @@ void exec_directory_sort(ExecDirectory *d) { /* Sort the exec directories to make always parent directories processed at first in * setup_exec_directory(), e.g., even if StateDirectory=foo/bar foo, we need to create foo at first, - * then foo/bar. Also, set .only_create flag if one of the parent directories is contained in the + * then foo/bar. Also, set the ONLY_CREATE flag if one of the parent directories is contained in the * list. See also comments in setup_exec_directory() and issue #24783. */ if (d->n_items <= 1) @@ -2793,7 +2806,7 @@ void exec_directory_sort(ExecDirectory *d) { for (size_t i = 1; i < d->n_items; i++) for (size_t j = 0; j < i; j++) if (path_startswith(d->items[i].path, d->items[j].path)) { - d->items[i].only_create = true; + d->items[i].flags |= EXEC_DIRECTORY_ONLY_CREATE; break; } } diff --git a/src/core/execute.h b/src/core/execute.h index 1fc7e6d785..2f5abc9785 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -15,6 +15,7 @@ typedef struct Manager Manager; #include #include +#include "bus-unit-util.h" #include "cgroup-util.h" #include "coredump-util.h" #include "cpu-set-util.h" @@ -161,7 +162,7 @@ static inline bool EXEC_DIRECTORY_TYPE_SHALL_CHOWN(ExecDirectoryType t) { typedef struct ExecDirectoryItem { char *path; char **symlinks; - bool only_create; + ExecDirectoryFlags flags; } ExecDirectoryItem; typedef struct ExecDirectory { @@ -585,7 +586,7 @@ void exec_params_deep_clear(ExecParameters *p); bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c); void exec_directory_done(ExecDirectory *d); -int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink); +int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink, ExecDirectoryFlags flags); void exec_directory_sort(ExecDirectory *d); bool exec_directory_is_private(const ExecContext *context, ExecDirectoryType type); diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index b059e515ef..1d813332b1 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -4698,9 +4698,9 @@ int config_parse_exec_directories( if (r == 0) return 0; - _cleanup_free_ char *src = NULL, *dest = NULL; + _cleanup_free_ char *src = NULL, *dest = NULL, *flags = NULL; const char *q = tuple; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &src, &dest); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_DONT_COALESCE_SEPARATORS, &src, &dest, &flags); if (r == -ENOMEM) return log_oom(); if (r <= 0) { @@ -4727,20 +4727,20 @@ int config_parse_exec_directories( continue; } + if (!isempty(dest) && streq(lvalue, "ConfigurationDirectory")) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Additional parameter is not supported for ConfigurationDirectory, ignoring: %s", tuple); + continue; + } + /* For State and Runtime directories we support an optional destination parameter, which * will be used to create a symlink to the source. */ _cleanup_free_ char *dresolved = NULL; if (!isempty(dest)) { - 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); + "Failed to resolve unit specifiers in \"%s\", ignoring: %m", dest); continue; } @@ -4749,7 +4749,14 @@ int config_parse_exec_directories( continue; } - r = exec_directory_add(ed, sresolved, dresolved); + ExecDirectoryFlags exec_directory_flags = exec_directory_flags_from_string(flags); + if (exec_directory_flags < 0 || (exec_directory_flags & ~_EXEC_DIRECTORY_FLAGS_PUBLIC) != 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid flags for %s=, ignoring: %s", lvalue, flags); + continue; + } + + r = exec_directory_add(ed, sresolved, dresolved, exec_directory_flags); if (r < 0) return log_oom(); } diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 2e17bae51a..90b6f233e2 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2127,7 +2127,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con } if (STR_IN_SET(field, "StateDirectory", "RuntimeDirectory", "CacheDirectory", "LogsDirectory")) { - _cleanup_strv_free_ char **symlinks = NULL, **sources = NULL; + _cleanup_strv_free_ char **symlinks = NULL, **symlinks_ro = NULL, **sources = NULL, **sources_ro = NULL; const char *p = eq; /* Adding new directories is supported from both *DirectorySymlink methods and the @@ -2135,7 +2135,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con * tuple use the new method, else use the old one. */ for (;;) { - _cleanup_free_ char *tuple = NULL, *source = NULL, *destination = NULL; + _cleanup_free_ char *tuple = NULL, *source = NULL, *dest = NULL, *flags = NULL; r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE); if (r < 0) @@ -2144,20 +2144,31 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con break; const char *t = tuple; - r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination); + r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &dest, &flags); if (r <= 0) return log_error_errno(r ?: SYNTHETIC_ERRNO(EINVAL), "Failed to parse argument: %m"); path_simplify(source); - if (isempty(destination)) { + if (isempty(dest) && isempty(flags)) { r = strv_consume(&sources, TAKE_PTR(source)); if (r < 0) return bus_log_create_error(r); + } else if (isempty(flags)) { + path_simplify(dest); + r = strv_consume_pair(&symlinks, TAKE_PTR(source), TAKE_PTR(dest)); + if (r < 0) + return log_oom(); } else { - path_simplify(destination); + ExecDirectoryFlags exec_directory_flags = exec_directory_flags_from_string(flags); + if (exec_directory_flags < 0 || (exec_directory_flags & ~_EXEC_DIRECTORY_FLAGS_PUBLIC) != 0) + return log_error_errno(r, "Failed to parse flags: %s", flags); - r = strv_consume_pair(&symlinks, TAKE_PTR(source), TAKE_PTR(destination)); + if (!isempty(dest)) { + path_simplify(dest); + r = strv_consume_pair(&symlinks_ro, TAKE_PTR(source), TAKE_PTR(dest)); + } else + r = strv_consume(&sources_ro, TAKE_PTR(source)); if (r < 0) return log_oom(); } @@ -2192,7 +2203,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con /* 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)) { + if (!strv_isempty(symlinks) || !strv_isempty(symlinks_ro) || !strv_isempty(sources_ro)) { const char *symlink_field; r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); @@ -2228,6 +2239,18 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return bus_log_create_error(r); } + STRV_FOREACH_PAIR(source, destination, symlinks_ro) { + r = sd_bus_message_append(m, "(sst)", *source, *destination, (uint64_t) EXEC_DIRECTORY_READ_ONLY); + if (r < 0) + return bus_log_create_error(r); + } + + STRV_FOREACH(source, sources_ro) { + r = sd_bus_message_append(m, "(sst)", *source, "", (uint64_t) EXEC_DIRECTORY_READ_ONLY); + 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); @@ -3078,3 +3101,13 @@ int unit_freezer_freeze(UnitFreezer *f) { int unit_freezer_thaw(UnitFreezer *f) { return unit_freezer_action(f, false); } + +ExecDirectoryFlags exec_directory_flags_from_string(const char *s) { + if (isempty(s)) + return 0; + + if (streq(s, "ro")) + return EXEC_DIRECTORY_READ_ONLY; + + return _EXEC_DIRECTORY_FLAGS_INVALID; +} diff --git a/src/shared/bus-unit-util.h b/src/shared/bus-unit-util.h index 6f0d55971c..8090b36e3d 100644 --- a/src/shared/bus-unit-util.h +++ b/src/shared/bus-unit-util.h @@ -7,6 +7,16 @@ #include "pidref.h" #include "unit-def.h" +typedef enum ExecDirectoryFlags { + EXEC_DIRECTORY_READ_ONLY = 1 << 0, /* Public API via DBUS, do not change */ + EXEC_DIRECTORY_ONLY_CREATE = 1 << 1, /* Only the private directory will be created, not the symlink to it */ + _EXEC_DIRECTORY_FLAGS_MAX, + _EXEC_DIRECTORY_FLAGS_PUBLIC = EXEC_DIRECTORY_READ_ONLY, + _EXEC_DIRECTORY_FLAGS_INVALID = -EINVAL, +} ExecDirectoryFlags; + +ExecDirectoryFlags exec_directory_flags_from_string(const char *s) _pure_; + typedef struct UnitInfo { const char *machine; const char *id; diff --git a/test/units/TEST-23-UNIT-FILE.statedir.sh b/test/units/TEST-23-UNIT-FILE.statedir.sh index b592314a09..cc30f0a27d 100755 --- a/test/units/TEST-23-UNIT-FILE.statedir.sh +++ b/test/units/TEST-23-UNIT-FILE.statedir.sh @@ -56,5 +56,10 @@ test "$(readlink "$HOME"/.local/state/foo)" = ../../.config/foo # Check that this will work safely a second time systemd-run --user -p StateDirectory=foo -p ConfigurationDirectory=foo --wait /bin/true +( ! systemd-run --user -p StateDirectory=foo::ro --wait sh -c "echo foo > $HOME/.local/state/foo/baz") +( ! systemd-run --user -p StateDirectory=foo:bar:ro --wait sh -c "echo foo > $HOME/.local/state/foo/baz") +( ! test -f "$HOME"/.local/state/foo/baz) +test -L "$HOME"/.local/state/bar + rm "$HOME"/.local/state/foo rmdir "$HOME"/.config/foo diff --git a/test/units/TEST-34-DYNAMICUSERMIGRATE.sh b/test/units/TEST-34-DYNAMICUSERMIGRATE.sh index d15b675e26..d6bdfb0741 100755 --- a/test/units/TEST-34-DYNAMICUSERMIGRATE.sh +++ b/test/units/TEST-34-DYNAMICUSERMIGRATE.sh @@ -23,6 +23,9 @@ test_directory() { systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test" systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing) + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" test -d "${path}"/www + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" test -L "${path}"/ro + (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" sh -c "echo foo > ${path}/www/test-missing") test -d "${path}"/zzz test ! -L "${path}"/zzz @@ -47,6 +50,9 @@ test_directory() { -p TemporaryFileSystem="${path}" -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test" systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing) + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"="www::ro www:ro:ro" test -d "${path}"/www + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"="www::ro www:ro:ro" test -L "${path}"/ro + (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"="www::ro www:ro:ro" sh -c "echo foo > ${path}/www/test-missing") test -L "${path}"/zzz test -d "${path}"/private/zzz @@ -70,6 +76,9 @@ test_directory() { systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test" systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing) + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" test -d "${path}"/www + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" test -L "${path}"/ro + (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" sh -c "echo foo > ${path}/www/test-missing") test -d "${path}"/zzz test ! -L "${path}"/zzz @@ -84,6 +93,8 @@ test_directory() { test -f "${path}"/zzz/test test ! -e "${path}"/zzz/test-missing + test -d "${path}"/www + test ! -e "${path}"/www/test-missing # Exercise the unit parsing paths too cat >/run/systemd/system/testservice-34.service <