From 2ac0841d6fcb577cb485140d70f5b5355a45b85a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 30 May 2024 04:23:16 +0200 Subject: Merging upstream version 252.26. Signed-off-by: Daniel Baumann --- .semaphore/semaphore-runner.sh | 3 +- man/systemd-run.xml | 21 +++++--- src/basic/linux/netfilter.h | 76 +++++++++++++++++++++++++++ src/core/execute.c | 28 +++++----- src/core/import-creds.c | 2 +- src/import/curl-util.c | 7 +++ src/libsystemd-network/icmp6-util.c | 4 +- src/libsystemd-network/test-dhcp-client.c | 2 +- src/libsystemd-network/test-dhcp-server.c | 4 +- src/libsystemd-network/test-dhcp6-client.c | 2 +- src/libsystemd-network/test-ndisc-ra.c | 2 +- src/machine/image-dbus.c | 79 +++++++++++++++++++---------- src/machine/image-dbus.h | 2 + src/machine/machined-dbus.c | 5 +- src/run/run.c | 25 ++++++--- src/shared/blockdev-util.c | 60 +++++++++++++++++----- src/shared/conf-parser.c | 6 ++- src/shared/discover-image.c | 62 +++++++++++++++++----- src/shared/journal-importer.c | 14 ++++- src/shared/libcrypt-util.c | 2 +- src/shared/ptyfwd.c | 3 ++ src/test/test-namespace.c | 34 +++++++++---- src/tmpfiles/tmpfiles.c | 2 +- test/networkd-test.py | 15 +++--- test/test-functions | 1 + test/test-network/systemd-networkd-tests.py | 60 ++++++++++++++++------ test/test-rpm-macros.sh | 2 +- test/units/testsuite-38.sh | 2 +- test/units/testsuite-46.sh | 2 +- test/units/testsuite-58.sh | 2 +- test/units/testsuite-64.sh | 7 +-- tmpfiles.d/systemd.conf.in | 9 ++-- 32 files changed, 410 insertions(+), 135 deletions(-) create mode 100644 src/basic/linux/netfilter.h diff --git a/.semaphore/semaphore-runner.sh b/.semaphore/semaphore-runner.sh index eb10ee6..4a37ee2 100755 --- a/.semaphore/semaphore-runner.sh +++ b/.semaphore/semaphore-runner.sh @@ -7,6 +7,7 @@ set -o pipefail # default to Debian testing DISTRO="${DISTRO:-debian}" RELEASE="${RELEASE:-bookworm}" +SALSA_URL="${SALSA_URL:-https://salsa.debian.org/systemd-team/systemd.git}" BRANCH="${BRANCH:-upstream-ci}" ARCH="${ARCH:-amd64}" CONTAINER="${RELEASE}-${ARCH}" @@ -69,7 +70,7 @@ for phase in "${PHASES[@]}"; do ;; RUN) # add current debian/ packaging - git fetch --depth=1 https://salsa.debian.org/systemd-team/systemd.git "$BRANCH" + git fetch --depth=1 "$SALSA_URL" "$BRANCH" git checkout FETCH_HEAD debian # craft changelog diff --git a/man/systemd-run.xml b/man/systemd-run.xml index 0c91d61..d57eda1 100644 --- a/man/systemd-run.xml +++ b/man/systemd-run.xml @@ -64,8 +64,8 @@ other service, and thus shows up in the output of systemctl list-units like any other unit. It will run in a clean and detached execution environment, with the service manager as its parent process. In this mode, systemd-run will start the service asynchronously in the background and return after the - command has begun execution (unless or are specified, see - below). + command has begun execution (unless , , , + or are specified, see below). If a command is run as transient scope unit, it will be executed by systemd-run itself as parent process and will thus inherit the execution environment of the caller. However, the @@ -262,6 +262,11 @@ to the terminal systemd-run is invoked on, via a pseudo TTY device. This allows running programs that expect interactive user input/output as services, such as interactive command shells. + This option will result in systemd-run synchronously waiting for + the transient service to terminate, similar to specifying . If specified + along with , systemd-run won't exit when manually disconnecting + from the pseudo TTY device. + Note that machinectl1's shell command is usually a better alternative for requesting a new, interactive login @@ -276,15 +281,19 @@ If specified, standard input, output, and error of the transient service are inherited from the systemd-run command itself. This allows systemd-run - to be used within shell pipelines. - Note that this mode is not suitable for interactive command shells and similar, as the - service process will not become a TTY controller when invoked on a terminal. Use instead - in that case. + to be used within shell pipelines. + + Note that this mode is not suitable for interactive command shells and similar, as the + service process will not become a TTY controller when invoked on a terminal. Use + instead in that case. When both and are used in combination the more appropriate option is automatically determined and used. Specifically, when invoked with standard input, output and error connected to a TTY is used, and otherwise . + This option will result in systemd-run synchronously waiting for + the transient service to terminate, similar to specifying . + When this option is used the original file descriptors systemd-run receives are passed to the service processes as-is. If the service runs with different privileges than systemd-run, this means the service might not be able to re-open the passed file diff --git a/src/basic/linux/netfilter.h b/src/basic/linux/netfilter.h new file mode 100644 index 0000000..30c045b --- /dev/null +++ b/src/basic/linux/netfilter.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_NETFILTER_H +#define __LINUX_NETFILTER_H + +#include + +#include +#include + +/* Responses from hook functions. */ +#define NF_DROP 0 +#define NF_ACCEPT 1 +#define NF_STOLEN 2 +#define NF_QUEUE 3 +#define NF_REPEAT 4 +#define NF_STOP 5 /* Deprecated, for userspace nf_queue compatibility. */ +#define NF_MAX_VERDICT NF_STOP + +/* we overload the higher bits for encoding auxiliary data such as the queue + * number or errno values. Not nice, but better than additional function + * arguments. */ +#define NF_VERDICT_MASK 0x000000ff + +/* extra verdict flags have mask 0x0000ff00 */ +#define NF_VERDICT_FLAG_QUEUE_BYPASS 0x00008000 + +/* queue number (NF_QUEUE) or errno (NF_DROP) */ +#define NF_VERDICT_QMASK 0xffff0000 +#define NF_VERDICT_QBITS 16 + +#define NF_QUEUE_NR(x) ((((x) << 16) & NF_VERDICT_QMASK) | NF_QUEUE) + +#define NF_DROP_ERR(x) (((-x) << 16) | NF_DROP) + +/* only for userspace compatibility */ + +/* NF_VERDICT_BITS should be 8 now, but userspace might break if this changes */ +#define NF_VERDICT_BITS 16 + +enum nf_inet_hooks { + NF_INET_PRE_ROUTING, + NF_INET_LOCAL_IN, + NF_INET_FORWARD, + NF_INET_LOCAL_OUT, + NF_INET_POST_ROUTING, + NF_INET_NUMHOOKS, + NF_INET_INGRESS = NF_INET_NUMHOOKS, +}; + +enum nf_dev_hooks { + NF_NETDEV_INGRESS, + NF_NETDEV_EGRESS, + NF_NETDEV_NUMHOOKS +}; + +enum { + NFPROTO_UNSPEC = 0, + NFPROTO_INET = 1, + NFPROTO_IPV4 = 2, + NFPROTO_ARP = 3, + NFPROTO_NETDEV = 5, + NFPROTO_BRIDGE = 7, + NFPROTO_IPV6 = 10, + NFPROTO_DECNET = 12, + NFPROTO_NUMPROTO, +}; + +union nf_inet_addr { + __u32 all[4]; + __be32 ip; + __be32 ip6[4]; + struct in_addr in; + struct in6_addr in6; +}; + +#endif /* __LINUX_NETFILTER_H */ diff --git a/src/core/execute.c b/src/core/execute.c index 2c1dda1..fc3d2ce 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -4710,12 +4710,14 @@ static int exec_child( if (ns_type_supported(NAMESPACE_NET)) { r = setup_shareable_ns(runtime->netns_storage_socket, CLONE_NEWNET); - if (r == -EPERM) - log_unit_warning_errno(unit, r, - "PrivateNetwork=yes is configured, but network namespace setup failed, ignoring: %m"); - else if (r < 0) { - *exit_status = EXIT_NETWORK; - return log_unit_error_errno(unit, r, "Failed to set up network namespacing: %m"); + if (r < 0) { + if (ERRNO_IS_PRIVILEGE(r)) + log_unit_warning_errno(unit, r, + "PrivateNetwork=yes is configured, but network namespace setup failed, ignoring: %m"); + else { + *exit_status = EXIT_NETWORK; + return log_unit_error_errno(unit, r, "Failed to set up network namespacing: %m"); + } } } else if (context->network_namespace_path) { *exit_status = EXIT_NETWORK; @@ -4729,12 +4731,14 @@ static int exec_child( if (ns_type_supported(NAMESPACE_IPC)) { r = setup_shareable_ns(runtime->ipcns_storage_socket, CLONE_NEWIPC); - if (r == -EPERM) - log_unit_warning_errno(unit, r, - "PrivateIPC=yes is configured, but IPC namespace setup failed, ignoring: %m"); - else if (r < 0) { - *exit_status = EXIT_NAMESPACE; - return log_unit_error_errno(unit, r, "Failed to set up IPC namespacing: %m"); + if (r < 0) { + if (ERRNO_IS_PRIVILEGE(r)) + log_unit_warning_errno(unit, r, + "PrivateIPC=yes is configured, but IPC namespace setup failed, ignoring: %m"); + else { + *exit_status = EXIT_NAMESPACE; + return log_unit_error_errno(unit, r, "Failed to set up IPC namespacing: %m"); + } } } else if (context->ipc_namespace_path) { *exit_status = EXIT_NAMESPACE; diff --git a/src/core/import-creds.c b/src/core/import-creds.c index dab7d36..91922ab 100644 --- a/src/core/import-creds.c +++ b/src/core/import-creds.c @@ -512,7 +512,7 @@ static int parse_smbios_strings(ImportCredentialContext *c, const char *data, si return log_oom(); if (!credential_name_valid(cn)) { - log_warning("SMBIOS credential name '%s' is not valid, ignoring: %m", cn); + log_warning("SMBIOS credential name '%s' is not valid, ignoring.", cn); continue; } diff --git a/src/import/curl-util.c b/src/import/curl-util.c index 94f718d..b631f4b 100644 --- a/src/import/curl-util.c +++ b/src/import/curl-util.c @@ -126,6 +126,13 @@ static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata assert(curl); + /* Don't configure timer anymore when the event loop is dead already. */ + if (g->timer) { + sd_event *event_loop = sd_event_source_get_event(g->timer); + if (event_loop && sd_event_get_state(event_loop) == SD_EVENT_FINISHED) + return 0; + } + if (timeout_ms < 0) { if (g->timer) { if (sd_event_source_set_enabled(g->timer, SD_EVENT_OFF) < 0) diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index d3ee7c4..8ea09fd 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -154,7 +154,7 @@ int icmp6_receive( /* This needs to be initialized with zero. See #20741. */ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int)) + /* ttl */ CMSG_SPACE_TIMEVAL) control = {}; - struct iovec iov = {}; + struct iovec iov = { buffer, size }; union sockaddr_union sa = {}; struct msghdr msg = { .msg_name = &sa.sa, @@ -169,8 +169,6 @@ int icmp6_receive( triple_timestamp t = {}; ssize_t len; - iov = IOVEC_MAKE(buffer, size); - len = recvmsg_safe(fd, &msg, MSG_DONTWAIT); if (len < 0) return (int) len; diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 787dcf1..bf17e51 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -513,7 +513,7 @@ static void test_addr_acq(sd_event *e) { callback_recv = test_addr_acq_recv_discover; assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, - 2 * USEC_PER_SEC, 0, + 30 * USEC_PER_SEC, 0, NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0); res = sd_dhcp_client_start(client); diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index f8a4c2c..763ecb9 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -62,7 +62,9 @@ static int test_basic(bool bind_to_interface) { test_pool(&address_lo, 1, 0); r = sd_dhcp_server_start(server); - if (r == -EPERM) + /* skip test if running in an environment with no full networking support, CONFIG_PACKET not + * compiled in kernel, nor af_packet module available. */ + if (r == -EPERM || r == -EAFNOSUPPORT) return r; assert_se(r >= 0); diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index 10233f5..a3a22e9 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -1003,7 +1003,7 @@ TEST(dhcp6_client) { assert_se(sd_event_new(&e) >= 0); assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, - 2 * USEC_PER_SEC, 0, + 30 * USEC_PER_SEC, 0, NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0); assert_se(sd_dhcp6_client_new(&client) >= 0); diff --git a/src/libsystemd-network/test-ndisc-ra.c b/src/libsystemd-network/test-ndisc-ra.c index d3d96e7..16a934c 100644 --- a/src/libsystemd-network/test-ndisc-ra.c +++ b/src/libsystemd-network/test-ndisc-ra.c @@ -332,7 +332,7 @@ TEST(ra) { assert_se(sd_event_source_set_io_fd_own(recv_router_advertisement, true) >= 0); assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, - 2 * USEC_PER_SEC, 0, + 30 * USEC_PER_SEC, 0, NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0); assert_se(sd_radv_start(ra) >= 0); diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index 84dc95e..6acbe8d 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -133,9 +133,17 @@ int bus_image_method_rename( if (r == 0) return 1; /* Will call us back */ + /* The image is cached with its name, hence it is necessary to remove from the cache before renaming. */ + assert_se(hashmap_remove_value(m->image_cache, image->name, image)); + r = image_rename(image, new_name); - if (r < 0) + if (r < 0) { + image_unref(image); return r; + } + + /* Then save the object again in the cache. */ + assert_se(hashmap_put(m->image_cache, image->name, image) > 0); return sd_bus_reply_method_return(message, NULL); } @@ -393,30 +401,17 @@ static int image_flush_cache(sd_event_source *s, void *userdata) { return 0; } -static int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - _cleanup_free_ char *e = NULL; - Manager *m = userdata; - Image *image = NULL; - const char *p; +int manager_acquire_image(Manager *m, const char *name, Image **ret) { int r; - assert(bus); - assert(path); - assert(interface); - assert(found); + assert(m); + assert(name); - p = startswith(path, "/org/freedesktop/machine1/image/"); - if (!p) + Image *existing = hashmap_get(m->image_cache, name); + if (existing) { + if (ret) + *ret = existing; return 0; - - e = bus_label_unescape(p); - if (!e) - return -ENOMEM; - - image = hashmap_get(m->image_cache, e); - if (image) { - *found = image; - return 1; } if (!m->image_cache_defer_event) { @@ -433,19 +428,49 @@ static int image_object_find(sd_bus *bus, const char *path, const char *interfac if (r < 0) return r; - r = image_find(IMAGE_MACHINE, e, NULL, &image); - if (r == -ENOENT) - return 0; + _cleanup_(image_unrefp) Image *image = NULL; + r = image_find(IMAGE_MACHINE, name, NULL, &image); if (r < 0) return r; image->userdata = m; r = hashmap_ensure_put(&m->image_cache, &image_hash_ops, image->name, image); - if (r < 0) { - image_unref(image); + if (r < 0) + return r; + + if (ret) + *ret = image; + + TAKE_PTR(image); + return 0; +} + +static int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + _cleanup_free_ char *e = NULL; + Manager *m = userdata; + Image *image; + const char *p; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + + p = startswith(path, "/org/freedesktop/machine1/image/"); + if (!p) + return 0; + + e = bus_label_unescape(p); + if (!e) + return -ENOMEM; + + r = manager_acquire_image(m, e, &image); + if (r == -ENOENT) + return 0; + if (r < 0) return r; - } *found = image; return 1; diff --git a/src/machine/image-dbus.h b/src/machine/image-dbus.h index 4b00203..0c4fab1 100644 --- a/src/machine/image-dbus.h +++ b/src/machine/image-dbus.h @@ -2,10 +2,12 @@ #pragma once #include "bus-object.h" +#include "discover-image.h" #include "machined.h" extern const BusObjectImplementation image_object; +int manager_acquire_image(Manager *m, const char *name, Image **ret); char *image_bus_path(const char *name); int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 56dd22d..1e16046 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -541,8 +541,8 @@ static int method_get_machine_uid_shift(sd_bus_message *message, void *userdata, } static int redirect_method_to_image(sd_bus_message *message, Manager *m, sd_bus_error *error, sd_bus_message_handler_t method) { - _cleanup_(image_unrefp) Image* i = NULL; const char *name; + Image *i; int r; assert(message); @@ -556,13 +556,12 @@ static int redirect_method_to_image(sd_bus_message *message, Manager *m, sd_bus_ if (!image_name_is_valid(name)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); - r = image_find(IMAGE_MACHINE, name, NULL, &i); + r = manager_acquire_image(m, name, &i); if (r == -ENOENT) return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); if (r < 0) return r; - i->userdata = m; return method(message, i, error); } diff --git a/src/run/run.c b/src/run/run.c index 8e4b0ec..c792807 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -772,11 +772,17 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p } if (pty_path) { + _cleanup_close_ int pty_slave = -EBADF; + + pty_slave = open_terminal(pty_path, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (pty_slave < 0) + return pty_slave; + r = sd_bus_message_append(m, "(sv)(sv)(sv)(sv)", - "StandardInput", "s", "tty", - "StandardOutput", "s", "tty", - "StandardError", "s", "tty", + "StandardInputFileDescriptor", "h", pty_slave, + "StandardOutputFileDescriptor", "h", pty_slave, + "StandardErrorFileDescriptor", "h", pty_slave, "TTYPath", "s", pty_path); if (r < 0) return bus_log_create_error(r); @@ -1027,7 +1033,7 @@ static void run_context_check_done(RunContext *c) { else done = true; - if (c->forward && done) /* If the service is gone, it's time to drain the output */ + if (c->forward && !pty_forward_is_done(c->forward) && done) /* If the service is gone, it's time to drain the output */ done = pty_forward_drain(c->forward); if (done) @@ -1095,11 +1101,18 @@ static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error } static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) { - RunContext *c = userdata; + RunContext *c = ASSERT_PTR(userdata); assert(f); - if (rcode < 0) { + if (rcode == -ECANCELED) { + log_debug_errno(rcode, "PTY forwarder disconnected."); + if (!arg_wait) + return sd_event_exit(c->event, EXIT_SUCCESS); + + /* If --wait is specified, we'll only exit the pty forwarding, but will continue to wait + * for the service to end. If the user hits ^C we'll exit too. */ + } else if (rcode < 0) { sd_event_exit(c->event, EXIT_FAILURE); return log_error_errno(rcode, "Error on PTY forwarding logic: %m"); } diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 99e37fd..3be617b 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -410,15 +410,43 @@ int blockdev_partscan_enabled(int fd) { * is 1, which can be check with 'ext_range' sysfs attribute. Explicit flag ('GENHD_FL_NO_PART_SCAN') * can be obtained from 'capability' sysattr. * - * With https://github.com/torvalds/linux/commit/1ebe2e5f9d68e94c524aba876f27b945669a7879 (v5.17), we - * can check the flag from 'ext_range' sysfs attribute directly. + * With https://github.com/torvalds/linux/commit/46e7eac647b34ed4106a8262f8bedbb90801fadd (v5.17), + * the flag is renamed to GENHD_FL_NO_PART. + * + * With https://github.com/torvalds/linux/commit/1ebe2e5f9d68e94c524aba876f27b945669a7879 (v5.17), + * we can check the flag from 'ext_range' sysfs attribute directly. + * + * With https://github.com/torvalds/linux/commit/430cc5d3ab4d0ba0bd011cfbb0035e46ba92920c (v5.17), + * the value of GENHD_FL_NO_PART is changed from 0x0200 to 0x0004. 💣💣💣 + * Note, the new value was used by the GENHD_FL_MEDIA_CHANGE_NOTIFY flag, which was introduced by + * 86ce18d7b7925bfd6b64c061828ca2a857ee83b8 (v2.6.22), and removed by + * 9243c6f3e012a92dd900d97ef45efaf8a8edc448 (v5.7). If we believe the commit message of + * e81cd5a983bb35dabd38ee472cf3fea1c63e0f23, the flag was never used. So, fortunately, we can use + * both the new and old values safely. + * + * With https://github.com/torvalds/linux/commit/b9684a71fca793213378dd410cd11675d973eaa1 (v5.19), + * another flag GD_SUPPRESS_PART_SCAN is introduced for loopback block device, and partition scanning + * is done only when both GENHD_FL_NO_PART and GD_SUPPRESS_PART_SCAN are not set. Before the commit, + * LO_FLAGS_PARTSCAN flag was directly tied with GENHD_FL_NO_PART. But with this change now it is + * tied with GD_SUPPRESS_PART_SCAN. So, LO_FLAGS_PARTSCAN cannot be obtained from 'ext_range' + * sysattr, which corresponds to GENHD_FL_NO_PART, and we need to read 'loop/partscan'. 💣💣💣 + * + * With https://github.com/torvalds/linux/commit/73a166d9749230d598320fdae3b687cdc0e2e205 (v6.3), + * the GD_SUPPRESS_PART_SCAN flag is also introduced for userspace block device (ublk). Though, not + * sure if we should support the device... * * With https://github.com/torvalds/linux/commit/e81cd5a983bb35dabd38ee472cf3fea1c63e0f23 (v6.3), - * the 'capability' sysfs attribute is deprecated, hence we cannot check the flag from it. + * the 'capability' sysfs attribute is deprecated, hence we cannot check flags from it. 💣💣💣 + * + * With https://github.com/torvalds/linux/commit/a4217c6740dc64a3eb6815868a9260825e8c68c6 (v6.10, + * backported to v6.9), the partscan status is directly exposed as 'partscan' sysattr. * - * To support both old and new kernels, we need to do the following: first check 'ext_range' sysfs - * attribute, and if '1' we can conclude partition scanning is disabled, otherwise check 'capability' - * sysattr for older version. */ + * To support both old and new kernels, we need to do the following: + * 1) check 'partscan' sysfs attribute where the information is made directly available, + * 2) check 'loop/partscan' sysfs attribute for loopback block devices, and if '0' we can conclude + * partition scanning is disabled, + * 3) check 'ext_range' sysfs attribute, and if '1' we can conclude partition scanning is disabled, + * 4) otherwise check 'capability' sysfs attribute for ancient version. */ assert(fd >= 0); @@ -426,6 +454,16 @@ int blockdev_partscan_enabled(int fd) { if (r < 0) return r; + /* For v6.10 or newer. */ + r = device_get_sysattr_bool(dev, "partscan"); + if (r != -ENOENT) + return r; + + /* For loopback block device, especially for v5.19 or newer. Even if this is enabled, we also need to + * check GENHD_FL_NO_PART flag through 'ext_range' and 'capability' sysfs attributes below. */ + if (device_get_sysattr_bool(dev, "loop/partscan") == 0) + return false; + r = device_get_sysattr_int(dev, "ext_range", &ext_range); if (r == -ENOENT) /* If the ext_range file doesn't exist then we are most likely looking at a * partition block device, not the whole block device. And that means we have no @@ -445,12 +483,10 @@ int blockdev_partscan_enabled(int fd) { if (r < 0) return r; -#ifndef GENHD_FL_NO_PART_SCAN -#define GENHD_FL_NO_PART_SCAN (0x0200) -#endif - - /* If 0x200 is set, part scanning is definitely off. */ - if (FLAGS_SET(capability, GENHD_FL_NO_PART_SCAN)) +#define GENHD_FL_NO_PART_OLD 0x0200 +#define GENHD_FL_NO_PART_NEW 0x0004 + /* If one of the NO_PART flags is set, part scanning is definitely off. */ + if ((capability & (GENHD_FL_NO_PART_OLD | GENHD_FL_NO_PART_NEW)) != 0) return false; /* Otherwise, assume part scanning is on, we have no further checks available. Assume the best. */ diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index 327dc38..55301af 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -155,7 +155,11 @@ static int next_assignment( /* Warn about unknown non-extension fields. */ if (!(flags & CONFIG_PARSE_RELAXED) && !startswith(lvalue, "X-")) log_syntax(unit, LOG_WARNING, filename, line, 0, - "Unknown key name '%s' in section '%s', ignoring.", lvalue, section); + "Unknown key '%s'%s%s%s, ignoring.", + lvalue, + section ? " in section [" : "", + strempty(section), + section ? "]" : ""); return 0; } diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 0350dd1..11729d0 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -202,6 +202,44 @@ static int extract_pretty(const char *path, const char *suffix, char **ret) { return 0; } +static int image_update_quota(Image *i, int fd) { + _cleanup_close_ int fd_close = -EBADF; + int r; + + assert(i); + + if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) + return -EROFS; + + if (i->type != IMAGE_SUBVOLUME) + return -EOPNOTSUPP; + + if (fd < 0) { + fd_close = open(i->path, O_CLOEXEC|O_NOCTTY|O_DIRECTORY); + if (fd_close < 0) + return -errno; + fd = fd_close; + } + + r = btrfs_quota_scan_ongoing(fd); + if (r < 0) + return r; + if (r > 0) + return 0; + + BtrfsQuotaInfo quota; + r = btrfs_subvol_get_subtree_quota_fd(fd, 0, "a); + if (r < 0) + return r; + + i->usage = quota.referenced; + i->usage_exclusive = quota.exclusive; + i->limit = quota.referenced_max; + i->limit_exclusive = quota.exclusive_max; + + return 1; +} + static int image_make( const char *pretty, int dfd, @@ -288,19 +326,7 @@ static int image_make( if (r < 0) return r; - if (btrfs_quota_scan_ongoing(fd) == 0) { - BtrfsQuotaInfo quota; - - r = btrfs_subvol_get_subtree_quota_fd(fd, 0, "a); - if (r >= 0) { - (*ret)->usage = quota.referenced; - (*ret)->usage_exclusive = quota.exclusive; - - (*ret)->limit = quota.referenced_max; - (*ret)->limit_exclusive = quota.exclusive_max; - } - } - + (void) image_update_quota(*ret, fd); return 0; } } @@ -1002,6 +1028,7 @@ int image_read_only(Image *i, bool b) { return -EOPNOTSUPP; } + i->read_only = b; return 0; } @@ -1100,6 +1127,8 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile } int image_set_limit(Image *i, uint64_t referenced_max) { + int r; + assert(i); if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) @@ -1115,7 +1144,12 @@ int image_set_limit(Image *i, uint64_t referenced_max) { (void) btrfs_qgroup_set_limit(i->path, 0, referenced_max); (void) btrfs_subvol_auto_qgroup(i->path, 0, true); - return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); + r = btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); + if (r < 0) + return r; + + (void) image_update_quota(i, -EBADF); + return 0; } int image_read_metadata(Image *i) { diff --git a/src/shared/journal-importer.c b/src/shared/journal-importer.c index d9eabec..1914e74 100644 --- a/src/shared/journal-importer.c +++ b/src/shared/journal-importer.c @@ -92,7 +92,12 @@ static int get_line(JournalImporter *imp, char **line, size_t *size) { imp->buf + imp->filled, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); if (n < 0) { - if (errno != EAGAIN) + if (ERRNO_IS_DISCONNECT(errno)) { + log_debug_errno(errno, "Got disconnect for importer %s.", strna(imp->name)); + return 0; + } + + if (!ERRNO_IS_TRANSIENT(errno)) log_error_errno(errno, "read(%d, ..., %zu): %m", imp->fd, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); @@ -133,7 +138,12 @@ static int fill_fixed_size(JournalImporter *imp, void **data, size_t size) { n = read(imp->fd, imp->buf + imp->filled, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); if (n < 0) { - if (errno != EAGAIN) + if (ERRNO_IS_DISCONNECT(errno)) { + log_debug_errno(errno, "Got disconnect for importer %s.", strna(imp->name)); + return 0; + } + + if (!ERRNO_IS_TRANSIENT(errno)) log_error_errno(errno, "read(%d, ..., %zu): %m", imp->fd, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); return -errno; diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c index 81e6f17..5ccf75a 100644 --- a/src/shared/libcrypt-util.c +++ b/src/shared/libcrypt-util.c @@ -114,7 +114,7 @@ static char* systemd_crypt_ra(const char *phrase, const char *setting, void **da if (!*data) { *data = new0(struct crypt_data, 1); if (!*data) { - errno = -ENOMEM; + errno = ENOMEM; return NULL; } diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index 6ffe86e..21cfe1d 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -406,6 +406,9 @@ int pty_forward_new( struct winsize ws; int r; + assert(master >= 0); + assert(ret); + f = new(PTYForward, 1); if (!f) return -ENOMEM; diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index 7084e70..6974ad8 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include @@ -85,6 +86,7 @@ TEST(tmpdir) { static void test_shareable_ns(unsigned long nsflag) { _cleanup_close_pair_ int s[2] = { -1, -1 }; + bool permission_denied = false; pid_t pid1, pid2, pid3; int r, n = 0; siginfo_t si; @@ -101,8 +103,8 @@ static void test_shareable_ns(unsigned long nsflag) { if (pid1 == 0) { r = setup_shareable_ns(s, nsflag); - assert_se(r >= 0); - _exit(r); + assert_se(r >= 0 || ERRNO_IS_PRIVILEGE(r)); + _exit(r >= 0 ? r : EX_NOPERM); } pid2 = fork(); @@ -110,8 +112,8 @@ static void test_shareable_ns(unsigned long nsflag) { if (pid2 == 0) { r = setup_shareable_ns(s, nsflag); - assert_se(r >= 0); - exit(r); + assert_se(r >= 0 || ERRNO_IS_PRIVILEGE(r)); + _exit(r >= 0 ? r : EX_NOPERM); } pid3 = fork(); @@ -119,24 +121,38 @@ static void test_shareable_ns(unsigned long nsflag) { if (pid3 == 0) { r = setup_shareable_ns(s, nsflag); - assert_se(r >= 0); - exit(r); + assert_se(r >= 0 || ERRNO_IS_PRIVILEGE(r)); + _exit(r >= 0 ? r : EX_NOPERM); } r = wait_for_terminate(pid1, &si); assert_se(r >= 0); assert_se(si.si_code == CLD_EXITED); - n += si.si_status; + if (si.si_status == EX_NOPERM) + permission_denied = true; + else + n += si.si_status; r = wait_for_terminate(pid2, &si); assert_se(r >= 0); assert_se(si.si_code == CLD_EXITED); - n += si.si_status; + if (si.si_status == EX_NOPERM) + permission_denied = true; + else + n += si.si_status; r = wait_for_terminate(pid3, &si); assert_se(r >= 0); assert_se(si.si_code == CLD_EXITED); - n += si.si_status; + if (si.si_status == EX_NOPERM) + permission_denied = true; + else + n += si.si_status; + + /* LSMs can cause setup_shareable_ns() to fail with permission denied, do not fail the test in that + * case (e.g.: LXC with AppArmor on kernel < v6.2). */ + if (permission_denied) + return (void) log_tests_skipped("insufficient privileges"); assert_se(n == 1); } diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index a186246..b44e572 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -2169,7 +2169,7 @@ static int item_do( de_fd = openat(fd, de->d_name, O_NOFOLLOW|O_CLOEXEC|O_PATH); if (de_fd < 0) { - if (errno != -ENOENT) + if (errno != ENOENT) q = log_error_errno(errno, "Failed to open file '%s': %m", de->d_name); } else { _cleanup_free_ char *de_path = NULL; diff --git a/test/networkd-test.py b/test/networkd-test.py index 84013e7..ebb5553 100755 --- a/test/networkd-test.py +++ b/test/networkd-test.py @@ -1037,13 +1037,16 @@ DNS=127.0.0.1 self.create_iface(dhcpserver_opts='EmitTimezone=yes\nTimezone=Pacific/Honolulu') self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=false\n[DHCP]\nUseTimezone=true', dhcp_mode='ipv4') - # should have applied the received timezone - try: - self.assertEqual(get_tz(), 'Pacific/Honolulu') - except AssertionError: + # Should have applied the received timezone. This is asynchronous, so we need to wait for a while: + for _ in range(20): + tz = get_tz() + if tz == 'Pacific/Honolulu': + break + time.sleep(0.5) + else: self.show_journal('systemd-networkd.service') - self.show_journal('systemd-hostnamed.service') - raise + self.show_journal('systemd-timedated.service') + self.fail(f'Timezone: {tz}, expected: Pacific/Honolulu') class MatchClientTest(unittest.TestCase, NetworkdTestingUtilities): diff --git a/test/test-functions b/test/test-functions index 097babf..976b172 100644 --- a/test/test-functions +++ b/test/test-functions @@ -183,6 +183,7 @@ BASICTOOLS=( lz4cat mkfifo mktemp + modinfo modprobe mount mountpoint diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index a9286e9..d84350b 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -59,6 +59,7 @@ asan_options = None lsan_options = None ubsan_options = None with_coverage = False +show_journal = True # When true, show journal on stopping networkd. active_units = [] protected_links = { @@ -166,8 +167,10 @@ def expectedFailureIfRoutingPolicyPortRangeIsNotAvailable(): def expectedFailureIfRoutingPolicyIPProtoIsNotAvailable(): def f(func): - rc = call_quiet('ip rule add not from 192.168.100.19 ipproto tcp table 7') - call_quiet('ip rule del not from 192.168.100.19 ipproto tcp table 7') + # IP protocol name is parsed by getprotobyname(), and it requires /etc/protocols. + # Hence. here we use explicit number: 6 == tcp. + rc = call_quiet('ip rule add not from 192.168.100.19 ipproto 6 table 7') + call_quiet('ip rule del not from 192.168.100.19 ipproto 6 table 7') return func if rc == 0 else unittest.expectedFailure(func) return f @@ -247,6 +250,22 @@ def expectedFailureIfNetdevsimWithSRIOVIsNotAvailable(): return f +def expectedFailureIfKernelReturnsInvalidFlags(): + ''' + This checks the kernel bug caused by 3ddc2231c8108302a8229d3c5849ee792a63230d. + It will be fixed by the following patch: + https://patchwork.kernel.org/project/netdevbpf/patch/20240510072932.2678952-1-edumazet@google.com/ + ''' + def f(func): + call_quiet('ip link add dummy98 type dummy') + call_quiet('ip link set up dev dummy98') + call_quiet('ip address add 192.0.2.1/24 dev dummy98 noprefixroute') + output = check_output('ip address show dev dummy98') + remove_link('dummy98') + return func if 'noprefixroute' in output else unittest.expectedFailure(func) + + return f + # pylint: disable=C0415 def compare_kernel_version(min_kernel_version): try: @@ -636,6 +655,8 @@ def read_networkd_log(invocation_id=None): return check_output('journalctl _SYSTEMD_INVOCATION_ID=' + invocation_id) def stop_networkd(show_logs=True): + global show_journal + show_logs = show_logs and show_journal if show_logs: invocation_id = networkd_invocation_id() check_output('systemctl stop systemd-networkd.socket') @@ -647,6 +668,8 @@ def start_networkd(): check_output('systemctl start systemd-networkd') def restart_networkd(show_logs=True): + global show_journal + show_logs = show_logs and show_journal if show_logs: invocation_id = networkd_invocation_id() check_output('systemctl restart systemd-networkd.service') @@ -1319,6 +1342,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities): print(output) self.assertRegex(output, 'macvtap mode ' + mode + ' ') + @expectedFailureIfModuleIsNotAvailable('macvlan') def test_macvlan(self): first = True for mode in ['private', 'vepa', 'bridge', 'passthru']: @@ -2610,12 +2634,12 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): output = check_output('ip rule') print(output) - self.assertRegex(output, '111') - self.assertRegex(output, 'from 192.168.100.18') - self.assertRegex(output, '1123-1150') - self.assertRegex(output, '3224-3290') - self.assertRegex(output, 'tcp') - self.assertRegex(output, 'lookup 7') + self.assertIn('111:', output) + self.assertIn('from 192.168.100.18 ', output) + self.assertIn('sport 1123-1150 ', output) + self.assertIn('dport 3224-3290 ', output) + self.assertRegex(output, 'ipproto (tcp|ipproto-6) ') + self.assertIn('lookup 7 ', output) @expectedFailureIfRoutingPolicyIPProtoIsNotAvailable() def test_routing_policy_rule_invert(self): @@ -2625,10 +2649,11 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): output = check_output('ip rule') print(output) - self.assertRegex(output, '111') - self.assertRegex(output, 'not.*?from.*?192.168.100.18') - self.assertRegex(output, 'tcp') - self.assertRegex(output, 'lookup 7') + self.assertIn('111:', output) + self.assertIn('not ', output) + self.assertIn('from 192.168.100.18 ', output) + self.assertRegex(output, 'ipproto (tcp|ipproto-6) ') + self.assertIn('lookup 7 ', output) @expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable() def test_routing_policy_rule_uidrange(self): @@ -2638,10 +2663,10 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): output = check_output('ip rule') print(output) - self.assertRegex(output, '111') - self.assertRegex(output, 'from 192.168.100.18') - self.assertRegex(output, 'lookup 7') - self.assertRegex(output, 'uidrange 100-200') + self.assertIn('111:', output) + self.assertIn('from 192.168.100.18 ', output) + self.assertIn('lookup 7 ', output) + self.assertIn('uidrange 100-200 ', output) def _test_route_static(self, manage_foreign_routes): if not manage_foreign_routes: @@ -4572,6 +4597,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): self.assertIn('DHCPREPLY(veth-peer)', output) self.assertNotIn('rapid-commit', output) + @expectedFailureIfKernelReturnsInvalidFlags() def test_dhcp_client_ipv4_only(self): copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network') @@ -5658,6 +5684,7 @@ if __name__ == '__main__': parser.add_argument('--lsan-options', help='LSAN options', dest='lsan_options') parser.add_argument('--ubsan-options', help='UBSAN options', dest='ubsan_options') parser.add_argument('--with-coverage', help='Loosen certain sandbox restrictions to make gcov happy', dest='with_coverage', type=bool, nargs='?', const=True, default=with_coverage) + parser.add_argument('--no-journal', help='Do not show journal of systemd-networkd on stop', dest='show_journal', action='store_false') ns, unknown_args = parser.parse_known_args(namespace=unittest) if ns.build_dir: @@ -5707,6 +5734,7 @@ if __name__ == '__main__': lsan_options = ns.lsan_options ubsan_options = ns.ubsan_options with_coverage = ns.with_coverage + show_journal = ns.show_journal if use_valgrind: # Do not forget the trailing space. diff --git a/test/test-rpm-macros.sh b/test/test-rpm-macros.sh index c7107de..c9a45dc 100755 --- a/test/test-rpm-macros.sh +++ b/test/test-rpm-macros.sh @@ -137,7 +137,7 @@ for i in sysusers tmpfiles; do PKG_DATA_FILE="$(mktemp "$WORK_DIR/pkg-data-XXX")" EXP_OUT="$(mktemp "$WORK_DIR/exp-out-XXX.log")" - CONF_DIR="$(pkg-config --variable="${i}dir" systemd)" + CONF_DIR="$(PKG_CONFIG_PATH="${BUILD_DIR}/src/core" pkg-config --variable="${i}dir" systemd)" EXTRA_ARGS=() if [[ "$i" == tmpfiles ]]; then diff --git a/test/units/testsuite-38.sh b/test/units/testsuite-38.sh index c5f9bcc..35c4f1c 100755 --- a/test/units/testsuite-38.sh +++ b/test/units/testsuite-38.sh @@ -91,7 +91,7 @@ check_freezer_state() { # Ignore the intermediate freezing & thawing states in case we check # the unit state too quickly - [[ "$state" =~ ^(freezing|thawing)$ ]] || break + [[ "$state" =~ ^(freezing|thawing) ]] || break sleep .5 done diff --git a/test/units/testsuite-46.sh b/test/units/testsuite-46.sh index ec80b71..e41ca4c 100755 --- a/test/units/testsuite-46.sh +++ b/test/units/testsuite-46.sh @@ -20,7 +20,7 @@ inspect() { userdbctl user "$USERNAME" | tee /tmp/b # diff uses the grep BREs for pattern matching - diff -I '^\s*Disk \(Size\|Free\|Floor\|Ceiling\):' /tmp/{a,b} + diff -I '^\s*Disk \(Size\|Free\|Floor\|Ceiling\|Usage\):' /tmp/{a,b} rm /tmp/{a,b} homectl inspect --json=pretty "$USERNAME" diff --git a/test/units/testsuite-58.sh b/test/units/testsuite-58.sh index e369e58..5b72a49 100755 --- a/test/units/testsuite-58.sh +++ b/test/units/testsuite-58.sh @@ -283,7 +283,7 @@ $imgs/zzz6 : start= 4194264, size= 2097152, type=0FC63DAF-8483-4772-8E79 $imgs/zzz7 : start= 6291416, size= 98304, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=7B93D1F2-595D-4CE3-B0B9-837FBD9E63B0, name=\"luks-format-copy\"" loop="$(losetup -P --show --find "$imgs/zzz")" - udevadm wait --timeout 60 --settle "${loop:?}" + udevadm wait --timeout 60 --settle "${loop:?}p7" volume="test-repart-$RANDOM" diff --git a/test/units/testsuite-64.sh b/test/units/testsuite-64.sh index e9f352c..f0cfce3 100755 --- a/test/units/testsuite-64.sh +++ b/test/units/testsuite-64.sh @@ -577,9 +577,10 @@ EOF for ((i = 0; i < ${#devices[@]}; i++)); do # Intentionally use weaker cipher-related settings, since we don't care # about security here as it's a throwaway LUKS partition - cryptsetup luksFormat -q \ - --use-urandom --pbkdf pbkdf2 --pbkdf-force-iterations 1000 \ - --uuid "deadbeef-dead-dead-beef-11111111111$i" --label "encdisk$i" "${devices[$i]}" /etc/btrfs_keyfile + udevadm lock --device="${devices[$i]}" \ + cryptsetup luksFormat -q \ + --use-urandom --pbkdf pbkdf2 --pbkdf-force-iterations 1000 \ + --uuid "deadbeef-dead-dead-beef-11111111111$i" --label "encdisk$i" "${devices[$i]}" /etc/btrfs_keyfile udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/deadbeef-dead-dead-beef-11111111111$i" "/dev/disk/by-label/encdisk$i" # Add the device into /etc/crypttab, reload systemd, and then activate # the device so we can create a filesystem on it later diff --git a/tmpfiles.d/systemd.conf.in b/tmpfiles.d/systemd.conf.in index 74f35cc..01cd7d8 100644 --- a/tmpfiles.d/systemd.conf.in +++ b/tmpfiles.d/systemd.conf.in @@ -26,13 +26,16 @@ Z /run/log/journal/%m ~2750 root systemd-journal - - {% if HAVE_ACL %} {% if ENABLE_ADM_GROUP and ENABLE_WHEEL_GROUP %} a+ /run/log/journal - - - - d:group::r-x,d:group:adm:r-x,d:group:wheel:r-x,group::r-x,group:adm:r-x,group:wheel:r-x -A+ /run/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x,group:adm:r-X,group:wheel:r-X +a+ /run/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x,group:adm:r-x,group:wheel:r-x +a+ /run/log/journal/%m/*.journal* - - - - group:adm:r--,group:wheel:r-- {% elif ENABLE_ADM_GROUP %} a+ /run/log/journal - - - - d:group::r-x,d:group:adm:r-x,group::r-x,group:adm:r-x -A+ /run/log/journal/%m - - - - d:group:adm:r-x,group:adm:r-X +a+ /run/log/journal/%m - - - - d:group:adm:r-x,group:adm:r-x +a+ /run/log/journal/%m/*.journal* - - - - group:adm:r-- {% elif ENABLE_WHEEL_GROUP %} a+ /run/log/journal - - - - d:group::r-x,d:group:wheel:r-x,group::r-x,group:wheel:r-x -A+ /run/log/journal/%m - - - - d:group:wheel:r-x,group:wheel:r-X +a+ /run/log/journal/%m - - - - d:group:wheel:r-x,group:wheel:r-x +a+ /run/log/journal/%m/*.journal* - - - - group:wheel:r-- {% endif %} {% endif %} -- cgit v1.2.3