import CS systemd-252-67.el9

This commit is contained in:
AlmaLinux RelEng Bot 2026-03-30 11:08:19 -04:00
parent b0cdb78193
commit 7a1189d5b7
119 changed files with 14942 additions and 5 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
SOURCES/rhel-net-naming-sysattrs-v0.5.tar.gz
SOURCES/net-naming-sysattrs-v0.5.tar.gz
SOURCES/systemd-252.tar.gz

View File

@ -1,2 +1,2 @@
9ce6834429dbb9cb049de1bdf77bc8c84763709c SOURCES/rhel-net-naming-sysattrs-v0.5.tar.gz
1f16c2c8705497c3068559321b2183f0f1e20b7b SOURCES/net-naming-sysattrs-v0.5.tar.gz
7c961dc6e8bb950825b85129f59dc80f4536cabb SOURCES/systemd-252.tar.gz

View File

@ -0,0 +1,96 @@
From 3d54c58e3d4c720a9a95a39879b795e7154f0209 Mon Sep 17 00:00:00 2001
From: Jan Janssen <medhefgo@web.de>
Date: Fri, 23 Dec 2022 14:14:53 +0100
Subject: [PATCH] Revert "boot: Use EFI_BOOT_MANAGER_POLICY_PROTOCOL to connect
console devices"
This reverts commit b99bf5811850afdb2502ba37251c48348da63c82.
It seems that using this protocol on some firmwares to forcibly
initialize console devices may break handles (already opened file
handles and the device handle itself) that we rely on to access the
boot filesystem, making it impossible to load the selected entry.
It might be possible to get a new handle by querying for the device
handle by using its device path after calling into this protocol, but
this is untested. The firmware might also be so buggy that accessing
devices after using this protocol is impossible.
It seems prudent to revert this for now until some reliable way is found
to initialize console devices without introducing huge boot delays. Any
users on firmware where console devices cannot be accessed would have to
rely on disabling fastboot.
Fixes: #25737, #25846
(cherry picked from commit f151abb0e5fa4f820109eb0541bfdcba319d2b92)
Resolves: RHEL-108596
---
src/boot/efi/console.c | 16 ----------------
src/boot/efi/missing_efi.h | 19 -------------------
2 files changed, 35 deletions(-)
diff --git a/src/boot/efi/console.c b/src/boot/efi/console.c
index 85a76e6e68..3b8b6b2e41 100644
--- a/src/boot/efi/console.c
+++ b/src/boot/efi/console.c
@@ -12,20 +12,6 @@
#define VERTICAL_MAX_OK 1080
#define VIEWPORT_RATIO 10
-static EFI_STATUS console_connect(void) {
- EFI_BOOT_MANAGER_POLICY_PROTOCOL *boot_policy;
- EFI_STATUS err;
-
- /* This should make console devices appear/fully initialize on fastboot firmware. */
-
- err = BS->LocateProtocol(
- &(EFI_GUID) EFI_BOOT_MANAGER_POLICY_PROTOCOL_GUID, NULL, (void **) &boot_policy);
- if (err != EFI_SUCCESS)
- return err;
-
- return boot_policy->ConnectDeviceClass(boot_policy, &(EFI_GUID) EFI_BOOT_MANAGER_POLICY_CONSOLE_GUID);
-}
-
static inline void event_closep(EFI_EVENT *event) {
if (!*event)
return;
@@ -61,8 +47,6 @@ EFI_STATUS console_key_read(uint64_t *key, uint64_t timeout_usec) {
assert(key);
if (!checked) {
- console_connect();
-
/* Get the *first* TextInputEx device.*/
err = BS->LocateProtocol(
MAKE_GUID_PTR(EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL), NULL, (void **) &extraInEx);
diff --git a/src/boot/efi/missing_efi.h b/src/boot/efi/missing_efi.h
index d34dfc3379..3c35a85e46 100644
--- a/src/boot/efi/missing_efi.h
+++ b/src/boot/efi/missing_efi.h
@@ -399,25 +399,6 @@ typedef struct {
} EFI_SHELL_PARAMETERS_PROTOCOL;
#endif
-#ifndef EFI_BOOT_MANAGER_POLICY_PROTOCOL_GUID
-#define EFI_BOOT_MANAGER_POLICY_PROTOCOL_GUID \
- { 0xFEDF8E0C, 0xE147, 0x11E3, { 0x99, 0x03, 0xB8, 0xE8, 0x56, 0x2C, 0xBA, 0xFA } }
-#define EFI_BOOT_MANAGER_POLICY_CONSOLE_GUID \
- { 0xCAB0E94C, 0xE15F, 0x11E3, { 0x91, 0x8D, 0xB8, 0xE8, 0x56, 0x2C, 0xBA, 0xFA } }
-
-typedef struct EFI_BOOT_MANAGER_POLICY_PROTOCOL EFI_BOOT_MANAGER_POLICY_PROTOCOL;
-struct EFI_BOOT_MANAGER_POLICY_PROTOCOL {
- UINT64 Revision;
- EFI_STATUS (EFIAPI *ConnectDevicePath)(
- EFI_BOOT_MANAGER_POLICY_PROTOCOL *This,
- EFI_DEVICE_PATH *DevicePath,
- BOOLEAN Recursive);
- EFI_STATUS (EFIAPI *ConnectDeviceClass)(
- EFI_BOOT_MANAGER_POLICY_PROTOCOL *This,
- EFI_GUID *Class);
-};
-#endif
-
#ifndef EFI_WARN_UNKNOWN_GLYPH
# define EFI_WARN_UNKNOWN_GLYPH 1
#endif

View File

@ -0,0 +1,38 @@
From bcb456e590fd6994a6b38e9b2d89e3ef0417d402 Mon Sep 17 00:00:00 2001
From: Jan Janssen <medhefgo@web.de>
Date: Tue, 2 May 2023 19:41:58 +0200
Subject: [PATCH] boot: Use correct memory type for allocations
We were using the wrong memory type when allocating pool memory. This
does not seem to cause a problem on x86, but the kernel will fail to
boot at least on ARM in QEMU.
This is caused by mixing different allocation types which ended up
breaking the kernel or EDK2 during boot services exit. Commit
2f3c3b0bee5534f2338439f04b0aa517479f8b76 appears to fix this boot
failure because it was replacing the gnu-efi xpool_print with xasprintf
thereby unifying the allocation type.
But this same issue can also happen without this fix somehow when the
random-seed logic is in use.
Fixes: #27371
(cherry picked from commit ec232e4abd7aebfec06b4814b30129532b2bcefd)
Resolves: RHEL-108555
---
src/boot/efi/util.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h
index f7452e3cf5..16c9fcc3e1 100644
--- a/src/boot/efi/util.h
+++ b/src/boot/efi/util.h
@@ -47,7 +47,7 @@ static inline void freep(void *p) {
_malloc_ _alloc_(1) _returns_nonnull_ _warn_unused_result_
static inline void *xmalloc(size_t size) {
void *p;
- assert_se(BS->AllocatePool(EfiBootServicesData, size, &p) == EFI_SUCCESS);
+ assert_se(BS->AllocatePool(EfiLoaderData, size, &p) == EFI_SUCCESS);
return p;
}

View File

@ -0,0 +1,33 @@
From 7ee5909a539f7a4a65c35e86404ceecb499c54c5 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Thu, 2 Nov 2023 14:20:11 +0900
Subject: [PATCH] meson: /etc/systemd/network is also used by udevd
(cherry picked from commit 6256c65aad2a719ac9054961561bb26e497208ce)
Resolves: RHEL-109096
---
network/meson.build | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/network/meson.build b/network/meson.build
index 4c6de20515..499ea61086 100644
--- a/network/meson.build
+++ b/network/meson.build
@@ -12,11 +12,12 @@ if conf.get('ENABLE_NETWORKD') == 1
'80-wifi-station.network.example',
install_dir : networkdir)
- if install_sysconfdir
- meson.add_install_script('sh', '-c',
- mkdir_p.format(sysconfdir / 'systemd/network'))
- endif
endif
install_data('99-default.link',
install_dir : networkdir)
+
+if install_sysconfdir
+ meson.add_install_script('sh', '-c',
+ mkdir_p.format(sysconfdir / 'systemd/network'))
+endif

View File

@ -0,0 +1,207 @@
From 3b49b68593cbca6ee4e08a34a780fd5b5c3ab9fb Mon Sep 17 00:00:00 2001
From: licunlong <licunlong1@huawei.com>
Date: Thu, 15 Jun 2023 16:28:28 +0800
Subject: [PATCH] sd-bus: make bus_add_match_full accept timeout
(cherry picked from commit bb30e58f644689feaa87d8136d1686b6c3a6f42a)
Related: RHEL-31756
---
src/libsystemd/sd-bus/bus-control.c | 48 +++++++++++++++++++++-------
src/libsystemd/sd-bus/bus-control.h | 4 +--
src/libsystemd/sd-bus/bus-internal.h | 10 ++++++
src/libsystemd/sd-bus/sd-bus.c | 17 ++++++----
4 files changed, 59 insertions(+), 20 deletions(-)
diff --git a/src/libsystemd/sd-bus/bus-control.c b/src/libsystemd/sd-bus/bus-control.c
index d96b7256a1..762656fa74 100644
--- a/src/libsystemd/sd-bus/bus-control.c
+++ b/src/libsystemd/sd-bus/bus-control.c
@@ -803,9 +803,10 @@ _public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **r
int bus_add_match_internal(
sd_bus *bus,
const char *match,
+ uint64_t timeout_usec,
uint64_t *ret_counter) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
const char *e;
int r;
@@ -816,16 +817,26 @@ int bus_add_match_internal(
e = append_eavesdrop(bus, match);
- r = sd_bus_call_method(
+ r = sd_bus_message_new_method_call(
bus,
+ &m,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
- "AddMatch",
+ "AddMatch");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "s", e);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call(
+ bus,
+ m,
+ timeout_usec,
NULL,
- &reply,
- "s",
- e);
+ &reply);
if (r < 0)
return r;
@@ -841,9 +852,12 @@ int bus_add_match_internal_async(
sd_bus_slot **ret_slot,
const char *match,
sd_bus_message_handler_t callback,
- void *userdata) {
+ void *userdata,
+ uint64_t timeout_usec) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
const char *e;
+ int r;
assert(bus);
@@ -852,17 +866,27 @@ int bus_add_match_internal_async(
e = append_eavesdrop(bus, match);
- return sd_bus_call_method_async(
+ r = sd_bus_message_new_method_call(
bus,
- ret_slot,
+ &m,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
- "AddMatch",
+ "AddMatch");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "s", e);
+ if (r < 0)
+ return r;
+
+ return sd_bus_call_async(
+ bus,
+ ret_slot,
+ m,
callback,
userdata,
- "s",
- e);
+ timeout_usec);
}
int bus_remove_match_internal(
diff --git a/src/libsystemd/sd-bus/bus-control.h b/src/libsystemd/sd-bus/bus-control.h
index 8182b9cd63..1cd4fb88d9 100644
--- a/src/libsystemd/sd-bus/bus-control.h
+++ b/src/libsystemd/sd-bus/bus-control.h
@@ -3,7 +3,7 @@
#include "sd-bus.h"
-int bus_add_match_internal(sd_bus *bus, const char *match, uint64_t *ret_counter);
-int bus_add_match_internal_async(sd_bus *bus, sd_bus_slot **ret, const char *match, sd_bus_message_handler_t callback, void *userdata);
+int bus_add_match_internal(sd_bus *bus, const char *match, uint64_t timeout_usec, uint64_t *ret_counter);
+int bus_add_match_internal_async(sd_bus *bus, sd_bus_slot **ret, const char *match, sd_bus_message_handler_t callback, void *userdata, uint64_t timeout_usec);
int bus_remove_match_internal(sd_bus *bus, const char *match);
diff --git a/src/libsystemd/sd-bus/bus-internal.h b/src/libsystemd/sd-bus/bus-internal.h
index 51673ad1c5..603a53fb10 100644
--- a/src/libsystemd/sd-bus/bus-internal.h
+++ b/src/libsystemd/sd-bus/bus-internal.h
@@ -386,6 +386,16 @@ int bus_attach_inotify_event(sd_bus *b);
void bus_close_inotify_fd(sd_bus *b);
void bus_close_io_fds(sd_bus *b);
+int bus_add_match_full(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ bool asynchronous,
+ const char *match,
+ sd_bus_message_handler_t callback,
+ sd_bus_message_handler_t install_callback,
+ void *userdata,
+ uint64_t timeout_usec);
+
#define OBJECT_PATH_FOREACH_PREFIX(prefix, path) \
for (char *_slash = ({ strcpy((prefix), (path)); streq((prefix), "/") ? NULL : strrchr((prefix), '/'); }) ; \
_slash && ((_slash[(_slash) == (prefix)] = 0), true); \
diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c
index 10efe53a25..b8406f154b 100644
--- a/src/libsystemd/sd-bus/sd-bus.c
+++ b/src/libsystemd/sd-bus/sd-bus.c
@@ -3486,14 +3486,15 @@ static int add_match_callback(
return r;
}
-static int bus_add_match_full(
+int bus_add_match_full(
sd_bus *bus,
sd_bus_slot **slot,
bool asynchronous,
const char *match,
sd_bus_message_handler_t callback,
sd_bus_message_handler_t install_callback,
- void *userdata) {
+ void *userdata,
+ uint64_t timeout_usec) {
struct bus_match_component *components = NULL;
unsigned n_components = 0;
@@ -3539,7 +3540,8 @@ static int bus_add_match_full(
&s->match_callback.install_slot,
s->match_callback.match_string,
add_match_callback,
- s);
+ s,
+ timeout_usec);
if (r < 0)
goto finish;
@@ -3549,7 +3551,10 @@ static int bus_add_match_full(
* then make it floating. */
r = sd_bus_slot_set_floating(s->match_callback.install_slot, true);
} else
- r = bus_add_match_internal(bus, s->match_callback.match_string, &s->match_callback.after);
+ r = bus_add_match_internal(bus,
+ s->match_callback.match_string,
+ timeout_usec,
+ &s->match_callback.after);
if (r < 0)
goto finish;
@@ -3580,7 +3585,7 @@ _public_ int sd_bus_add_match(
sd_bus_message_handler_t callback,
void *userdata) {
- return bus_add_match_full(bus, slot, false, match, callback, NULL, userdata);
+ return bus_add_match_full(bus, slot, false, match, callback, NULL, userdata, 0);
}
_public_ int sd_bus_add_match_async(
@@ -3591,7 +3596,7 @@ _public_ int sd_bus_add_match_async(
sd_bus_message_handler_t install_callback,
void *userdata) {
- return bus_add_match_full(bus, slot, true, match, callback, install_callback, userdata);
+ return bus_add_match_full(bus, slot, true, match, callback, install_callback, userdata, 0);
}
bool bus_pid_changed(sd_bus *bus) {

View File

@ -0,0 +1,52 @@
From 1a5720577ae6791ae64795486c5902b7c5aceb6b Mon Sep 17 00:00:00 2001
From: licunlong <licunlong1@huawei.com>
Date: Thu, 15 Jun 2023 10:47:32 +0800
Subject: [PATCH] core/unit: add get_timeout_start_usec in UnitVTable and
define it for service
(cherry picked from commit f5a9d2ee2a849aca1f2d15485d020142ff33cc30)
Related: RHEL-31756
---
src/core/service.c | 6 ++++++
src/core/unit.h | 3 +++
2 files changed, 9 insertions(+)
diff --git a/src/core/service.c b/src/core/service.c
index 433df0afe3..305f3b7170 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -4383,6 +4383,11 @@ static int service_get_timeout(Unit *u, usec_t *timeout) {
return 1;
}
+static usec_t service_get_timeout_start_usec(Unit *u) {
+ Service *s = SERVICE(ASSERT_PTR(u));
+ return s->timeout_start_usec;
+}
+
static bool pick_up_pid_from_bus_name(Service *s) {
assert(s);
@@ -4870,6 +4875,7 @@ const UnitVTable service_vtable = {
.bus_commit_properties = bus_service_commit_properties,
.get_timeout = service_get_timeout,
+ .get_timeout_start_usec = service_get_timeout_start_usec,
.needs_console = service_needs_console,
.exit_status = service_exit_status,
.status_text = service_status_text,
diff --git a/src/core/unit.h b/src/core/unit.h
index 4bb85b55be..fdea76458d 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -713,6 +713,9 @@ typedef struct UnitVTable {
/* Returns the next timeout of a unit */
int (*get_timeout)(Unit *u, usec_t *timeout);
+ /* Returns the start timeout of a unit */
+ usec_t (*get_timeout_start_usec)(Unit *u);
+
/* Returns the main PID if there is any defined, or 0. */
pid_t (*main_pid)(Unit *u);

View File

@ -0,0 +1,99 @@
From 6b2f0e500e6688d1b2d9ad8b9947e03bd58f27a2 Mon Sep 17 00:00:00 2001
From: licunlong <licunlong1@huawei.com>
Date: Wed, 24 May 2023 11:45:31 +0800
Subject: [PATCH] core/unit: increase the NameOwnerChanged/GetNameOwner timeout
to the unit's start timeout
When dbus is overloaded, these messages are easily timedout,
systemd may kill dbus-type service by mistake. This PR
mitigates this problem by increasing the timeout to the
unit's start timeout.
(cherry picked from commit 8df433d7cd268ae96cfe795feaa59f4d3e87b85c)
Related: RHEL-31756
---
src/core/unit.c | 39 ++++++++++++++++++++++++++++++++++-----
1 file changed, 34 insertions(+), 5 deletions(-)
diff --git a/src/core/unit.c b/src/core/unit.c
index 0d65880ac7..dddaeb96f6 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -14,6 +14,7 @@
#include "bpf-foreign.h"
#include "bpf-socket-bind.h"
#include "bus-common-errors.h"
+#include "bus-internal.h"
#include "bus-util.h"
#include "cgroup-setup.h"
#include "cgroup-util.h"
@@ -3539,7 +3540,9 @@ static int get_name_owner_handler(sd_bus_message *message, void *userdata, sd_bu
}
int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
const char *match;
+ usec_t timeout_usec = 0;
int r;
assert(u);
@@ -3549,6 +3552,12 @@ int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name) {
if (u->match_bus_slot || u->get_name_owner_slot)
return -EBUSY;
+ /* NameOwnerChanged and GetNameOwner is used to detect when a service finished starting up. The dbus
+ * call timeout shouldn't be earlier than that. If we couldn't get the start timeout, use the default
+ * value defined above. */
+ if (UNIT_VTABLE(u)->get_timeout_start_usec)
+ timeout_usec = UNIT_VTABLE(u)->get_timeout_start_usec(u);
+
match = strjoina("type='signal',"
"sender='org.freedesktop.DBus',"
"path='/org/freedesktop/DBus',"
@@ -3556,20 +3565,40 @@ int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name) {
"member='NameOwnerChanged',"
"arg0='", name, "'");
- r = sd_bus_add_match_async(bus, &u->match_bus_slot, match, signal_name_owner_changed, NULL, u);
+ r = bus_add_match_full(
+ bus,
+ &u->match_bus_slot,
+ true,
+ match,
+ signal_name_owner_changed,
+ NULL,
+ u,
+ timeout_usec);
if (r < 0)
return r;
- r = sd_bus_call_method_async(
+ r = sd_bus_message_new_method_call(
bus,
- &u->get_name_owner_slot,
+ &m,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
- "GetNameOwner",
+ "GetNameOwner");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "s", name);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call_async(
+ bus,
+ &u->get_name_owner_slot,
+ m,
get_name_owner_handler,
u,
- "s", name);
+ timeout_usec);
+
if (r < 0) {
u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot);
return r;

View File

@ -0,0 +1,38 @@
From 14c6c9a0a58a99d66f541cec50a5cc860303ae7e Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Sun, 29 Dec 2024 15:10:53 +0900
Subject: [PATCH] core,sd-bus: drop empty lines between function call and error
check
(cherry picked from commit 7baf4d234a70f136014f9e92f00c078a55c7adba)
Related: RHEL-31756
---
src/core/unit.c | 1 -
src/libsystemd/sd-bus/sd-bus.c | 1 -
2 files changed, 2 deletions(-)
diff --git a/src/core/unit.c b/src/core/unit.c
index dddaeb96f6..5a091b3bc4 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -3598,7 +3598,6 @@ int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name) {
get_name_owner_handler,
u,
timeout_usec);
-
if (r < 0) {
u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot);
return r;
diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c
index b8406f154b..c2dded43ad 100644
--- a/src/libsystemd/sd-bus/sd-bus.c
+++ b/src/libsystemd/sd-bus/sd-bus.c
@@ -3542,7 +3542,6 @@ int bus_add_match_full(
add_match_callback,
s,
timeout_usec);
-
if (r < 0)
goto finish;

View File

@ -0,0 +1,71 @@
From 899e94d72119c5e5f3d0ae75f39abd376bc28b0e Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Sun, 29 Dec 2024 15:50:43 +0900
Subject: [PATCH] core: do not disconnect from bus when failed to install
signal match
If bus_add_match_full() is called without install callback and we failed
to install the signal match e.g. by timeout, then add_match_callback()
will disconnect from the bus.
Let's use a custom install handler and handle failures gracefully.
This does not *solve* the root cause of issue #30573, but should improve
the situation when the issue is triggered.
(cherry picked from commit db6b214f95aa42f9a9fa3d94a3c6492cc57b58fb)
Related: RHEL-31756
---
src/core/unit.c | 30 ++++++++++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/src/core/unit.c b/src/core/unit.c
index 5a091b3bc4..9e349402ff 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -3486,6 +3486,32 @@ int unit_load_related_unit(Unit *u, const char *type, Unit **_found) {
return r;
}
+static int signal_name_owner_changed_install_handler(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Unit *u = ASSERT_PTR(userdata);
+ const sd_bus_error *e;
+ int r;
+
+ e = sd_bus_message_get_error(message);
+ if (!e) {
+ log_unit_trace(u, "Successfully installed NameOwnerChanged signal match.");
+ return 0;
+ }
+
+ r = sd_bus_error_get_errno(e);
+ log_unit_error_errno(u, r,
+ "Unexpected error response on installing NameOwnerChanged signal match: %s",
+ bus_error_message(e, r));
+
+ /* If we failed to install NameOwnerChanged signal, also unref the bus slot of GetNameOwner(). */
+ u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot);
+ u->get_name_owner_slot = sd_bus_slot_unref(u->get_name_owner_slot);
+
+ if (UNIT_VTABLE(u)->bus_name_owner_change)
+ UNIT_VTABLE(u)->bus_name_owner_change(u, NULL);
+
+ return 0;
+}
+
static int signal_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
const char *new_owner;
Unit *u = ASSERT_PTR(userdata);
@@ -3568,10 +3594,10 @@ int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name) {
r = bus_add_match_full(
bus,
&u->match_bus_slot,
- true,
+ /* asynchronous = */ true,
match,
signal_name_owner_changed,
- NULL,
+ signal_name_owner_changed_install_handler,
u,
timeout_usec);
if (r < 0)

View File

@ -0,0 +1,180 @@
From 5917dd6d4667ed4f97b63baaa3b35e2c1410f3c0 Mon Sep 17 00:00:00 2001
From: Ronan Pigott <ronan@rjp.ie>
Date: Thu, 28 Nov 2024 12:53:32 -0700
Subject: [PATCH] dbus: stash the subscriber list when we disconenct from the
bus
If we unexpectly disconnect from the bus, systemd would end up dropping
the list of subscribers, which breaks the ability of clients like logind
to monitor the state of units.
Stash the list of subscribers into the deserialized state in the event
of a disconnect so that when we recover we can renew the broken
subscriptions.
(cherry picked from commit 8402ca04d1a063c3d8a9e3d5c16df8bb8778ae98)
Related: RHEL-31756
---
src/core/dbus.c | 21 +++++++++++++++------
src/core/dbus.h | 2 +-
src/core/manager.c | 8 ++++----
src/shared/bus-util.c | 22 ++++++++++++++++++++++
src/shared/bus-util.h | 1 +
5 files changed, 43 insertions(+), 11 deletions(-)
diff --git a/src/core/dbus.c b/src/core/dbus.c
index 1431e079c2..12a6f2fa27 100644
--- a/src/core/dbus.c
+++ b/src/core/dbus.c
@@ -834,6 +834,8 @@ int bus_init_api(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to set up API bus: %m");
+ (void) bus_track_coldplug(bus, &m->subscribed, /* recursive= */ false, m->deserialized_subscribed);
+ m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
m->api_bus = TAKE_PTR(bus);
return 0;
@@ -986,8 +988,17 @@ static void destroy_bus(Manager *m, sd_bus **bus) {
}
/* Get rid of tracked clients on this bus */
- if (m->subscribed && sd_bus_track_get_bus(m->subscribed) == *bus)
+ if (m->subscribed && sd_bus_track_get_bus(m->subscribed) == *bus) {
+ _cleanup_strv_free_ char **subscribed = NULL;
+ int r;
+
+ r = bus_track_to_strv(m->subscribed, &subscribed);
+ if (r < 0)
+ log_warning_errno(r, "Failed to serialize api subscribers, ignoring: %m");
+ strv_free_and_replace(m->deserialized_subscribed, subscribed);
+
m->subscribed = sd_bus_track_unref(m->subscribed);
+ }
HASHMAP_FOREACH(j, m->jobs)
if (j->bus_track && sd_bus_track_get_bus(j->bus_track) == *bus)
@@ -1046,7 +1057,6 @@ void bus_done(Manager *m) {
assert(!m->subscribed);
- m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
bus_verify_polkit_async_registry_free(m->polkit_registry);
}
@@ -1137,20 +1147,19 @@ void bus_track_serialize(sd_bus_track *t, FILE *f, const char *prefix) {
}
}
-int bus_track_coldplug(Manager *m, sd_bus_track **t, bool recursive, char **l) {
+int bus_track_coldplug(sd_bus *bus, sd_bus_track **t, bool recursive, char **l) {
int r;
- assert(m);
assert(t);
if (strv_isempty(l))
return 0;
- if (!m->api_bus)
+ if (!bus)
return 0;
if (!*t) {
- r = sd_bus_track_new(m->api_bus, t, NULL, NULL);
+ r = sd_bus_track_new(bus, t, NULL, NULL);
if (r < 0)
return r;
}
diff --git a/src/core/dbus.h b/src/core/dbus.h
index 50e7bb400e..3f0d902c89 100644
--- a/src/core/dbus.h
+++ b/src/core/dbus.h
@@ -19,7 +19,7 @@ void bus_done(Manager *m);
int bus_fdset_add_all(Manager *m, FDSet *fds);
void bus_track_serialize(sd_bus_track *t, FILE *f, const char *prefix);
-int bus_track_coldplug(Manager *m, sd_bus_track **t, bool recursive, char **l);
+int bus_track_coldplug(sd_bus *bus, sd_bus_track **t, bool recursive, char **l);
int bus_foreach_bus(Manager *m, sd_bus_track *subscribed2, int (*send_message)(sd_bus *bus, void *userdata), void *userdata);
diff --git a/src/core/manager.c b/src/core/manager.c
index daeaa641d7..6c67780c99 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -1560,6 +1560,9 @@ Manager* manager_free(Manager *m) {
free(m->switch_root);
free(m->switch_root_init);
+ sd_bus_track_unref(m->subscribed);
+ strv_free(m->deserialized_subscribed);
+
free(m->default_smack_process_label);
rlimit_free_all(m->rlimit);
@@ -1862,7 +1865,7 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo
manager_setup_bus(m);
/* Now that we are connected to all possible buses, let's deserialize who is tracking us. */
- r = bus_track_coldplug(m, &m->subscribed, false, m->deserialized_subscribed);
+ r = bus_track_coldplug(m->api_bus, &m->subscribed, false, m->deserialized_subscribed);
if (r < 0)
log_warning_errno(r, "Failed to deserialized tracked clients, ignoring: %m");
m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
@@ -3412,9 +3415,6 @@ int manager_reload(Manager *m) {
/* Clean up runtime objects no longer referenced */
manager_vacuum(m);
- /* Clean up deserialized tracked clients */
- m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
-
/* Consider the reload process complete now. */
assert(m->n_reloading > 0);
m->n_reloading--;
diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c
index d09ec5148d..9293f10fdc 100644
--- a/src/shared/bus-util.c
+++ b/src/shared/bus-util.c
@@ -495,6 +495,28 @@ int bus_track_add_name_many(sd_bus_track *t, char **l) {
return r;
}
+int bus_track_to_strv(sd_bus_track *t, char ***ret) {
+ _cleanup_strv_free_ char **subscribed = NULL;
+ int r = 0;
+
+ assert(ret);
+
+ for (const char *n = sd_bus_track_first(t); n; n = sd_bus_track_next(t)) {
+ r = sd_bus_track_count_name(t, n);
+ if (r < 0)
+ return r;
+
+ for (int j = 0; j < r; j++) {
+ r = strv_extend(&subscribed, n);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ *ret = TAKE_PTR(subscribed);
+ return r;
+}
+
int bus_open_system_watch_bind_with_description(sd_bus **ret, const char *description) {
_cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL;
const char *e;
diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h
index 955cdcb57c..e1fdf2ef48 100644
--- a/src/shared/bus-util.h
+++ b/src/shared/bus-util.h
@@ -52,6 +52,7 @@ int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id,
int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external);
int bus_track_add_name_many(sd_bus_track *t, char **l);
+int bus_track_to_strv(sd_bus_track *t, char ***ret);
int bus_open_system_watch_bind_with_description(sd_bus **ret, const char *description);
static inline int bus_open_system_watch_bind(sd_bus **ret) {

View File

@ -0,0 +1,94 @@
From 435d53448b8c427dc1b61b4d27342fb593185acf Mon Sep 17 00:00:00 2001
From: Ronan Pigott <ronan@rjp.ie>
Date: Wed, 11 Dec 2024 12:47:10 -0700
Subject: [PATCH] manager: s/deserialized_subscribed/subscribed_as_strv
Now that this field may get populated at runtime, the deserialized name
is misleading. Change the name to reflect its updated purpose.
(cherry picked from commit e1315a621ae26473fcc9cd0d6013836f5f498d40)
Related: RHEL-31756
---
src/core/dbus.c | 6 +++---
src/core/manager-serialize.c | 2 +-
src/core/manager.c | 6 +++---
src/core/manager.h | 2 +-
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/core/dbus.c b/src/core/dbus.c
index 12a6f2fa27..7655427f38 100644
--- a/src/core/dbus.c
+++ b/src/core/dbus.c
@@ -834,8 +834,8 @@ int bus_init_api(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to set up API bus: %m");
- (void) bus_track_coldplug(bus, &m->subscribed, /* recursive= */ false, m->deserialized_subscribed);
- m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
+ (void) bus_track_coldplug(bus, &m->subscribed, /* recursive= */ false, m->subscribed_as_strv);
+ m->subscribed_as_strv = strv_free(m->subscribed_as_strv);
m->api_bus = TAKE_PTR(bus);
return 0;
@@ -995,7 +995,7 @@ static void destroy_bus(Manager *m, sd_bus **bus) {
r = bus_track_to_strv(m->subscribed, &subscribed);
if (r < 0)
log_warning_errno(r, "Failed to serialize api subscribers, ignoring: %m");
- strv_free_and_replace(m->deserialized_subscribed, subscribed);
+ strv_free_and_replace(m->subscribed_as_strv, subscribed);
m->subscribed = sd_bus_track_unref(m->subscribed);
}
diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c
index f3b2d7ee16..a87d490219 100644
--- a/src/core/manager-serialize.c
+++ b/src/core/manager-serialize.c
@@ -529,7 +529,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
(void) exec_runtime_deserialize_one(m, val, fds);
else if ((val = startswith(l, "subscribed="))) {
- if (strv_extend(&m->deserialized_subscribed, val) < 0)
+ if (strv_extend(&m->subscribed_as_strv, val) < 0)
return -ENOMEM;
} else if ((val = startswith(l, "varlink-server-socket-address="))) {
if (!m->varlink_server && MANAGER_IS_SYSTEM(m)) {
diff --git a/src/core/manager.c b/src/core/manager.c
index 6c67780c99..45676d3def 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -1561,7 +1561,7 @@ Manager* manager_free(Manager *m) {
free(m->switch_root_init);
sd_bus_track_unref(m->subscribed);
- strv_free(m->deserialized_subscribed);
+ strv_free(m->subscribed_as_strv);
free(m->default_smack_process_label);
@@ -1865,10 +1865,10 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo
manager_setup_bus(m);
/* Now that we are connected to all possible buses, let's deserialize who is tracking us. */
- r = bus_track_coldplug(m->api_bus, &m->subscribed, false, m->deserialized_subscribed);
+ r = bus_track_coldplug(m->api_bus, &m->subscribed, false, m->subscribed_as_strv);
if (r < 0)
log_warning_errno(r, "Failed to deserialized tracked clients, ignoring: %m");
- m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
+ m->subscribed_as_strv = strv_free(m->subscribed_as_strv);
r = manager_varlink_init(m);
if (r < 0)
diff --git a/src/core/manager.h b/src/core/manager.h
index 86e7e40989..a96ba7bf9d 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -276,7 +276,7 @@ struct Manager {
considered subscribes, since they last for very short only,
and it is much simpler that way. */
sd_bus_track *subscribed;
- char **deserialized_subscribed;
+ char **subscribed_as_strv;
/* This is used during reloading: before the reload we queue
* the reply message here, and afterwards we send it */

View File

@ -0,0 +1,55 @@
From da904dede254800cdbac51905db9423f8f88fefa Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Sat, 11 Jan 2025 16:26:55 +0100
Subject: [PATCH] bus-util: do not reset the count returned by
sd_bus_track_count_name()
Follow-up for 8402ca04d1a063c3d8a9e3d5c16df8bb8778ae98
While at it, turn the retval check for sd_bus_track_count_name()
into assertion, given we're working with already established tracks
(service_name_is_valid() should never yield false in this case).
Addresses https://github.com/systemd/systemd/pull/35406#discussion_r1912066774
(cherry picked from commit 33eeea4128f31df7ab4bd8866b582062d70114ae)
Related: RHEL-31756
---
src/shared/bus-util.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c
index 9293f10fdc..5e6d17d201 100644
--- a/src/shared/bus-util.c
+++ b/src/shared/bus-util.c
@@ -497,16 +497,15 @@ int bus_track_add_name_many(sd_bus_track *t, char **l) {
int bus_track_to_strv(sd_bus_track *t, char ***ret) {
_cleanup_strv_free_ char **subscribed = NULL;
- int r = 0;
+ int r;
assert(ret);
for (const char *n = sd_bus_track_first(t); n; n = sd_bus_track_next(t)) {
- r = sd_bus_track_count_name(t, n);
- if (r < 0)
- return r;
+ int c = sd_bus_track_count_name(t, n);
+ assert(c >= 0);
- for (int j = 0; j < r; j++) {
+ for (int j = 0; j < c; j++) {
r = strv_extend(&subscribed, n);
if (r < 0)
return r;
@@ -514,7 +513,7 @@ int bus_track_to_strv(sd_bus_track *t, char ***ret) {
}
*ret = TAKE_PTR(subscribed);
- return r;
+ return 0;
}
int bus_open_system_watch_bind_with_description(sd_bus **ret, const char *description) {

View File

@ -0,0 +1,31 @@
From 02e5c4076ce3981fb2b0c7830197b2f13e225efe Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Mon, 13 Jan 2025 17:30:51 +0100
Subject: [PATCH] core/manager: restore bus track deserialization cleanup in
manager_reload()
There's zero explanation why it got (spuriously) removed in
8402ca04d1a063c3d8a9e3d5c16df8bb8778ae98...
(cherry picked from commit 34f4b817f67b002eae7e2c09b19bf4b66c4791b6)
Related: RHEL-31756
---
src/core/manager.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/core/manager.c b/src/core/manager.c
index 45676d3def..92f283d33d 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -3409,6 +3409,10 @@ int manager_reload(Manager *m) {
(void) manager_setup_cgroups_agent(m);
(void) manager_setup_user_lookup_fd(m);
+ /* Clean up deserialized bus track information. They're never consumed during reload (as opposed to
+ * reexec) since we do not disconnect from the bus. */
+ m->subscribed_as_strv = strv_free(m->subscribed_as_strv);
+
/* Third, fire things up! */
manager_coldplug(m);

View File

@ -0,0 +1,32 @@
From 8e451eebfe6f37bacb02543d9a38c4ddaa6f8400 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Sat, 11 Jan 2025 18:38:49 +0100
Subject: [PATCH] core/manager: drop duplicate bus track deserialization
bus_init_api() now does this internally
(after 8402ca04d1a063c3d8a9e3d5c16df8bb8778ae98).
(cherry picked from commit af0e10354e567bfd0b9521376b2aad55f12a4e3d)
Related: RHEL-31756
---
src/core/manager.c | 6 ------
1 file changed, 6 deletions(-)
diff --git a/src/core/manager.c b/src/core/manager.c
index 92f283d33d..653b0c2d22 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -1864,12 +1864,6 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo
/* Connect to the bus if we are good for it */
manager_setup_bus(m);
- /* Now that we are connected to all possible buses, let's deserialize who is tracking us. */
- r = bus_track_coldplug(m->api_bus, &m->subscribed, false, m->subscribed_as_strv);
- if (r < 0)
- log_warning_errno(r, "Failed to deserialized tracked clients, ignoring: %m");
- m->subscribed_as_strv = strv_free(m->subscribed_as_strv);
-
r = manager_varlink_init(m);
if (r < 0)
log_warning_errno(r, "Failed to set up Varlink, ignoring: %m");

View File

@ -0,0 +1,102 @@
From f18c7dd568ba87c6aa9a045878c0a9ef7c23208e Mon Sep 17 00:00:00 2001
From: Michal Sekletar <msekleta@redhat.com>
Date: Thu, 31 Jul 2025 18:26:09 +0200
Subject: [PATCH] sd-bus/bus-track: use install_callback in
sd_bus_track_add_name()
Previously we didn't provide any install_callback to
sd_bus_add_match_async() so in case AddMatch() method call timed out we
destroyed the bus connection. This seems overly aggressive and simply
updating the sd_bus_track object accordingly should be enough.
Follow-up for 37ce3fd2b7dd8f81f6f4bca2003961a92b2963dc.
Fixes #32381
(cherry picked from commit dcf42d1ee21222ee698e5e0ab3ecf3411b63da40)
Related: RHEL-31756
---
src/libsystemd/sd-bus/bus-track.c | 32 +++++++++++++++++++++++++++----
1 file changed, 28 insertions(+), 4 deletions(-)
diff --git a/src/libsystemd/sd-bus/bus-track.c b/src/libsystemd/sd-bus/bus-track.c
index f9c59a1007..276e177d2b 100644
--- a/src/libsystemd/sd-bus/bus-track.c
+++ b/src/libsystemd/sd-bus/bus-track.c
@@ -3,6 +3,7 @@
#include "sd-bus.h"
#include "alloc-util.h"
+#include "bus-error.h"
#include "bus-internal.h"
#include "bus-track.h"
#include "string-util.h"
@@ -11,6 +12,7 @@ struct track_item {
unsigned n_ref;
char *name;
sd_bus_slot *slot;
+ sd_bus_track *track;
};
struct sd_bus_track {
@@ -163,18 +165,39 @@ static sd_bus_track *track_free(sd_bus_track *track) {
DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_bus_track, sd_bus_track, track_free);
-static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
- sd_bus_track *track = ASSERT_PTR(userdata);
+static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error) {
+ struct track_item *item = ASSERT_PTR(userdata);
const char *name;
int r;
assert(message);
+ assert(item->track);
r = sd_bus_message_read(message, "sss", &name, NULL, NULL);
if (r < 0)
return 0;
- bus_track_remove_name_fully(track, name);
+ bus_track_remove_name_fully(item->track, name);
+ return 0;
+}
+
+static int name_owner_changed_install_callback(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error) {
+ struct track_item *item = ASSERT_PTR(userdata);
+ const sd_bus_error *e;
+ int r;
+
+ assert(message);
+ assert(item->track);
+ assert(item->name);
+
+ e = sd_bus_message_get_error(message);
+ if (!e)
+ return 0;
+
+ r = sd_bus_error_get_errno(e);
+ log_debug_errno(r, "Failed to install match for tracking name '%s': %s", item->name, bus_error_message(e, r));
+
+ bus_track_remove_name_fully(item->track, item->name);
return 0;
}
@@ -216,6 +239,7 @@ _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
*n = (struct track_item) {
.n_ref = 1,
+ .track = track,
};
n->name = strdup(name);
@@ -227,7 +251,7 @@ _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */
- r = sd_bus_add_match_async(track->bus, &n->slot, match, on_name_owner_changed, NULL, track);
+ r = sd_bus_add_match_async(track->bus, &n->slot, match, on_name_owner_changed, name_owner_changed_install_callback, n);
if (r < 0) {
bus_track_add_to_queue(track);
return r;

View File

@ -0,0 +1,38 @@
From 9f940102616443911fff789aae63546c1da3138e Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Tue, 18 Feb 2025 21:15:08 +0000
Subject: [PATCH] shell completion: add kernel-identify/inspect verbs for
bootctl
Follow-up for a05255981ba5b04f1cf54ea656fbce1dfd9c3a68
Follow-up for 3e0a3a0259324b4c40a9a62c8506fe683cd0273b
(cherry picked from commit 6a6d4c3f3c123a1cbb6770f1cae8c130a48333e1)
Resolves: RHEL-108576
---
shell-completion/bash/bootctl | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/shell-completion/bash/bootctl b/shell-completion/bash/bootctl
index 0b7cef7871..328289e0cf 100644
--- a/shell-completion/bash/bootctl
+++ b/shell-completion/bash/bootctl
@@ -70,6 +70,7 @@ _bootctl() {
[STANDALONE]='help status install update remove is-installed random-seed systemd-efi-options list set-timeout set-timeout-oneshot'
[BOOTENTRY]='set-default set-oneshot'
[BOOLEAN]='reboot-to-firmware'
+ [FILE]='kernel-identify kernel-inspect'
)
for ((i=0; i < COMP_CWORD; i++)); do
@@ -100,6 +101,9 @@ _bootctl() {
fi
elif __contains_word "$verb" ${VERBS[BOOLEAN]}; then
comps="yes no"
+ elif __contains_word "$verb" ${VERBS[FILE]}; then
+ comps=$( compgen -A file -- "$cur" )
+ compopt -o filenames
fi
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )

View File

@ -0,0 +1,444 @@
From 05b4623cb23c6f083a5bee9769e5cd22d8ff4e16 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Sun, 12 Feb 2023 05:30:49 +0900
Subject: [PATCH] test: add tests for format_timestamp() and parse_timestamp()
with various timezone
(cherry picked from commit 8b51c41fd0796b1299f3b7f2f11eaf4efae8c2db)
Related: RHEL-109488
---
src/test/test-time-util.c | 378 ++++++++++++++++++++++++++++++++++++--
1 file changed, 366 insertions(+), 12 deletions(-)
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index 6b546fb9f5..3fbf7bf3d0 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -1,6 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "dirent-util.h"
#include "env-util.h"
+#include "fd-util.h"
+#include "fileio.h"
#include "random-util.h"
#include "serialize.h"
#include "string-util.h"
@@ -8,6 +11,8 @@
#include "tests.h"
#include "time-util.h"
+#define TRIAL 100u
+
TEST(parse_sec) {
usec_t u;
@@ -334,11 +339,11 @@ TEST(usec_sub_signed) {
}
TEST(format_timestamp) {
- for (unsigned i = 0; i < 100; i++) {
+ for (unsigned i = 0; i < TRIAL; i++) {
char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)];
usec_t x, y;
- x = random_u64_range(2147483600 * USEC_PER_SEC) + 1;
+ x = random_u64_range(USEC_TIMESTAMP_FORMATTABLE_MAX - USEC_PER_SEC) + USEC_PER_SEC;
assert_se(format_timestamp(buf, sizeof(buf), x));
log_debug("%s", buf);
@@ -377,20 +382,91 @@ TEST(format_timestamp) {
}
}
+static void test_format_timestamp_impl(usec_t x) {
+ bool success;
+ const char *xx, *yy;
+ usec_t y;
+
+ xx = FORMAT_TIMESTAMP(x);
+ assert_se(xx);
+ assert_se(parse_timestamp(xx, &y) >= 0);
+ yy = FORMAT_TIMESTAMP(y);
+ assert_se(yy);
+
+ success = (x / USEC_PER_SEC == y / USEC_PER_SEC) && streq(xx, yy);
+ log_full(success ? LOG_DEBUG : LOG_ERR, "@" USEC_FMT " → %s → @" USEC_FMT " → %s", x, xx, y, yy);
+ assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC);
+ assert_se(streq(xx, yy));
+}
+
+static void test_format_timestamp_loop(void) {
+ test_format_timestamp_impl(USEC_PER_SEC);
+
+ for (unsigned i = 0; i < TRIAL; i++) {
+ usec_t x;
+
+ x = random_u64_range(USEC_TIMESTAMP_FORMATTABLE_MAX - USEC_PER_SEC) + USEC_PER_SEC;
+ test_format_timestamp_impl(x);
+ }
+}
+
TEST(FORMAT_TIMESTAMP) {
- for (unsigned i = 0; i < 100; i++) {
- _cleanup_free_ char *buf;
- usec_t x, y;
+ test_format_timestamp_loop();
+}
- x = random_u64_range(2147483600 * USEC_PER_SEC) + 1;
+static void test_format_timestamp_with_tz_one(const char *name1, const char *name2) {
+ _cleanup_free_ char *buf = NULL, *tz = NULL;
+ const char *name, *saved_tz;
- /* strbuf() is to test the macro in an argument to a function call. */
- assert_se(buf = strdup(FORMAT_TIMESTAMP(x)));
- log_debug("%s", buf);
- assert_se(parse_timestamp(buf, &y) >= 0);
- assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC);
+ if (name2)
+ assert_se(buf = path_join(name1, name2));
+ name = buf ?: name1;
+
+ if (!timezone_is_valid(name, LOG_DEBUG))
+ return;
- assert_se(streq(FORMAT_TIMESTAMP(x), buf));
+ log_info("/* %s(%s) */", __func__, name);
+
+ saved_tz = getenv("TZ");
+
+ assert_se(tz = strjoin(":", name));
+ assert_se(setenv("TZ", tz, 1) >= 0);
+ tzset();
+ log_debug("%s: tzname[0]=%s, tzname[1]=%s", tz, strempty(tzname[0]), strempty(tzname[1]));
+
+ test_format_timestamp_loop();
+
+ assert_se(set_unset_env("TZ", saved_tz, true) == 0);
+ tzset();
+}
+
+TEST(FORMAT_TIMESTAMP_with_tz) {
+ if (!slow_tests_enabled())
+ return (void) log_tests_skipped("slow tests are disabled");
+
+ _cleanup_closedir_ DIR *dir = opendir("/usr/share/zoneinfo");
+ if (!dir)
+ return (void) log_tests_skipped_errno(errno, "Failed to open /usr/share/zoneinfo");
+
+ FOREACH_DIRENT(de, dir, break) {
+ if (de->d_type == DT_REG)
+ test_format_timestamp_with_tz_one(de->d_name, NULL);
+
+ else if (de->d_type == DT_DIR) {
+ if (streq(de->d_name, "right"))
+ /* The test does not support timezone with leap second info. */
+ continue;
+
+ _cleanup_closedir_ DIR *subdir = xopendirat(dirfd(dir), de->d_name, 0);
+ if (!subdir) {
+ log_notice_errno(errno, "Failed to open /usr/share/zoneinfo/%s, ignoring: %m", de->d_name);
+ continue;
+ }
+
+ FOREACH_DIRENT(subde, subdir, break)
+ if (subde->d_type == DT_REG)
+ test_format_timestamp_with_tz_one(de->d_name, subde->d_name);
+ }
}
}
@@ -490,6 +566,219 @@ TEST(format_timestamp_utc) {
test_format_timestamp_utc_one(USEC_INFINITY, NULL);
}
+static void test_parse_timestamp_one(const char *str, usec_t max_diff, usec_t expected) {
+ usec_t usec;
+
+ log_debug("/* %s(%s) */", __func__, str);
+ assert_se(parse_timestamp(str, &usec) >= 0);
+ assert_se(usec >= expected);
+ assert_se(usec_sub_unsigned(usec, expected) <= max_diff);
+}
+
+TEST(parse_timestamp) {
+ usec_t today, now_usec;
+
+ /* UTC */
+ test_parse_timestamp_one("Thu 1970-01-01 00:01 UTC", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Thu 1970-01-01 00:00:01 UTC", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Thu 1970-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Thu 1970-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("Thu 70-01-01 00:01 UTC", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Thu 70-01-01 00:00:01 UTC", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Thu 70-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Thu 70-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("1970-01-01 00:01 UTC", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("1970-01-01 00:00:01 UTC", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("1970-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("1970-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("70-01-01 00:01 UTC", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("70-01-01 00:00:01 UTC", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("70-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("70-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000);
+
+ if (timezone_is_valid("Asia/Tokyo", LOG_DEBUG)) {
+ /* Asia/Tokyo (+0900) */
+ test_parse_timestamp_one("Thu 1970-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Thu 1970-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Thu 1970-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Thu 1970-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("Thu 70-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Thu 70-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Thu 70-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Thu 70-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("1970-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("1970-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("1970-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("1970-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("70-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("70-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("70-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("70-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
+
+ const char *saved_tz = getenv("TZ");
+ assert_se(setenv("TZ", ":Asia/Tokyo", 1) >= 0);
+
+ /* JST (+0900) */
+ test_parse_timestamp_one("Thu 1970-01-01 09:01 JST", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Thu 1970-01-01 09:00:01 JST", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Thu 1970-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Thu 1970-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("Thu 70-01-01 09:01 JST", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Thu 70-01-01 09:00:01 JST", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Thu 70-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Thu 70-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("1970-01-01 09:01 JST", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("1970-01-01 09:00:01 JST", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("1970-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("1970-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("70-01-01 09:01 JST", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("70-01-01 09:00:01 JST", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("70-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("70-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000);
+
+ assert_se(set_unset_env("TZ", saved_tz, true) == 0);
+ }
+
+ if (timezone_is_valid("America/New_York", LOG_DEBUG)) {
+ /* America/New_York (-0500) */
+ test_parse_timestamp_one("Wed 1969-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Wed 1969-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Wed 1969-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Wed 1969-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("Wed 69-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Wed 69-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Wed 69-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Wed 69-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("1969-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("1969-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("1969-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("1969-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("69-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("69-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("69-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("69-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000);
+
+ const char *saved_tz = getenv("TZ");
+ assert_se(setenv("TZ", ":America/New_York", 1) >= 0);
+
+ /* EST (-0500) */
+ test_parse_timestamp_one("Wed 1969-12-31 19:01 EST", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Wed 1969-12-31 19:00:01 EST", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Wed 1969-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Wed 1969-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("Wed 69-12-31 19:01 EST", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Wed 69-12-31 19:00:01 EST", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Wed 69-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Wed 69-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("1969-12-31 19:01 EST", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("1969-12-31 19:00:01 EST", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("1969-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("1969-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("69-12-31 19:01 EST", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("69-12-31 19:00:01 EST", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("69-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("69-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000);
+
+ assert_se(set_unset_env("TZ", saved_tz, true) == 0);
+ }
+
+ /* -06 */
+ test_parse_timestamp_one("Wed 1969-12-31 18:01 -06", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Wed 1969-12-31 18:00:01 -06", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Wed 1969-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Wed 1969-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("Wed 69-12-31 18:01 -06", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Wed 69-12-31 18:00:01 -06", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Wed 69-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Wed 69-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("1969-12-31 18:01 -06", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("1969-12-31 18:00:01 -06", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("1969-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("1969-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("69-12-31 18:01 -06", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("69-12-31 18:00:01 -06", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("69-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("69-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000);
+
+ /* -0600 */
+ test_parse_timestamp_one("Wed 1969-12-31 18:01 -0600", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Wed 1969-12-31 18:00:01 -0600", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Wed 1969-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Wed 1969-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("Wed 69-12-31 18:01 -0600", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Wed 69-12-31 18:00:01 -0600", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Wed 69-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Wed 69-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("1969-12-31 18:01 -0600", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("1969-12-31 18:00:01 -0600", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("1969-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("1969-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("69-12-31 18:01 -0600", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("69-12-31 18:00:01 -0600", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("69-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("69-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000);
+
+ /* -06:00 */
+ test_parse_timestamp_one("Wed 1969-12-31 18:01 -06:00", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Wed 1969-12-31 18:00:01 -06:00", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Wed 1969-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Wed 1969-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("Wed 69-12-31 18:01 -06:00", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("Wed 69-12-31 18:00:01 -06:00", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("Wed 69-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("Wed 69-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("1969-12-31 18:01 -06:00", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("1969-12-31 18:00:01 -06:00", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("1969-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("1969-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000);
+
+ test_parse_timestamp_one("69-12-31 18:01 -06:00", 0, USEC_PER_MINUTE);
+ test_parse_timestamp_one("69-12-31 18:00:01 -06:00", 0, USEC_PER_SEC);
+ test_parse_timestamp_one("69-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("69-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000);
+
+ /* without date */
+ assert_se(parse_timestamp("today", &today) == 0);
+ test_parse_timestamp_one("00:01", 0, today + USEC_PER_MINUTE);
+ test_parse_timestamp_one("00:00:01", 0, today + USEC_PER_SEC);
+ test_parse_timestamp_one("00:00:01.001", 0, today + USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("00:00:01.0010", 0, today + USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("tomorrow", 0, today + USEC_PER_DAY);
+ test_parse_timestamp_one("yesterday", 0, today - USEC_PER_DAY);
+
+ /* relative */
+ assert_se(parse_timestamp("now", &now_usec) == 0);
+ test_parse_timestamp_one("+5hours", USEC_PER_MINUTE, now_usec + 5 * USEC_PER_HOUR);
+ if (now_usec >= 10 * USEC_PER_DAY)
+ test_parse_timestamp_one("-10days", USEC_PER_MINUTE, now_usec - 10 * USEC_PER_DAY);
+ test_parse_timestamp_one("2weeks left", USEC_PER_MINUTE, now_usec + 2 * USEC_PER_WEEK);
+ if (now_usec >= 30 * USEC_PER_MINUTE)
+ test_parse_timestamp_one("30minutes ago", USEC_PER_MINUTE, now_usec - 30 * USEC_PER_MINUTE);
+}
+
TEST(deserialize_dual_timestamp) {
int r;
dual_timestamp t;
@@ -613,6 +902,71 @@ TEST(map_clock_usec) {
}
}
+static void test_timezone_offset_change_one(const char *utc, const char *pretty) {
+ usec_t x, y, z;
+ char *s;
+
+ assert_se(parse_timestamp(utc, &x) >= 0);
+
+ s = FORMAT_TIMESTAMP_STYLE(x, TIMESTAMP_UTC);
+ assert_se(parse_timestamp(s, &y) >= 0);
+ log_debug("%s -> " USEC_FMT " -> %s -> " USEC_FMT, utc, x, s, y);
+ assert_se(streq(s, utc));
+ assert_se(x == y);
+
+ assert_se(parse_timestamp(pretty, &y) >= 0);
+ s = FORMAT_TIMESTAMP_STYLE(y, TIMESTAMP_PRETTY);
+ assert_se(parse_timestamp(s, &z) >= 0);
+ log_debug("%s -> " USEC_FMT " -> %s -> " USEC_FMT, pretty, y, s, z);
+ assert_se(streq(s, pretty));
+ assert_se(x == y);
+ assert_se(x == z);
+}
+
+TEST(timezone_offset_change) {
+ const char *tz = getenv("TZ");
+
+ /* See issue #26370. */
+
+ if (timezone_is_valid("Africa/Casablanca", LOG_DEBUG)) {
+ assert_se(setenv("TZ", ":Africa/Casablanca", 1) >= 0);
+ tzset();
+ log_debug("Africa/Casablanca: tzname[0]=%s, tzname[1]=%s", strempty(tzname[0]), strempty(tzname[1]));
+
+ test_timezone_offset_change_one("Sun 2015-10-25 01:59:59 UTC", "Sun 2015-10-25 02:59:59 +01");
+ test_timezone_offset_change_one("Sun 2015-10-25 02:00:00 UTC", "Sun 2015-10-25 02:00:00 +00");
+ test_timezone_offset_change_one("Sun 2018-06-17 01:59:59 UTC", "Sun 2018-06-17 01:59:59 +00");
+ test_timezone_offset_change_one("Sun 2018-06-17 02:00:00 UTC", "Sun 2018-06-17 03:00:00 +01");
+ test_timezone_offset_change_one("Sun 2018-10-28 01:59:59 UTC", "Sun 2018-10-28 02:59:59 +01");
+ test_timezone_offset_change_one("Sun 2018-10-28 02:00:00 UTC", "Sun 2018-10-28 03:00:00 +01");
+ }
+
+ if (timezone_is_valid("Asia/Atyrau", LOG_DEBUG)) {
+ assert_se(setenv("TZ", ":Asia/Atyrau", 1) >= 0);
+ tzset();
+ log_debug("Asia/Atyrau: tzname[0]=%s, tzname[1]=%s", strempty(tzname[0]), strempty(tzname[1]));
+
+ test_timezone_offset_change_one("Sat 2004-03-27 21:59:59 UTC", "Sun 2004-03-28 01:59:59 +04");
+ test_timezone_offset_change_one("Sat 2004-03-27 22:00:00 UTC", "Sun 2004-03-28 03:00:00 +05");
+ test_timezone_offset_change_one("Sat 2004-10-30 21:59:59 UTC", "Sun 2004-10-31 02:59:59 +05");
+ test_timezone_offset_change_one("Sat 2004-10-30 22:00:00 UTC", "Sun 2004-10-31 03:00:00 +05");
+ }
+
+ if (timezone_is_valid("Chile/EasterIsland", LOG_DEBUG)) {
+ assert_se(setenv("TZ", ":Chile/EasterIsland", 1) >= 0);
+ tzset();
+ log_debug("Chile/EasterIsland: tzname[0]=%s, tzname[1]=%s", strempty(tzname[0]), strempty(tzname[1]));
+
+ test_timezone_offset_change_one("Sun 1981-10-11 03:59:59 UTC", "Sat 1981-10-10 20:59:59 -07");
+ test_timezone_offset_change_one("Sun 1981-10-11 04:00:00 UTC", "Sat 1981-10-10 22:00:00 -06");
+ test_timezone_offset_change_one("Sun 1982-03-14 02:59:59 UTC", "Sat 1982-03-13 20:59:59 -06");
+ test_timezone_offset_change_one("Sun 1982-03-14 03:00:00 UTC", "Sat 1982-03-13 21:00:00 -06");
+ }
+
+ assert_se(set_unset_env("TZ", tz, true) == 0);
+ tzset();
+}
+
static int intro(void) {
log_info("realtime=" USEC_FMT "\n"
"monotonic=" USEC_FMT "\n"

View File

@ -0,0 +1,52 @@
From c7a62e108ffbe41ccf1bb5fba4b5a37daf317939 Mon Sep 17 00:00:00 2001
From: David Tardon <dtardon@redhat.com>
Date: Fri, 15 Aug 2025 15:12:14 +0200
Subject: [PATCH] test-time-util: disable failing tests
Presumably they depend on fixes that we don't have yet. And I don't want
to backport stuff not relevant to the "--when" feature as a part of this
PR.
RHEL-only: ci
Related: RHEL-109488
---
src/test/test-time-util.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index 3fbf7bf3d0..f2acb6159a 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -697,6 +697,7 @@ TEST(parse_timestamp) {
assert_se(set_unset_env("TZ", saved_tz, true) == 0);
}
+#if 0
/* -06 */
test_parse_timestamp_one("Wed 1969-12-31 18:01 -06", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 1969-12-31 18:00:01 -06", 0, USEC_PER_SEC);
@@ -759,6 +760,7 @@ TEST(parse_timestamp) {
test_parse_timestamp_one("69-12-31 18:00:01 -06:00", 0, USEC_PER_SEC);
test_parse_timestamp_one("69-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000);
+#endif
/* without date */
assert_se(parse_timestamp("today", &today) == 0);
@@ -941,6 +943,7 @@ TEST(timezone_offset_change) {
test_timezone_offset_change_one("Sun 2018-10-28 02:00:00 UTC", "Sun 2018-10-28 03:00:00 +01");
}
+#if 0
if (timezone_is_valid("Asia/Atyrau", LOG_DEBUG)) {
assert_se(setenv("TZ", ":Asia/Atyrau", 1) >= 0);
tzset();
@@ -962,6 +965,7 @@ TEST(timezone_offset_change) {
test_timezone_offset_change_one("Sun 1982-03-14 02:59:59 UTC", "Sat 1982-03-13 20:59:59 -06");
test_timezone_offset_change_one("Sun 1982-03-14 03:00:00 UTC", "Sat 1982-03-13 21:00:00 -06");
}
+#endif
assert_se(set_unset_env("TZ", tz, true) == 0);
tzset();

View File

@ -0,0 +1,122 @@
From 9010f2b16067fbe974cd1922b596bcd526de07bc Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Fri, 3 Mar 2023 12:09:59 +0900
Subject: [PATCH] test: test parse_timestamp() in various timezone
(cherry picked from commit d8f3ad627c9a857d46d442f8ab722c1efab30d5c)
Related: RHEL-109488
---
src/test/test-time-util.c | 57 +++++++++++++++++++++++++++++----------
1 file changed, 43 insertions(+), 14 deletions(-)
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index f2acb6159a..ee861135c2 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -567,15 +567,17 @@ TEST(format_timestamp_utc) {
}
static void test_parse_timestamp_one(const char *str, usec_t max_diff, usec_t expected) {
- usec_t usec;
+ usec_t usec = USEC_INFINITY;
+ int r;
- log_debug("/* %s(%s) */", __func__, str);
- assert_se(parse_timestamp(str, &usec) >= 0);
+ r = parse_timestamp(str, &usec);
+ log_debug("/* %s(%s): max_diff="USEC_FMT", expected="USEC_FMT", result="USEC_FMT"*/", __func__, str, max_diff, expected, usec);
+ assert_se(r >= 0);
assert_se(usec >= expected);
assert_se(usec_sub_unsigned(usec, expected) <= max_diff);
}
-TEST(parse_timestamp) {
+static void test_parse_timestamp_impl(const char *tz) {
usec_t today, now_usec;
/* UTC */
@@ -620,10 +622,9 @@ TEST(parse_timestamp) {
test_parse_timestamp_one("70-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC);
test_parse_timestamp_one("70-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("70-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
+ }
- const char *saved_tz = getenv("TZ");
- assert_se(setenv("TZ", ":Asia/Tokyo", 1) >= 0);
-
+ if (streq_ptr(tz, "Asia/Tokyo")) {
/* JST (+0900) */
test_parse_timestamp_one("Thu 1970-01-01 09:01 JST", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Thu 1970-01-01 09:00:01 JST", 0, USEC_PER_SEC);
@@ -644,8 +645,6 @@ TEST(parse_timestamp) {
test_parse_timestamp_one("70-01-01 09:00:01 JST", 0, USEC_PER_SEC);
test_parse_timestamp_one("70-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("70-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000);
-
- assert_se(set_unset_env("TZ", saved_tz, true) == 0);
}
if (timezone_is_valid("America/New_York", LOG_DEBUG)) {
@@ -669,10 +668,9 @@ TEST(parse_timestamp) {
test_parse_timestamp_one("69-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC);
test_parse_timestamp_one("69-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000);
+ }
- const char *saved_tz = getenv("TZ");
- assert_se(setenv("TZ", ":America/New_York", 1) >= 0);
-
+ if (streq_ptr(tz, "America/New_York")) {
/* EST (-0500) */
test_parse_timestamp_one("Wed 1969-12-31 19:01 EST", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 1969-12-31 19:00:01 EST", 0, USEC_PER_SEC);
@@ -693,8 +691,6 @@ TEST(parse_timestamp) {
test_parse_timestamp_one("69-12-31 19:00:01 EST", 0, USEC_PER_SEC);
test_parse_timestamp_one("69-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000);
-
- assert_se(set_unset_env("TZ", saved_tz, true) == 0);
}
#if 0
@@ -781,6 +777,39 @@ TEST(parse_timestamp) {
test_parse_timestamp_one("30minutes ago", USEC_PER_MINUTE, now_usec - 30 * USEC_PER_MINUTE);
}
+TEST(parse_timestamp) {
+ test_parse_timestamp_impl(NULL);
+}
+
+static void test_parse_timestamp_with_tz_one(const char *tz) {
+ const char *saved_tz, *colon_tz;
+
+ if (!timezone_is_valid(tz, LOG_DEBUG))
+ return;
+
+ log_info("/* %s(%s) */", __func__, tz);
+
+ saved_tz = getenv("TZ");
+
+ assert_se(colon_tz = strjoina(":", tz));
+ assert_se(setenv("TZ", colon_tz, 1) >= 0);
+ tzset();
+ log_debug("%s: tzname[0]=%s, tzname[1]=%s", tz, strempty(tzname[0]), strempty(tzname[1]));
+
+ test_parse_timestamp_impl(tz);
+
+ assert_se(set_unset_env("TZ", saved_tz, true) == 0);
+ tzset();
+}
+
+TEST(parse_timestamp_with_tz) {
+ _cleanup_strv_free_ char **timezones = NULL;
+
+ assert_se(get_timezones(&timezones) >= 0);
+ STRV_FOREACH(tz, timezones)
+ test_parse_timestamp_with_tz_one(*tz);
+}
+
TEST(deserialize_dual_timestamp) {
int r;
dual_timestamp t;

View File

@ -0,0 +1,47 @@
From 41b7fedf9ed75f6dfa9fec03a70964b897fbf9ba Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Tue, 14 Mar 2023 06:56:17 +0800
Subject: [PATCH] systemctl: logind: add missing asserts
(cherry picked from commit 9071eea01bd26d838bfd793db497efd849ad44da)
Related: RHEL-109488
---
src/systemctl/systemctl-logind.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/systemctl/systemctl-logind.c b/src/systemctl/systemctl-logind.c
index 1c3b68f09f..f910fe6675 100644
--- a/src/systemctl/systemctl-logind.c
+++ b/src/systemctl/systemctl-logind.c
@@ -21,6 +21,8 @@ static int logind_set_wall_message(sd_bus *bus) {
_cleanup_free_ char *m = NULL;
int r;
+ assert(bus);
+
m = strv_join(arg_wall, " ");
if (!m)
return log_oom();
@@ -55,7 +57,10 @@ int logind_reboot(enum action a) {
sd_bus *bus;
int r;
- if (a < 0 || a >= _ACTION_MAX || !actions[a])
+ assert(a >= 0);
+ assert(a < _ACTION_MAX);
+
+ if (!actions[a])
return -EINVAL;
r = acquire_bus(BUS_FULL, &bus);
@@ -106,6 +111,9 @@ int logind_check_inhibitors(enum action a) {
unsigned c = 0;
int r;
+ assert(a >= 0);
+ assert(a < _ACTION_MAX);
+
if (arg_check_inhibitors == 0 || arg_force > 0)
return 0;

View File

@ -0,0 +1,70 @@
From 5085c1c72a52d6c4e8b47d91a6cd08ceec9c49cc Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Sun, 5 Mar 2023 23:11:48 +0800
Subject: [PATCH] systemctl: logind: make logind_schedule_shutdown accept
action as param
(cherry picked from commit 92b00e867844948bdf559758739343c4308570c0)
Related: RHEL-109488
---
src/systemctl/systemctl-compat-halt.c | 2 +-
src/systemctl/systemctl-logind.c | 8 +++++---
src/systemctl/systemctl-logind.h | 2 +-
3 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/systemctl/systemctl-compat-halt.c b/src/systemctl/systemctl-compat-halt.c
index 8a0e4e6294..b9164c27ab 100644
--- a/src/systemctl/systemctl-compat-halt.c
+++ b/src/systemctl/systemctl-compat-halt.c
@@ -149,7 +149,7 @@ int halt_main(void) {
if (arg_force == 0) {
/* always try logind first */
if (arg_when > 0)
- r = logind_schedule_shutdown();
+ r = logind_schedule_shutdown(arg_action);
else {
r = logind_check_inhibitors(arg_action);
if (r < 0)
diff --git a/src/systemctl/systemctl-logind.c b/src/systemctl/systemctl-logind.c
index f910fe6675..068f54e18b 100644
--- a/src/systemctl/systemctl-logind.c
+++ b/src/systemctl/systemctl-logind.c
@@ -291,19 +291,21 @@ int prepare_boot_loader_entry(void) {
#endif
}
-int logind_schedule_shutdown(void) {
-
+int logind_schedule_shutdown(enum action a) {
#if ENABLE_LOGIND
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
const char *action;
sd_bus *bus;
int r;
+ assert(a >= 0);
+ assert(a < _ACTION_MAX);
+
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
- action = action_table[arg_action].verb;
+ action = action_table[a].verb;
if (!action)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Scheduling not supported for this action.");
diff --git a/src/systemctl/systemctl-logind.h b/src/systemctl/systemctl-logind.h
index 925f4559c1..516f74952f 100644
--- a/src/systemctl/systemctl-logind.h
+++ b/src/systemctl/systemctl-logind.h
@@ -10,7 +10,7 @@ int prepare_firmware_setup(void);
int prepare_boot_loader_menu(void);
int prepare_boot_loader_entry(void);
-int logind_schedule_shutdown(void);
+int logind_schedule_shutdown(enum action a);
int logind_cancel_shutdown(void);
int logind_show_shutdown(void);

View File

@ -0,0 +1,262 @@
From 87f8db36eb01b805e7000aeb69ebfaf1c8c323b8 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Sun, 5 Mar 2023 23:27:44 +0800
Subject: [PATCH] systemctl: add option --when for scheduled shutdown
Pass an empty string or "cancel" will cancel the action.
Pass "show" will show the scheduled actions.
Replaces #17258
(cherry picked from commit 1433e1f998465b7acf472c73d58c14e7e2eb3f13)
Resolves: RHEL-109488
---
man/systemctl.xml | 41 ++++++++++++++-----------
src/systemctl/systemctl-logind.c | 26 ++++++++++------
src/systemctl/systemctl-start-special.c | 33 +++++++++++++-------
src/systemctl/systemctl.c | 28 +++++++++++++++++
4 files changed, 89 insertions(+), 39 deletions(-)
diff --git a/man/systemctl.xml b/man/systemctl.xml
index 1df0b158bd..cea6192224 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -1448,6 +1448,9 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<option>--force</option> is specified twice the halt operation is executed by <command>systemctl</command>
itself, and the system manager is not contacted. This means the command should succeed even when the system
manager has crashed.</para>
+
+ <para>If combined with <option>--when=</option>, shutdown will be scheduled after the given timestamp.
+ And <option>--when=cancel</option> will cancel the shutdown.</para>
</listitem>
</varlistentry>
<varlistentry>
@@ -1459,13 +1462,8 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
users. This command is asynchronous; it will return after the power-off operation is enqueued, without
waiting for it to complete.</para>
- <para>If combined with <option>--force</option>, shutdown of all running services is skipped, however all
- processes are killed and all file systems are unmounted or mounted read-only, immediately followed by the
- powering off. If <option>--force</option> is specified twice, the operation is immediately executed without
- terminating any processes or unmounting any file systems. This may result in data loss. Note that when
- <option>--force</option> is specified twice the power-off operation is executed by
- <command>systemctl</command> itself, and the system manager is not contacted. This means the command should
- succeed even when the system manager has crashed.</para>
+ <para>This command honors <option>--force</option> and <option>--when=</option> in a similar way
+ as <command>halt</command>.</para>
</listitem>
</varlistentry>
<varlistentry>
@@ -1479,14 +1477,6 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
users. This command is asynchronous; it will return after the reboot operation is enqueued,
without waiting for it to complete.</para>
- <para>If combined with <option>--force</option>, shutdown of all running services is skipped, however all
- processes are killed and all file systems are unmounted or mounted read-only, immediately followed by the
- reboot. If <option>--force</option> is specified twice, the operation is immediately executed without
- terminating any processes or unmounting any file systems. This may result in data loss. Note that when
- <option>--force</option> is specified twice the reboot operation is executed by
- <command>systemctl</command> itself, and the system manager is not contacted. This means the command should
- succeed even when the system manager has crashed.</para>
-
<para>If the switch <option>--reboot-argument=</option> is given, it will be passed as the optional
argument to the <citerefentry><refentrytitle>reboot</refentrytitle><manvolnum>2</manvolnum></citerefentry>
system call.</para>
@@ -1494,6 +1484,9 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<para>Options <option>--boot-loader-entry=</option>, <option>--boot-loader-menu=</option>, and
<option>--firmware-setup</option> can be used to select what to do <emphasis>after</emphasis> the
reboot. See the descriptions of those options for details.</para>
+
+ <para>This command honors <option>--force</option> and <option>--when=</option> in a similar way
+ as <command>halt</command>.</para>
</listitem>
</varlistentry>
@@ -1506,9 +1499,8 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
asynchronous; it will return after the reboot operation is enqueued, without waiting for it to
complete.</para>
- <para>If combined with <option>--force</option>, shutdown of all running services is skipped, however all
- processes are killed and all file systems are unmounted or mounted read-only, immediately followed by the
- reboot.</para>
+ <para>This command honors <option>--force</option> and <option>--when=</option> in a similar way
+ as <command>halt</command>.</para>
</listitem>
</varlistentry>
@@ -2420,6 +2412,19 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<listitem><para>When used with <command>bind</command>, creates a read-only bind mount.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--when=</option></term>
+
+ <listitem>
+ <para>When used with <command>halt</command>, <command>poweroff</command>, <command>reboot</command>
+ or <command>kexec</command>, schedule the action to be performed at the given timestamp,
+ which should adhere to the syntax documented in <citerefentry
+ project='man-pages'><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+ section "PARSING TIMESTAMPS". Specially, if <literal>show</literal> is given, the currently scheduled
+ action will be shown, which can be canceled by passing an empty string or <literal>cancel</literal>.</para>
+ </listitem>
+ </varlistentry>
+
<xi:include href="user-system-options.xml" xpointer="host" />
<xi:include href="user-system-options.xml" xpointer="machine" />
diff --git a/src/systemctl/systemctl-logind.c b/src/systemctl/systemctl-logind.c
index 068f54e18b..fd8ca09de8 100644
--- a/src/systemctl/systemctl-logind.c
+++ b/src/systemctl/systemctl-logind.c
@@ -356,7 +356,7 @@ int logind_show_shutdown(void) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
sd_bus *bus;
- const char *action = NULL;
+ const char *action, *pretty_action;
uint64_t elapse;
int r;
@@ -376,17 +376,23 @@ int logind_show_shutdown(void) {
return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "No scheduled shutdown.");
if (STR_IN_SET(action, "halt", "poweroff", "exit"))
- action = "Shutdown";
+ pretty_action = "Shutdown";
else if (streq(action, "kexec"))
- action = "Reboot via kexec";
+ pretty_action = "Reboot via kexec";
else if (streq(action, "reboot"))
- action = "Reboot";
-
- /* If we don't recognize the action string, we'll show it as-is */
-
- log_info("%s scheduled for %s, use 'shutdown -c' to cancel.",
- action,
- FORMAT_TIMESTAMP_STYLE(elapse, arg_timestamp_style));
+ pretty_action = "Reboot";
+ else /* If we don't recognize the action string, we'll show it as-is */
+ pretty_action = action;
+
+ if (arg_action == ACTION_SYSTEMCTL)
+ log_info("%s scheduled for %s, use 'systemctl %s --when=cancel' to cancel.",
+ pretty_action,
+ FORMAT_TIMESTAMP_STYLE(elapse, arg_timestamp_style),
+ action);
+ else
+ log_info("%s scheduled for %s, use 'shutdown -c' to cancel.",
+ pretty_action,
+ FORMAT_TIMESTAMP_STYLE(elapse, arg_timestamp_style));
return 0;
#else
diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c
index 9363764cd7..4dee3028b0 100644
--- a/src/systemctl/systemctl-start-special.c
+++ b/src/systemctl/systemctl-start-special.c
@@ -208,22 +208,33 @@ int verb_start_special(int argc, char *argv[], void *userdata) {
ACTION_POWEROFF,
ACTION_REBOOT,
ACTION_KEXEC,
- ACTION_HALT,
- ACTION_SUSPEND,
- ACTION_HIBERNATE,
- ACTION_HYBRID_SLEEP,
- ACTION_SUSPEND_THEN_HIBERNATE)) {
-
- r = logind_reboot(a);
- if (r >= 0)
- return r;
- if (IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS))
- /* Requested operation requires auth, is not supported or already in progress */
+ ACTION_HALT)) {
+
+ if (arg_when == 0)
+ r = logind_reboot(a);
+ else if (arg_when != USEC_INFINITY)
+ r = logind_schedule_shutdown(a);
+ else /* arg_when == USEC_INFINITY */
+ r = logind_cancel_shutdown();
+ if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS))
+ /* The latter indicates that the requested operation requires auth,
+ * is not supported or already in progress, in which cases we ignore the error. */
return r;
/* On all other errors, try low-level operation. In order to minimize the difference
* between operation with and without logind, we explicitly enable non-blocking mode
* for this, as logind's shutdown operations are always non-blocking. */
+ arg_no_block = true;
+
+ } else if (IN_SET(a,
+ ACTION_SUSPEND,
+ ACTION_HIBERNATE,
+ ACTION_HYBRID_SLEEP,
+ ACTION_SUSPEND_THEN_HIBERNATE)) {
+
+ r = logind_reboot(a);
+ if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS))
+ return r;
arg_no_block = true;
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 883a5b75f4..9dfde28426 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -313,6 +313,8 @@ static int systemctl_help(void) {
" --read-only Create read-only bind mount\n"
" --mkdir Create directory before mounting, if missing\n"
" --marked Restart/reload previously marked units\n"
+ " --when=TIME Schedule halt/power-off/reboot/kexec action after\n"
+ " a certain timestamp\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@@ -435,6 +437,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
ARG_MKDIR,
ARG_MARKED,
ARG_NO_WARN,
+ ARG_WHEN,
};
static const struct option options[] = {
@@ -497,6 +500,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
{ "read-only", no_argument, NULL, ARG_READ_ONLY },
{ "mkdir", no_argument, NULL, ARG_MKDIR },
{ "marked", no_argument, NULL, ARG_MARKED },
+ { "when", required_argument, NULL, ARG_WHEN },
{}
};
@@ -933,6 +937,30 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
arg_no_warn = true;
break;
+ case ARG_WHEN:
+ if (streq(optarg, "show")) {
+ r = logind_show_shutdown();
+ if (r < 0 && r != -ENODATA)
+ return r;
+
+ return 0;
+ }
+
+ if (STR_IN_SET(optarg, "", "cancel")) {
+ arg_when = USEC_INFINITY;
+ break;
+ }
+
+ r = parse_timestamp(optarg, &arg_when);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --when= argument '%s': %m", optarg);
+
+ if (!timestamp_is_set(arg_when))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid timestamp '%s' specified for --when=.", optarg);
+
+ break;
+
case '.':
/* Output an error mimicking getopt, and print a hint afterwards */
log_error("%s: invalid option -- '.'", program_invocation_name);

View File

@ -0,0 +1,31 @@
From e8f66d4c6570765fd60111ec7e3b5dbdb7d14c69 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Tue, 14 Mar 2023 07:16:18 +0800
Subject: [PATCH] test-time-util: add test cases to invalidate "show" and
"cancel"
Ensure that systemctl reboot --when=show and --when=cancel will not result in ambiguities
(cherry picked from commit 165655cb1de2e79d954d9165459143140e52c53b)
Related: RHEL-109488
---
src/test/test-time-util.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index ee861135c2..379f55ff2f 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -580,6 +580,11 @@ static void test_parse_timestamp_one(const char *str, usec_t max_diff, usec_t ex
static void test_parse_timestamp_impl(const char *tz) {
usec_t today, now_usec;
+ /* Invalid: Ensure that systemctl reboot --when=show and --when=cancel
+ * will not result in ambiguities */
+ assert_se(parse_timestamp("show", NULL) == -EINVAL);
+ assert_se(parse_timestamp("cancel", NULL) == -EINVAL);
+
/* UTC */
test_parse_timestamp_one("Thu 1970-01-01 00:01 UTC", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Thu 1970-01-01 00:00:01 UTC", 0, USEC_PER_SEC);

View File

@ -0,0 +1,128 @@
From 5bec9826a14483e7e612879fe2630f1b517e37be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Sun, 9 Jul 2023 13:25:42 -0600
Subject: [PATCH] Introduce RET_GATHER and use it in src/shared/
The idea is to make it easier to implement the common pattern of
accumulating errors (negative values) in an accumulator to return
the first error.
(cherry picked from commit 809c3a84e1a572ccaaa7eca5394c0b842118c22f)
Resolves: RHEL-108598
---
src/basic/errno-util.h | 10 ++++++++++
src/shared/bus-util.c | 10 ++--------
src/shared/devnode-acl.c | 4 ++--
src/shared/nscd-flush.c | 16 +++++-----------
src/test/test-errno-util.c | 11 +++++++++++
5 files changed, 30 insertions(+), 21 deletions(-)
diff --git a/src/basic/errno-util.h b/src/basic/errno-util.h
index b10dd755c9..27804e6382 100644
--- a/src/basic/errno-util.h
+++ b/src/basic/errno-util.h
@@ -74,6 +74,16 @@ static inline int RET_NERRNO(int ret) {
return ret;
}
+/* Collect possible errors in <acc>, so that the first error can be returned.
+ * Returns (possibly updated) <acc>. */
+#define RET_GATHER(acc, err) \
+ ({ \
+ int *__a = &(acc), __e = (err); \
+ if (*__a >= 0 && __e < 0) \
+ *__a = __e; \
+ *__a; \
+ })
+
static inline int errno_or_else(int fallback) {
/* To be used when invoking library calls where errno handling is not defined clearly: we return
* errno if it is set, and the specified error otherwise. The idea is that the caller initializes
diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c
index 5e6d17d201..7c8d2aa6f5 100644
--- a/src/shared/bus-util.c
+++ b/src/shared/bus-util.c
@@ -484,14 +484,8 @@ int bus_track_add_name_many(sd_bus_track *t, char **l) {
/* Continues adding after failure, and returns the first failure. */
- STRV_FOREACH(i, l) {
- int k;
-
- k = sd_bus_track_add_name(t, *i);
- if (k < 0 && r >= 0)
- r = k;
- }
-
+ STRV_FOREACH(i, l)
+ RET_GATHER(r, sd_bus_track_add_name(t, *i));
return r;
}
diff --git a/src/shared/devnode-acl.c b/src/shared/devnode-acl.c
index 66e3a40f2f..8c961061cf 100644
--- a/src/shared/devnode-acl.c
+++ b/src/shared/devnode-acl.c
@@ -220,8 +220,8 @@ int devnode_acl_all(const char *seat,
k = devnode_acl(n, flush, del, old_uid, add, new_uid);
if (k == -ENOENT)
log_debug("Device %s disappeared while setting ACLs", n);
- else if (k < 0 && r == 0)
- r = k;
+ else
+ RET_GATHER(r, k);
}
return r;
diff --git a/src/shared/nscd-flush.c b/src/shared/nscd-flush.c
index 9b0ba2d67a..d2b41f2b4d 100644
--- a/src/shared/nscd-flush.c
+++ b/src/shared/nscd-flush.c
@@ -128,21 +128,15 @@ static int nscd_flush_cache_one(const char *database, usec_t end) {
}
int nscd_flush_cache(char **databases) {
- usec_t end;
int r = 0;
- /* Tries to invalidate the specified database in nscd. We do this carefully, with a 5s timeout, so that we
- * don't block indefinitely on another service. */
+ /* Tries to invalidate the specified database in nscd. We do this carefully, with a 5s timeout,
+ * so that we don't block indefinitely on another service. */
- end = usec_add(now(CLOCK_MONOTONIC), NSCD_FLUSH_CACHE_TIMEOUT_USEC);
+ usec_t end = usec_add(now(CLOCK_MONOTONIC), NSCD_FLUSH_CACHE_TIMEOUT_USEC);
- STRV_FOREACH(i, databases) {
- int k;
-
- k = nscd_flush_cache_one(*i, end);
- if (k < 0 && r >= 0)
- r = k;
- }
+ STRV_FOREACH(i, databases)
+ RET_GATHER(r, nscd_flush_cache_one(*i, end));
return r;
}
diff --git a/src/test/test-errno-util.c b/src/test/test-errno-util.c
index cac0d5402b..77fb7d0011 100644
--- a/src/test/test-errno-util.c
+++ b/src/test/test-errno-util.c
@@ -67,4 +67,15 @@ TEST(ERRNO_IS_TRANSIENT) {
assert_se(!ERRNO_IS_NEG_TRANSIENT(INTMAX_MIN));
}
+TEST(RET_GATHER) {
+ int x = 0, y = 2;
+
+ assert_se(RET_GATHER(x, 5) == 0);
+ assert_se(RET_GATHER(x, -5) == -5);
+ assert_se(RET_GATHER(x, -1) == -5);
+
+ assert_se(RET_GATHER(x, y++) == -5);
+ assert_se(y == 3);
+}
+
DEFINE_TEST_MAIN(LOG_INFO);

View File

@ -0,0 +1,50 @@
From c2dc44abd4014f13a40dde350af92e2d74201359 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Fri, 29 Dec 2023 17:57:59 +0800
Subject: [PATCH] fd-util: don't eat up errors in fd_cloexec_many
Follow-up for ed18c22c989495aab36512f03449222cfcf79aa7
Before this commit, a successful fd_cloexec() call would
discard all previously gathered errors.
(cherry picked from commit 6b9cac874c33f4fa27aa4b4b5b980f60c28ee043)
Resolves: RHEL-108598
---
src/basic/fd-util.c | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c
index 66bb7569bb..932c5a8d80 100644
--- a/src/basic/fd-util.c
+++ b/src/basic/fd-util.c
@@ -175,7 +175,7 @@ int fd_cloexec(int fd, bool cloexec) {
}
int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec) {
- int ret = 0, r;
+ int r = 0;
assert(n_fds == 0 || fds);
@@ -183,14 +183,13 @@ int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec) {
if (fds[i] < 0) /* Skip gracefully over already invalidated fds */
continue;
- r = fd_cloexec(fds[i], cloexec);
- if (r < 0 && ret >= 0) /* Continue going, but return first error */
- ret = r;
- else
- ret = 1; /* report if we did anything */
+ RET_GATHER(r, fd_cloexec(fds[i], cloexec));
+
+ if (r >= 0)
+ r = 1; /* report if we did anything */
}
- return ret;
+ return r;
}
_pure_ static bool fd_in_set(int fd, const int fdset[], size_t n_fdset) {

View File

@ -0,0 +1,52 @@
From 762a8dc0c328e256847b111249bbff8e70f98942 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Fri, 19 May 2023 04:33:39 +0900
Subject: [PATCH] sd-bus: refuse to send messages with an invalid string
Prompted by aaf7b0e41105d7b7cf30912cdac32820f011a219 and
4804da58536ab7ad46178a03f4d2da49fd8e4ba2.
(cherry picked from commit 26a9dd6f55bb757e0033995cbb16bca12986b7cd)
Resolves: RHEL-108584
---
src/libsystemd/sd-bus/bus-message.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c
index 213b276e33..c51af56dda 100644
--- a/src/libsystemd/sd-bus/bus-message.c
+++ b/src/libsystemd/sd-bus/bus-message.c
@@ -1324,12 +1324,21 @@ int message_append_basic(sd_bus_message *m, char type, const void *p, const void
* into the empty string */
p = strempty(p);
- _fallthrough_;
+ if (!utf8_is_valid(p))
+ return -EINVAL;
+
+ align = 4;
+ sz = 4 + strlen(p) + 1;
+ break;
+
case SD_BUS_TYPE_OBJECT_PATH:
if (!p)
return -EINVAL;
+ if (!object_path_is_valid(p))
+ return -EINVAL;
+
align = 4;
sz = 4 + strlen(p) + 1;
break;
@@ -1338,6 +1347,9 @@ int message_append_basic(sd_bus_message *m, char type, const void *p, const void
p = strempty(p);
+ if (!signature_is_valid(p, /* allow_dict_entry = */ true))
+ return -EINVAL;
+
align = 1;
sz = 1 + strlen(p) + 1;
break;

View File

@ -0,0 +1,92 @@
From f3f939b236636fdca38e89ca564a669f0da4fd4d Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Fri, 19 May 2023 18:42:36 +0200
Subject: [PATCH] test: check if we correctly handle invalid UTF-8 in mount
stuff
Provides coverage for #27611.
(cherry picked from commit b74df879fc81d4668ce14532a76c23b85e651170)
Resolves: RHEL-108584
---
.../units/testsuite-07.mount-invalid-chars.sh | 70 +++++++++++++++++++
1 file changed, 70 insertions(+)
create mode 100755 test/units/testsuite-07.mount-invalid-chars.sh
diff --git a/test/units/testsuite-07.mount-invalid-chars.sh b/test/units/testsuite-07.mount-invalid-chars.sh
new file mode 100755
index 0000000000..617ea697c8
--- /dev/null
+++ b/test/units/testsuite-07.mount-invalid-chars.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Don't send invalid characters over dbus if a mount contains them
+
+at_exit() {
+ mountpoint -q /proc/1/mountinfo && umount /proc/1/mountinfo
+ [[ -e /tmp/fstab.bak ]] && mv -f /tmp/fstab /etc/fstab
+ rm -f /run/systemd/system/foo-*.mount
+ systemctl daemon-reload
+}
+
+trap at_exit EXIT
+
+# Check invalid characters directly in /proc/mountinfo
+#
+# This is a bit tricky (and hacky), since we have to temporarily replace
+# PID 1's /proc/mountinfo, but we have to keep the original mounts intact,
+# otherwise systemd would unmount them on reload
+TMP_MOUNTINFO="$(mktemp)"
+
+cp /proc/1/mountinfo "$TMP_MOUNTINFO"
+# Add a mount entry with a "Unicode non-character" in it
+echo -ne '69 1 252:2 / /foo/mountinfo rw,relatime shared:1 - cifs //foo\ufffebar rw,seclabel\n' >>"$TMP_MOUNTINFO"
+mount --bind "$TMP_MOUNTINFO" /proc/1/mountinfo
+systemctl daemon-reload
+# On affected versions this would throw an error:
+# Failed to get properties: Bad message
+systemctl status foo-mountinfo.mount
+
+umount /proc/1/mountinfo
+systemctl daemon-reload
+rm -f "$TMP_MOUNTINFO"
+
+# Check invalid characters in a mount unit
+#
+# systemd already handles this and refuses to load the invalid string, e.g.:
+# foo-fstab.mount:9: String is not UTF-8 clean, ignoring assignment: What=//localhost/foo<6F><6F><EFBFBD>bar
+#
+# a) Unit generated from /etc/fstab
+[[ -e /etc/fstab ]] && cp -f /etc/fstab /tmp/fstab.bak
+
+echo -ne '//localhost/foo\ufffebar /foo/fstab cifs defaults 0 0\n' >/etc/fstab
+systemctl daemon-reload
+[[ "$(systemctl show -P UnitFileState foo-fstab.mount)" == bad ]]
+
+# b) Unit generated from /etc/fstab (but the invalid character is in options)
+echo -ne '//localhost/foobar /foo/fstab/opt cifs nosuid,a\ufffeb,noexec 0 0\n' >/etc/fstab
+systemctl daemon-reload
+[[ "$(systemctl show -P UnitFileState foo-fstab-opt.mount)" == bad ]]
+rm -f /etc/fstab
+
+[[ -e /tmp/fstab.bak ]] && mv -f /tmp/fstab /etc/fstab
+systemctl daemon-reload
+
+# c) Mount unit
+mkdir -p /run/systemd/system
+echo -ne '[Mount]\nWhat=//localhost/foo\ufffebar\nWhere=/foo/unit\nType=cifs\nOptions=noexec\n' >/run/systemd/system/foo-unit.mount
+systemctl daemon-reload
+[[ "$(systemctl show -P UnitFileState foo-unit.mount)" == bad ]]
+rm -f /run/systemd/system/foo-unit.mount
+
+# d) Mount unit (but the invalid character is in Options=)
+mkdir -p /run/systemd/system
+echo -ne '[Mount]\nWhat=//localhost/foobar\nWhere=/foo/unit/opt\nType=cifs\nOptions=noexec,a\ufffeb,nosuid\n' >/run/systemd/system/foo-unit-opt.mount
+systemctl daemon-reload
+[[ "$(systemctl show -P UnitFileState foo-unit-opt.mount)" == bad ]]
+rm -f /run/systemd/system/foo-unit-opt.mount

View File

@ -0,0 +1,25 @@
From e882eabc5c9115413db1e6d83f4542ad618fec23 Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Mon, 22 May 2023 12:06:16 +0200
Subject: [PATCH] test: fix a typo in the cleanup stuff
(cherry picked from commit 7942811255f3d6973b246ebf6b26b690bbceab37)
Resolves: RHEL-108584
---
test/units/testsuite-07.mount-invalid-chars.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/units/testsuite-07.mount-invalid-chars.sh b/test/units/testsuite-07.mount-invalid-chars.sh
index 617ea697c8..b70e621126 100755
--- a/test/units/testsuite-07.mount-invalid-chars.sh
+++ b/test/units/testsuite-07.mount-invalid-chars.sh
@@ -7,7 +7,7 @@ set -o pipefail
at_exit() {
mountpoint -q /proc/1/mountinfo && umount /proc/1/mountinfo
- [[ -e /tmp/fstab.bak ]] && mv -f /tmp/fstab /etc/fstab
+ [[ -e /tmp/fstab.bak ]] && mv -f /tmp/fstab.bak /etc/fstab
rm -f /run/systemd/system/foo-*.mount
systemctl daemon-reload
}

View File

@ -0,0 +1,62 @@
From 78641d8a552eb95dd85cad9686d829af48478727 Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Mon, 14 Aug 2023 20:09:31 +0200
Subject: [PATCH] test: explicitly specify a UTF-8 locale for UTF-8 shenanigans
As things don't work well without it:
$ LANG=C printf "\ufffe\n"
\uFFFE
(cherry picked from commit 01febfcdce0326aa1888d085c1009c9399f6a930)
Resolves: RHEL-108584
---
test/units/testsuite-07.mount-invalid-chars.sh | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/test/units/testsuite-07.mount-invalid-chars.sh b/test/units/testsuite-07.mount-invalid-chars.sh
index b70e621126..5a07d14d04 100755
--- a/test/units/testsuite-07.mount-invalid-chars.sh
+++ b/test/units/testsuite-07.mount-invalid-chars.sh
@@ -23,7 +23,7 @@ TMP_MOUNTINFO="$(mktemp)"
cp /proc/1/mountinfo "$TMP_MOUNTINFO"
# Add a mount entry with a "Unicode non-character" in it
-echo -ne '69 1 252:2 / /foo/mountinfo rw,relatime shared:1 - cifs //foo\ufffebar rw,seclabel\n' >>"$TMP_MOUNTINFO"
+LANG="C.UTF-8" printf '69 1 252:2 / /foo/mountinfo rw,relatime shared:1 - cifs //foo\ufffebar rw,seclabel\n' >>"$TMP_MOUNTINFO"
mount --bind "$TMP_MOUNTINFO" /proc/1/mountinfo
systemctl daemon-reload
# On affected versions this would throw an error:
@@ -42,12 +42,12 @@ rm -f "$TMP_MOUNTINFO"
# a) Unit generated from /etc/fstab
[[ -e /etc/fstab ]] && cp -f /etc/fstab /tmp/fstab.bak
-echo -ne '//localhost/foo\ufffebar /foo/fstab cifs defaults 0 0\n' >/etc/fstab
+LANG="C.UTF-8" printf '//localhost/foo\ufffebar /foo/fstab cifs defaults 0 0\n' >/etc/fstab
systemctl daemon-reload
[[ "$(systemctl show -P UnitFileState foo-fstab.mount)" == bad ]]
# b) Unit generated from /etc/fstab (but the invalid character is in options)
-echo -ne '//localhost/foobar /foo/fstab/opt cifs nosuid,a\ufffeb,noexec 0 0\n' >/etc/fstab
+LANG="C.UTF-8" printf '//localhost/foobar /foo/fstab/opt cifs nosuid,a\ufffeb,noexec 0 0\n' >/etc/fstab
systemctl daemon-reload
[[ "$(systemctl show -P UnitFileState foo-fstab-opt.mount)" == bad ]]
rm -f /etc/fstab
@@ -57,14 +57,14 @@ systemctl daemon-reload
# c) Mount unit
mkdir -p /run/systemd/system
-echo -ne '[Mount]\nWhat=//localhost/foo\ufffebar\nWhere=/foo/unit\nType=cifs\nOptions=noexec\n' >/run/systemd/system/foo-unit.mount
+LANG="C.UTF-8" printf '[Mount]\nWhat=//localhost/foo\ufffebar\nWhere=/foo/unit\nType=cifs\nOptions=noexec\n' >/run/systemd/system/foo-unit.mount
systemctl daemon-reload
[[ "$(systemctl show -P UnitFileState foo-unit.mount)" == bad ]]
rm -f /run/systemd/system/foo-unit.mount
# d) Mount unit (but the invalid character is in Options=)
mkdir -p /run/systemd/system
-echo -ne '[Mount]\nWhat=//localhost/foobar\nWhere=/foo/unit/opt\nType=cifs\nOptions=noexec,a\ufffeb,nosuid\n' >/run/systemd/system/foo-unit-opt.mount
+LANG="C.UTF-8" printf '[Mount]\nWhat=//localhost/foobar\nWhere=/foo/unit/opt\nType=cifs\nOptions=noexec,a\ufffeb,nosuid\n' >/run/systemd/system/foo-unit-opt.mount
systemctl daemon-reload
[[ "$(systemctl show -P UnitFileState foo-unit-opt.mount)" == bad ]]
rm -f /run/systemd/system/foo-unit-opt.mount

View File

@ -0,0 +1,26 @@
From 15ad0f0a0145640ee290d805030338f8c01051f4 Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Wed, 23 Aug 2023 15:10:23 +0200
Subject: [PATCH] test: use the correct file name when restoring the original
fstab
(cherry picked from commit 9541addff028b56724df79fcf5b88e1544403957)
Resolves: RHEL-108584
---
test/units/testsuite-07.mount-invalid-chars.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/units/testsuite-07.mount-invalid-chars.sh b/test/units/testsuite-07.mount-invalid-chars.sh
index 5a07d14d04..a879334869 100755
--- a/test/units/testsuite-07.mount-invalid-chars.sh
+++ b/test/units/testsuite-07.mount-invalid-chars.sh
@@ -52,7 +52,7 @@ systemctl daemon-reload
[[ "$(systemctl show -P UnitFileState foo-fstab-opt.mount)" == bad ]]
rm -f /etc/fstab
-[[ -e /tmp/fstab.bak ]] && mv -f /tmp/fstab /etc/fstab
+[[ -e /tmp/fstab.bak ]] && mv -f /tmp/fstab.bak /etc/fstab
systemctl daemon-reload
# c) Mount unit

View File

@ -0,0 +1,134 @@
From b280191167ddc52a77da5b4047297d288f9ce73b Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Fri, 20 Jun 2025 13:16:10 +0200
Subject: [PATCH] core: escape UTF-8 in mount unit Where field before sending
to clients
Followup for: 4804da58536ab7ad46178a03f4d2da49fd8e4ba2 #27541
Fixes: #36206
(cherry picked from commit 222b0b05ce9ac29283cd89cf98444c4da3373568)
Resolves: RHEL-108584
---
src/core/dbus-mount.c | 23 ++++++++++++++++++-
src/core/mount.c | 16 ++++++++++++-
src/core/mount.h | 2 ++
.../units/testsuite-07.mount-invalid-chars.sh | 5 ++--
4 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c
index 55ad4f2c98..7006ebbbba 100644
--- a/src/core/dbus-mount.c
+++ b/src/core/dbus-mount.c
@@ -11,6 +11,27 @@
#include "unit.h"
#include "utf8.h"
+static int property_get_where(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Mount *m = ASSERT_PTR(userdata);
+
+ assert(bus);
+ assert(reply);
+
+ _cleanup_free_ char *escaped = mount_get_where_escaped(m);
+ if (!escaped)
+ return -ENOMEM;
+
+ return sd_bus_message_append_basic(reply, 's', escaped);
+}
+
static int property_get_what(
sd_bus *bus,
const char *path,
@@ -84,7 +105,7 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, mount_result, MountResu
const sd_bus_vtable bus_mount_vtable[] = {
SD_BUS_VTABLE_START(0),
- SD_BUS_PROPERTY("Where", "s", NULL, offsetof(Mount, where), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Where", "s", property_get_where, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("What", "s", property_get_what, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Options","s", property_get_options, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Type", "s", property_get_type, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
diff --git a/src/core/mount.c b/src/core/mount.c
index cfe3f40302..79772fb6f1 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -31,6 +31,7 @@
#include "strv.h"
#include "unit-name.h"
#include "unit.h"
+#include "utf8.h"
#define RETRY_UMOUNT_MAX 32
@@ -657,7 +658,11 @@ static int mount_add_extras(Mount *m) {
path_simplify(m->where);
if (!u->description) {
- r = unit_set_description(u, m->where);
+ _cleanup_free_ char *w = mount_get_where_escaped(m);
+ if (!w)
+ return log_oom();
+
+ r = unit_set_description(u, w);
if (r < 0)
return r;
}
@@ -2207,6 +2212,15 @@ static int mount_can_start(Unit *u) {
return 1;
}
+char* mount_get_where_escaped(const Mount *m) {
+ assert(m);
+
+ if (!m->where)
+ return strdup("");
+
+ return utf8_escape_invalid(m->where);
+}
+
static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = {
[MOUNT_EXEC_MOUNT] = "ExecMount",
[MOUNT_EXEC_UNMOUNT] = "ExecUnmount",
diff --git a/src/core/mount.h b/src/core/mount.h
index 1a0d9fc5e5..db4a915202 100644
--- a/src/core/mount.h
+++ b/src/core/mount.h
@@ -93,6 +93,8 @@ extern const UnitVTable mount_vtable;
void mount_fd_event(Manager *m, int events);
+char* mount_get_where_escaped(const Mount *m);
+
const char* mount_exec_command_to_string(MountExecCommand i) _const_;
MountExecCommand mount_exec_command_from_string(const char *s) _pure_;
diff --git a/test/units/testsuite-07.mount-invalid-chars.sh b/test/units/testsuite-07.mount-invalid-chars.sh
index a879334869..cd2ca78fdf 100755
--- a/test/units/testsuite-07.mount-invalid-chars.sh
+++ b/test/units/testsuite-07.mount-invalid-chars.sh
@@ -23,12 +23,13 @@ TMP_MOUNTINFO="$(mktemp)"
cp /proc/1/mountinfo "$TMP_MOUNTINFO"
# Add a mount entry with a "Unicode non-character" in it
-LANG="C.UTF-8" printf '69 1 252:2 / /foo/mountinfo rw,relatime shared:1 - cifs //foo\ufffebar rw,seclabel\n' >>"$TMP_MOUNTINFO"
+LANG="C.UTF-8" printf '69 1 252:2 / /foo/mount\ufffeinfo rw,relatime shared:1 - cifs //foo\ufffebar rw,seclabel\n' >>"$TMP_MOUNTINFO"
mount --bind "$TMP_MOUNTINFO" /proc/1/mountinfo
systemctl daemon-reload
# On affected versions this would throw an error:
# Failed to get properties: Bad message
-systemctl status foo-mountinfo.mount
+systemctl list-units -t mount
+systemctl status foo-mount\\xef\\xbf\\xbeinfo.mount
umount /proc/1/mountinfo
systemctl daemon-reload

View File

@ -0,0 +1,51 @@
From 4d4f5e617bb467be81274dc32b7066fc5ce52b75 Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <fsumsal@redhat.com>
Date: Wed, 3 Sep 2025 13:55:00 +0200
Subject: [PATCH] Revert "test-time-util: disable failing tests"
This won't be needed anymore.
This reverts commit c7a62e108ffbe41ccf1bb5fba4b5a37daf317939.
rhel-only: ci
Related: RHEL-109488
---
src/test/test-time-util.c | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index 379f55ff2f..56a71ecfba 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -698,7 +698,6 @@ static void test_parse_timestamp_impl(const char *tz) {
test_parse_timestamp_one("69-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000);
}
-#if 0
/* -06 */
test_parse_timestamp_one("Wed 1969-12-31 18:01 -06", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 1969-12-31 18:00:01 -06", 0, USEC_PER_SEC);
@@ -761,7 +760,6 @@ static void test_parse_timestamp_impl(const char *tz) {
test_parse_timestamp_one("69-12-31 18:00:01 -06:00", 0, USEC_PER_SEC);
test_parse_timestamp_one("69-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000);
-#endif
/* without date */
assert_se(parse_timestamp("today", &today) == 0);
@@ -977,7 +975,6 @@ TEST(timezone_offset_change) {
test_timezone_offset_change_one("Sun 2018-10-28 02:00:00 UTC", "Sun 2018-10-28 03:00:00 +01");
}
-#if 0
if (timezone_is_valid("Asia/Atyrau", LOG_DEBUG)) {
assert_se(setenv("TZ", ":Asia/Atyrau", 1) >= 0);
tzset();
@@ -999,7 +996,6 @@ TEST(timezone_offset_change) {
test_timezone_offset_change_one("Sun 1982-03-14 02:59:59 UTC", "Sat 1982-03-13 20:59:59 -06");
test_timezone_offset_change_one("Sun 1982-03-14 03:00:00 UTC", "Sat 1982-03-13 21:00:00 -06");
}
-#endif
assert_se(set_unset_env("TZ", tz, true) == 0);
tzset();

View File

@ -0,0 +1,92 @@
From 03dff755efde1a311969636e11fe95c398b7d878 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Fri, 3 Mar 2023 19:40:40 +0900
Subject: [PATCH] test: use get_timezones() to iterate all known timezones
(cherry picked from commit 0b20d70d1c7c190fb943dd4d1f28e6f456d2193e)
Related: RHEL-109488
---
src/test/test-time-util.c | 50 +++++++++------------------------------
1 file changed, 11 insertions(+), 39 deletions(-)
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index 56a71ecfba..eb8cc538c0 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -1,6 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include "dirent-util.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
@@ -414,23 +413,18 @@ TEST(FORMAT_TIMESTAMP) {
test_format_timestamp_loop();
}
-static void test_format_timestamp_with_tz_one(const char *name1, const char *name2) {
- _cleanup_free_ char *buf = NULL, *tz = NULL;
- const char *name, *saved_tz;
-
- if (name2)
- assert_se(buf = path_join(name1, name2));
- name = buf ?: name1;
+static void test_format_timestamp_with_tz_one(const char *tz) {
+ const char *saved_tz, *colon_tz;
- if (!timezone_is_valid(name, LOG_DEBUG))
+ if (!timezone_is_valid(tz, LOG_DEBUG))
return;
- log_info("/* %s(%s) */", __func__, name);
+ log_info("/* %s(%s) */", __func__, tz);
saved_tz = getenv("TZ");
- assert_se(tz = strjoin(":", name));
- assert_se(setenv("TZ", tz, 1) >= 0);
+ assert_se(colon_tz = strjoina(":", tz));
+ assert_se(setenv("TZ", colon_tz, 1) >= 0);
tzset();
log_debug("%s: tzname[0]=%s, tzname[1]=%s", tz, strempty(tzname[0]), strempty(tzname[1]));
@@ -441,33 +435,11 @@ static void test_format_timestamp_with_tz_one(const char *name1, const char *nam
}
TEST(FORMAT_TIMESTAMP_with_tz) {
- if (!slow_tests_enabled())
- return (void) log_tests_skipped("slow tests are disabled");
-
- _cleanup_closedir_ DIR *dir = opendir("/usr/share/zoneinfo");
- if (!dir)
- return (void) log_tests_skipped_errno(errno, "Failed to open /usr/share/zoneinfo");
-
- FOREACH_DIRENT(de, dir, break) {
- if (de->d_type == DT_REG)
- test_format_timestamp_with_tz_one(de->d_name, NULL);
-
- else if (de->d_type == DT_DIR) {
- if (streq(de->d_name, "right"))
- /* The test does not support timezone with leap second info. */
- continue;
-
- _cleanup_closedir_ DIR *subdir = xopendirat(dirfd(dir), de->d_name, 0);
- if (!subdir) {
- log_notice_errno(errno, "Failed to open /usr/share/zoneinfo/%s, ignoring: %m", de->d_name);
- continue;
- }
-
- FOREACH_DIRENT(subde, subdir, break)
- if (subde->d_type == DT_REG)
- test_format_timestamp_with_tz_one(de->d_name, subde->d_name);
- }
- }
+ _cleanup_strv_free_ char **timezones = NULL;
+
+ assert_se(get_timezones(&timezones) >= 0);
+ STRV_FOREACH(tz, timezones)
+ test_format_timestamp_with_tz_one(*tz);
}
TEST(format_timestamp_relative) {

View File

@ -0,0 +1,72 @@
From c1e4badeadf75e75b6059a6d644d28414013c102 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Mon, 13 Mar 2023 03:47:45 +0900
Subject: [PATCH] test-time-util: do not fail on DST change
(cherry picked from commit cfacd245e798282fcb9b3231bd6e857abfe124fc)
Related: RHEL-109488
---
src/test/test-time-util.c | 37 ++++++++++++++++++++++++++++++-------
1 file changed, 30 insertions(+), 7 deletions(-)
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index eb8cc538c0..775e6bb84a 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -543,12 +543,30 @@ static void test_parse_timestamp_one(const char *str, usec_t max_diff, usec_t ex
int r;
r = parse_timestamp(str, &usec);
- log_debug("/* %s(%s): max_diff="USEC_FMT", expected="USEC_FMT", result="USEC_FMT"*/", __func__, str, max_diff, expected, usec);
+ log_debug("/* %s(%s): max_diff="USEC_FMT", expected="USEC_FMT", result="USEC_FMT" */", __func__, str, max_diff, expected, usec);
assert_se(r >= 0);
assert_se(usec >= expected);
assert_se(usec_sub_unsigned(usec, expected) <= max_diff);
}
+static bool time_is_zero(usec_t usec) {
+ const char *s;
+
+ s = FORMAT_TIMESTAMP(usec);
+ return strstr(s, " 00:00:00 ");
+}
+
+static bool timezone_equal(usec_t today, usec_t target) {
+ const char *s, *t, *sz, *tz;
+
+ s = FORMAT_TIMESTAMP(today);
+ t = FORMAT_TIMESTAMP(target);
+ assert_se(sz = strrchr(s, ' '));
+ assert_se(tz = strrchr(t, ' '));
+ log_debug("%s("USEC_FMT", "USEC_FMT") -> %s, %s", __func__, today, target, s, t);
+ return streq(sz, tz);
+}
+
static void test_parse_timestamp_impl(const char *tz) {
usec_t today, now_usec;
@@ -735,12 +753,17 @@ static void test_parse_timestamp_impl(const char *tz) {
/* without date */
assert_se(parse_timestamp("today", &today) == 0);
- test_parse_timestamp_one("00:01", 0, today + USEC_PER_MINUTE);
- test_parse_timestamp_one("00:00:01", 0, today + USEC_PER_SEC);
- test_parse_timestamp_one("00:00:01.001", 0, today + USEC_PER_SEC + 1000);
- test_parse_timestamp_one("00:00:01.0010", 0, today + USEC_PER_SEC + 1000);
- test_parse_timestamp_one("tomorrow", 0, today + USEC_PER_DAY);
- test_parse_timestamp_one("yesterday", 0, today - USEC_PER_DAY);
+ if (time_is_zero(today)) {
+ test_parse_timestamp_one("00:01", 0, today + USEC_PER_MINUTE);
+ test_parse_timestamp_one("00:00:01", 0, today + USEC_PER_SEC);
+ test_parse_timestamp_one("00:00:01.001", 0, today + USEC_PER_SEC + 1000);
+ test_parse_timestamp_one("00:00:01.0010", 0, today + USEC_PER_SEC + 1000);
+
+ if (timezone_equal(today, today + USEC_PER_DAY) && time_is_zero(today + USEC_PER_DAY))
+ test_parse_timestamp_one("tomorrow", 0, today + USEC_PER_DAY);
+ if (timezone_equal(today, today - USEC_PER_DAY) && time_is_zero(today - USEC_PER_DAY))
+ test_parse_timestamp_one("yesterday", 0, today - USEC_PER_DAY);
+ }
/* relative */
assert_se(parse_timestamp("now", &now_usec) == 0);

View File

@ -0,0 +1,82 @@
From 8e8569467616ee982df2cc73ac39240482c92d36 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Sun, 26 Nov 2023 20:58:43 +0100
Subject: [PATCH] test-time-util: suppress timestamp conversion failures for
Africa/Khartoum timezone
Our timestamp conversion roundtrip test was failing. But I think that this
is not our bug:
$ TZ='Africa/Khartoum' date --date='@1509482094'
Tue Oct 31 23:34:54 EAT 2017
$ TZ='Africa/Khartoum' date --date='Tue Oct 31 23:34:54 EAT 2017' +%s
1509485694
$ TZ='Africa/Khartoum' date --date='@1509485694'
Tue Oct 31 23:34:54 CAT 2017
$ echo $[1509485694 - 1509482094]
3600
This is essentially the same as what happens in our test. After a round-trip, we
end up one hour ahead.
> For 1509482094632752, from the change log of tzdata:
>
> Release 2017c - 2017-10-20 14:49:34 -0700
>
> Changes to future timestamps
> Sudan will switch from +03 to +02 on 2017-11-01.
Fixes https://github.com/systemd/systemd/issues/28472.
(cherry picked from commit 78b95ccad864e1f993fe0776841dd8f39856581b)
Related: RHEL-109488
---
src/test/test-time-util.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index 775e6bb84a..2550ffdba2 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -382,7 +382,7 @@ TEST(format_timestamp) {
}
static void test_format_timestamp_impl(usec_t x) {
- bool success;
+ bool success, override;
const char *xx, *yy;
usec_t y;
@@ -393,14 +393,28 @@ static void test_format_timestamp_impl(usec_t x) {
assert_se(yy);
success = (x / USEC_PER_SEC == y / USEC_PER_SEC) && streq(xx, yy);
- log_full(success ? LOG_DEBUG : LOG_ERR, "@" USEC_FMT " → %s → @" USEC_FMT " → %s", x, xx, y, yy);
- assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC);
- assert_se(streq(xx, yy));
+ /* Workaround for https://github.com/systemd/systemd/issues/28472 */
+ override = !success &&
+ (STRPTR_IN_SET(tzname[0], "CAT", "EAT") ||
+ STRPTR_IN_SET(tzname[1], "CAT", "EAT")) &&
+ DIV_ROUND_UP(y - x, USEC_PER_SEC) == 3600; /* 1 hour, ignore fractional second */
+ log_full(success ? LOG_DEBUG : override ? LOG_WARNING : LOG_ERR,
+ "@" USEC_FMT " → %s → @" USEC_FMT " → %s%s",
+ x, xx, y, yy,
+ override ? ", ignoring." : "");
+ if (!override) {
+ assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC);
+ assert_se(streq(xx, yy));
+ }
}
static void test_format_timestamp_loop(void) {
test_format_timestamp_impl(USEC_PER_SEC);
+ /* Two cases which trigger https://github.com/systemd/systemd/issues/28472 */
+ test_format_timestamp_impl(1504938962980066);
+ test_format_timestamp_impl(1509482094632752);
+
for (unsigned i = 0; i < TRIAL; i++) {
usec_t x;

View File

@ -0,0 +1,68 @@
From 846cb844c3e8b5621745798f62bfbfb4275735f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Thu, 5 Dec 2024 13:32:19 +0100
Subject: [PATCH] test-time-util: do more suppression of time zone checks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The issue is directly triggered by tzdata-2024b, where the setting of timezone
started to fail and the tests stopped passing. But those timestamps in 1/1/1970
appear to have some problems already before:
$ sudo date -s 'Thu 1970-01-01 13:00:01 WET'
Thu Jan 1 03:00:01 PM EET 1970
$ sudo date -s 'Thu 1970-01-01 12:00:01 WET'
date: cannot set date: Invalid argument
Thu Jan 1 02:00:01 PM EET 1970
$ rpm -q tzdata
tzdata-2024a-9.fc41.noarch
The same issue appears with other timezones. So move the first timestamp one
day forward to avoid the issue.
After the previous problem is solved, we also get the problem already seen
previously where the roundtrip returns a time that is off by one hour:
@86401000000 → Fri 1970-01-02 00:00:01 WET → @82801000000 → Thu 1970-01-01 23:00:01 WET
Assertion 'x / USEC_PER_SEC == y / USEC_PER_SEC' failed at src/test/test-time-util.c:415, function test_format_timestamp_impl(). Aborting.
Extend the override to suppress this.
(cherry picked from commit 3cf362f6f57b7d0b5f6b86a49316303b0dda7599)
Related: RHEL-109488
---
src/test/test-time-util.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index 2550ffdba2..45d7541415 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -393,11 +393,12 @@ static void test_format_timestamp_impl(usec_t x) {
assert_se(yy);
success = (x / USEC_PER_SEC == y / USEC_PER_SEC) && streq(xx, yy);
- /* Workaround for https://github.com/systemd/systemd/issues/28472 */
+ /* Workaround for https://github.com/systemd/systemd/issues/28472
+ * and https://github.com/systemd/systemd/pull/35471. */
override = !success &&
- (STRPTR_IN_SET(tzname[0], "CAT", "EAT") ||
- STRPTR_IN_SET(tzname[1], "CAT", "EAT")) &&
- DIV_ROUND_UP(y - x, USEC_PER_SEC) == 3600; /* 1 hour, ignore fractional second */
+ (STRPTR_IN_SET(tzname[0], "CAT", "EAT", "WET") ||
+ STRPTR_IN_SET(tzname[1], "CAT", "EAT", "WET")) &&
+ DIV_ROUND_UP(x > y ? x - y : y - x, USEC_PER_SEC) == 3600; /* 1 hour, ignore fractional second */
log_full(success ? LOG_DEBUG : override ? LOG_WARNING : LOG_ERR,
"@" USEC_FMT " → %s → @" USEC_FMT " → %s%s",
x, xx, y, yy,
@@ -409,7 +410,7 @@ static void test_format_timestamp_impl(usec_t x) {
}
static void test_format_timestamp_loop(void) {
- test_format_timestamp_impl(USEC_PER_SEC);
+ test_format_timestamp_impl(USEC_PER_DAY + USEC_PER_SEC);
/* Two cases which trigger https://github.com/systemd/systemd/issues/28472 */
test_format_timestamp_impl(1504938962980066);

View File

@ -0,0 +1,61 @@
From cb442eb90085aae9db3bdf0cd7a4730912dba3ef Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Sat, 14 Dec 2024 16:49:54 +0900
Subject: [PATCH] test-time-util: fix truncation of usec to sec
Also
- use ASSERT_XYZ() macros,
- log tzname[] on failure.
(cherry picked from commit 3f1d499964abb6a4c0141d7ea8f852829880adff)
Related: RHEL-109488
---
src/test/test-time-util.c | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index 45d7541415..a5443ad7d7 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -384,28 +384,32 @@ TEST(format_timestamp) {
static void test_format_timestamp_impl(usec_t x) {
bool success, override;
const char *xx, *yy;
- usec_t y;
+ usec_t y, x_sec, y_sec;
xx = FORMAT_TIMESTAMP(x);
- assert_se(xx);
- assert_se(parse_timestamp(xx, &y) >= 0);
+ ASSERT_NOT_NULL(xx);
+ ASSERT_OK(parse_timestamp(xx, &y));
yy = FORMAT_TIMESTAMP(y);
- assert_se(yy);
+ ASSERT_NOT_NULL(yy);
- success = (x / USEC_PER_SEC == y / USEC_PER_SEC) && streq(xx, yy);
+ x_sec = x / USEC_PER_SEC;
+ y_sec = y / USEC_PER_SEC;
+ success = (x_sec == y_sec) && streq(xx, yy);
/* Workaround for https://github.com/systemd/systemd/issues/28472
* and https://github.com/systemd/systemd/pull/35471. */
override = !success &&
(STRPTR_IN_SET(tzname[0], "CAT", "EAT", "WET") ||
STRPTR_IN_SET(tzname[1], "CAT", "EAT", "WET")) &&
- DIV_ROUND_UP(x > y ? x - y : y - x, USEC_PER_SEC) == 3600; /* 1 hour, ignore fractional second */
+ (x_sec > y_sec ? x_sec - y_sec : y_sec - x_sec) == 3600; /* 1 hour, ignore fractional second */
log_full(success ? LOG_DEBUG : override ? LOG_WARNING : LOG_ERR,
"@" USEC_FMT " → %s → @" USEC_FMT " → %s%s",
x, xx, y, yy,
override ? ", ignoring." : "");
if (!override) {
- assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC);
- assert_se(streq(xx, yy));
+ if (!success)
+ log_warning("tzname[0]=\"%s\", tzname[1]=\"%s\"", tzname[0], tzname[1]);
+ ASSERT_EQ(x_sec, y_sec);
+ ASSERT_STREQ(xx, yy);
}
}

View File

@ -0,0 +1,63 @@
From e0ac4a4632c99750ad63476c3ae62a1988b2883b Mon Sep 17 00:00:00 2001
From: Luca Boccassi <bluca@debian.org>
Date: Fri, 26 Jan 2024 00:22:38 +0000
Subject: [PATCH] test: unset TZ before timezone-sensitive unit tests are run
Some tests have hard-coded results that need to match, and change if
the caller has a timezone set via the TZ= environment variable, as it
is the case during reproducible build tests. Unset it.
(cherry picked from commit 1e902c3463024bb328bf0d01a5d58a69e1ccf739)
Related: RHEL-109488
---
src/test/test-calendarspec.c | 9 ++++++++-
src/test/test-date.c | 3 +++
src/test/test-time-util.c | 3 +++
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c
index 564983b699..4699991535 100644
--- a/src/test/test-calendarspec.c
+++ b/src/test/test-calendarspec.c
@@ -260,4 +260,11 @@ TEST(calendar_spec_from_string) {
assert_se(calendar_spec_from_string("*:4,30:*\n", &c) == -EINVAL);
}
-DEFINE_TEST_MAIN(LOG_INFO);
+static int intro(void) {
+ /* Tests have hard-coded results that do not expect a specific timezone to be set by the caller */
+ assert_se(unsetenv("TZ") >= 0);
+
+ return EXIT_SUCCESS;
+}
+
+DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro);
diff --git a/src/test/test-date.c b/src/test/test-date.c
index 097066b61a..cc11bd999e 100644
--- a/src/test/test-date.c
+++ b/src/test/test-date.c
@@ -62,6 +62,9 @@ static void test_one_noutc(const char *p) {
}
int main(int argc, char *argv[]) {
+ /* Tests have hard-coded results that do not expect a specific timezone to be set by the caller */
+ assert_se(unsetenv("TZ") >= 0);
+
test_setup_logging(LOG_DEBUG);
test_one("17:41");
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index a5443ad7d7..21b05a3010 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -1016,6 +1016,9 @@ TEST(timezone_offset_change) {
}
static int intro(void) {
+ /* Tests have hard-coded results that do not expect a specific timezone to be set by the caller */
+ assert_se(unsetenv("TZ") >= 0);
+
log_info("realtime=" USEC_FMT "\n"
"monotonic=" USEC_FMT "\n"
"boottime=" USEC_FMT "\n",

View File

@ -0,0 +1,30 @@
From bef180f6ddc06bc6e669024b5d1fb9b97a1e3f4d Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <fsumsal@redhat.com>
Date: Tue, 2 Sep 2025 17:33:35 +0200
Subject: [PATCH] meson: extend timeout for test-time-util
As it runs through many timezones and hits the default timeout when
running under sanitizers.
Based on upstream's b66b3c409900a77b3da7b366ae5a0179abacea99.
rhel-only: ci
Related: RHEL-109488
---
src/test/meson.build | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/test/meson.build b/src/test/meson.build
index 5547271ee7..9de487ced5 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -459,7 +459,8 @@ tests += [
[files('test-fileio.c')],
- [files('test-time-util.c')],
+ [files('test-time-util.c'),
+ [], [], [], '', 'timeout=120'],
[files('test-clock.c')],

View File

@ -0,0 +1,25 @@
From c5bdf8c148fc9ab18d3412756ada7317caece6fe Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 14 Feb 2023 01:40:56 +0900
Subject: [PATCH] time-util: use DEFINE_STRING_TABLE_LOOKUP_TO_STRING() macro
(cherry picked from commit d227a42aadf04c23c668ac3089bc7b4a9baaf7e1)
Related: RHEL-109488
---
src/basic/time-util.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index f5e10bba1a..6eee8a48a7 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -1616,7 +1616,7 @@ static const char* const timestamp_style_table[_TIMESTAMP_STYLE_MAX] = {
};
/* Use the macro for enum → string to allow for aliases */
-_DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle,);
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle);
/* For the string → enum mapping we use the generic implementation, but also support two aliases */
TimestampStyle timestamp_style_from_string(const char *s) {

View File

@ -0,0 +1,30 @@
From 243ecafd63c2c0f8cbdecee770626143bf4def62 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 14 Feb 2023 01:41:38 +0900
Subject: [PATCH] time-util: align string table
(cherry picked from commit e01a8fdd2645c06cdb9057bd5b8a45ab02c0d6ee)
Related: RHEL-109488
---
src/basic/time-util.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index 6eee8a48a7..404fde01db 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -1609,10 +1609,10 @@ int time_change_fd(void) {
static const char* const timestamp_style_table[_TIMESTAMP_STYLE_MAX] = {
[TIMESTAMP_PRETTY] = "pretty",
- [TIMESTAMP_US] = "us",
- [TIMESTAMP_UTC] = "utc",
+ [TIMESTAMP_US] = "us",
+ [TIMESTAMP_UTC] = "utc",
[TIMESTAMP_US_UTC] = "us+utc",
- [TIMESTAMP_UNIX] = "unix",
+ [TIMESTAMP_UNIX] = "unix",
};
/* Use the macro for enum → string to allow for aliases */

View File

@ -0,0 +1,316 @@
From 753dd2e5f28a9b5a552efda75bcbec20ad501e69 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 14 Feb 2023 02:04:31 +0900
Subject: [PATCH] time-util: rename variables
(cherry picked from commit cf98b66d1ad0ff0e9ee0444861069ebade038dbb)
Related: RHEL-109488
---
src/basic/time-util.c | 86 +++++++++++++++++++++----------------------
src/basic/time-util.h | 16 ++++----
2 files changed, 51 insertions(+), 51 deletions(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index 404fde01db..7dbd3af20a 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -604,7 +604,7 @@ char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
return buf;
}
-static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
+static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
static const struct {
const char *name;
const int nr;
@@ -628,7 +628,7 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
const char *k, *utc = NULL, *tzn = NULL;
struct tm tm, copy;
time_t x;
- usec_t x_usec, plus = 0, minus = 0, ret;
+ usec_t usec, x_usec, plus = 0, minus = 0;
int r, weekday = -1, dst = -1;
size_t i;
@@ -651,9 +651,9 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
assert(t);
if (t[0] == '@' && !with_tz)
- return parse_sec(t + 1, usec);
+ return parse_sec(t + 1, ret);
- ret = now(CLOCK_REALTIME);
+ usec = now(CLOCK_REALTIME);
if (!with_tz) {
if (streq(t, "now"))
@@ -734,7 +734,7 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
}
}
- x = (time_t) (ret / USEC_PER_SEC);
+ x = (time_t) (usec / USEC_PER_SEC);
x_usec = 0;
if (!localtime_or_gmtime_r(&x, &tm, utc))
@@ -871,24 +871,24 @@ from_tm:
if (x < 0)
return -EINVAL;
- ret = (usec_t) x * USEC_PER_SEC + x_usec;
- if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
+ usec = (usec_t) x * USEC_PER_SEC + x_usec;
+ if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
return -EINVAL;
finish:
- if (ret + plus < ret) /* overflow? */
+ if (usec + plus < usec) /* overflow? */
return -EINVAL;
- ret += plus;
- if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
+ usec += plus;
+ if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
return -EINVAL;
- if (ret >= minus)
- ret -= minus;
+ if (usec >= minus)
+ usec -= minus;
else
return -EINVAL;
- if (usec)
- *usec = ret;
+ if (ret)
+ *ret = usec;
return 0;
}
@@ -897,7 +897,7 @@ typedef struct ParseTimestampResult {
int return_value;
} ParseTimestampResult;
-int parse_timestamp(const char *t, usec_t *usec) {
+int parse_timestamp(const char *t, usec_t *ret) {
char *last_space, *tz = NULL;
ParseTimestampResult *shared, tmp;
int r;
@@ -907,7 +907,7 @@ int parse_timestamp(const char *t, usec_t *usec) {
tz = last_space + 1;
if (!tz || endswith_no_case(t, " UTC"))
- return parse_timestamp_impl(t, usec, false);
+ return parse_timestamp_impl(t, ret, false);
shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if (shared == MAP_FAILED)
@@ -949,13 +949,13 @@ int parse_timestamp(const char *t, usec_t *usec) {
if (munmap(shared, sizeof *shared) != 0)
return negative_errno();
- if (tmp.return_value == 0 && usec)
- *usec = tmp.usec;
+ if (tmp.return_value == 0 && ret)
+ *ret = tmp.usec;
return tmp.return_value;
}
-static const char* extract_multiplier(const char *p, usec_t *multiplier) {
+static const char* extract_multiplier(const char *p, usec_t *ret) {
static const struct {
const char *suffix;
usec_t usec;
@@ -996,7 +996,7 @@ static const char* extract_multiplier(const char *p, usec_t *multiplier) {
e = startswith(p, table[i].suffix);
if (e) {
- *multiplier = table[i].usec;
+ *ret = table[i].usec;
return e;
}
}
@@ -1004,9 +1004,9 @@ static const char* extract_multiplier(const char *p, usec_t *multiplier) {
return p;
}
-int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
+int parse_time(const char *t, usec_t *ret, usec_t default_unit) {
const char *p, *s;
- usec_t r = 0;
+ usec_t usec = 0;
bool something = false;
assert(t);
@@ -1021,8 +1021,8 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
if (*s != 0)
return -EINVAL;
- if (usec)
- *usec = USEC_INFINITY;
+ if (ret)
+ *ret = USEC_INFINITY;
return 0;
}
@@ -1069,10 +1069,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
return -ERANGE;
k = (usec_t) l * multiplier;
- if (k >= USEC_INFINITY - r)
+ if (k >= USEC_INFINITY - usec)
return -ERANGE;
- r += k;
+ usec += k;
something = true;
@@ -1082,10 +1082,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
k = (usec_t) (*b - '0') * m;
- if (k >= USEC_INFINITY - r)
+ if (k >= USEC_INFINITY - usec)
return -ERANGE;
- r += k;
+ usec += k;
}
/* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
@@ -1094,13 +1094,13 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
}
}
- if (usec)
- *usec = r;
+ if (ret)
+ *ret = usec;
return 0;
}
-int parse_sec(const char *t, usec_t *usec) {
- return parse_time(t, usec, USEC_PER_SEC);
+int parse_sec(const char *t, usec_t *ret) {
+ return parse_time(t, ret, USEC_PER_SEC);
}
int parse_sec_fix_0(const char *t, usec_t *ret) {
@@ -1127,7 +1127,7 @@ int parse_sec_def_infinity(const char *t, usec_t *ret) {
return parse_sec(t, ret);
}
-static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
+static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) {
static const struct {
const char *suffix;
nsec_t nsec;
@@ -1172,7 +1172,7 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
e = startswith(p, table[i].suffix);
if (e) {
- *multiplier = table[i].nsec;
+ *ret = table[i].nsec;
return e;
}
}
@@ -1180,13 +1180,13 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
return p;
}
-int parse_nsec(const char *t, nsec_t *nsec) {
+int parse_nsec(const char *t, nsec_t *ret) {
const char *p, *s;
- nsec_t r = 0;
+ nsec_t nsec = 0;
bool something = false;
assert(t);
- assert(nsec);
+ assert(ret);
p = t;
@@ -1197,7 +1197,7 @@ int parse_nsec(const char *t, nsec_t *nsec) {
if (*s != 0)
return -EINVAL;
- *nsec = NSEC_INFINITY;
+ *ret = NSEC_INFINITY;
return 0;
}
@@ -1244,10 +1244,10 @@ int parse_nsec(const char *t, nsec_t *nsec) {
return -ERANGE;
k = (nsec_t) l * multiplier;
- if (k >= NSEC_INFINITY - r)
+ if (k >= NSEC_INFINITY - nsec)
return -ERANGE;
- r += k;
+ nsec += k;
something = true;
@@ -1257,10 +1257,10 @@ int parse_nsec(const char *t, nsec_t *nsec) {
for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
k = (nsec_t) (*b - '0') * m;
- if (k >= NSEC_INFINITY - r)
+ if (k >= NSEC_INFINITY - nsec)
return -ERANGE;
- r += k;
+ nsec += k;
}
/* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
@@ -1269,7 +1269,7 @@ int parse_nsec(const char *t, nsec_t *nsec) {
}
}
- *nsec = r;
+ *ret = nsec;
return 0;
}
diff --git a/src/basic/time-util.h b/src/basic/time-util.h
index c98f95a530..9d44cac747 100644
--- a/src/basic/time-util.h
+++ b/src/basic/time-util.h
@@ -141,15 +141,15 @@ static inline char* format_timestamp(char *buf, size_t l, usec_t t) {
#define FORMAT_TIMESTAMP_STYLE(t, style) \
format_timestamp_style((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t, style)
-int parse_timestamp(const char *t, usec_t *usec);
+int parse_timestamp(const char *t, usec_t *ret);
-int parse_sec(const char *t, usec_t *usec);
-int parse_sec_fix_0(const char *t, usec_t *usec);
-int parse_sec_def_infinity(const char *t, usec_t *usec);
-int parse_time(const char *t, usec_t *usec, usec_t default_unit);
-int parse_nsec(const char *t, nsec_t *nsec);
+int parse_sec(const char *t, usec_t *ret);
+int parse_sec_fix_0(const char *t, usec_t *ret);
+int parse_sec_def_infinity(const char *t, usec_t *ret);
+int parse_time(const char *t, usec_t *ret, usec_t default_unit);
+int parse_nsec(const char *t, nsec_t *ret);
-int get_timezones(char ***l);
+int get_timezones(char ***ret);
int verify_timezone(const char *name, int log_level);
static inline bool timezone_is_valid(const char *name, int log_level) {
return verify_timezone(name, log_level) >= 0;
@@ -159,7 +159,7 @@ bool clock_supported(clockid_t clock);
usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to);
-int get_timezone(char **timezone);
+int get_timezone(char **ret);
time_t mktime_or_timegm(struct tm *tm, bool utc);
struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc);

View File

@ -0,0 +1,115 @@
From 23f6608ffa05ce80ebfbca9a28369ff858e9d42e Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 14 Feb 2023 02:06:13 +0900
Subject: [PATCH] time-util: add assertions
(cherry picked from commit dff3bddc5416834d42cc682cb544732a4b91db3b)
Related: RHEL-109488
---
src/basic/time-util.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index 7dbd3af20a..ac28dc9be6 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -172,6 +172,8 @@ dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) {
usec_t nowm;
+ assert(ts);
+
if (u == USEC_INFINITY) {
ts->realtime = ts->monotonic = USEC_INFINITY;
return ts;
@@ -184,6 +186,7 @@ dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) {
}
usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
+ assert(ts);
switch (clock) {
@@ -421,6 +424,8 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
const char *s;
usec_t n, d;
+ assert(buf);
+
if (!timestamp_is_set(t))
return NULL;
@@ -902,6 +907,8 @@ int parse_timestamp(const char *t, usec_t *ret) {
ParseTimestampResult *shared, tmp;
int r;
+ assert(t);
+
last_space = strrchr(t, ' ');
if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG))
tz = last_space + 1;
@@ -991,6 +998,9 @@ static const char* extract_multiplier(const char *p, usec_t *ret) {
{ "µs", 1ULL },
};
+ assert(p);
+ assert(ret);
+
for (size_t i = 0; i < ELEMENTSOF(table); i++) {
char *e;
@@ -1119,6 +1129,9 @@ int parse_sec_fix_0(const char *t, usec_t *ret) {
}
int parse_sec_def_infinity(const char *t, usec_t *ret) {
+ assert(t);
+ assert(ret);
+
t += strspn(t, WHITESPACE);
if (isempty(t)) {
*ret = USEC_INFINITY;
@@ -1167,6 +1180,9 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) {
};
size_t i;
+ assert(p);
+ assert(ret);
+
for (i = 0; i < ELEMENTSOF(table); i++) {
char *e;
@@ -1320,6 +1336,8 @@ static int get_timezones_from_tzdata_zi(char ***ret) {
_cleanup_strv_free_ char **zones = NULL;
int r;
+ assert(ret);
+
f = fopen("/usr/share/zoneinfo/tzdata.zi", "re");
if (!f)
return -errno;
@@ -1477,6 +1495,8 @@ int get_timezone(char **ret) {
char *z;
int r;
+ assert(ret);
+
r = readlink_malloc("/etc/localtime", &t);
if (r == -ENOENT) {
/* If the symlink does not exist, assume "UTC", like glibc does */
@@ -1506,10 +1526,15 @@ int get_timezone(char **ret) {
}
time_t mktime_or_timegm(struct tm *tm, bool utc) {
+ assert(tm);
+
return utc ? timegm(tm) : mktime(tm);
}
struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) {
+ assert(t);
+ assert(tm);
+
return utc ? gmtime_r(t, tm) : localtime_r(t, tm);
}

View File

@ -0,0 +1,54 @@
From a3c08667c9abaf60793ed35e93123bc5f89981f3 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 14 Feb 2023 03:43:58 +0900
Subject: [PATCH] time-util: drop redundant else
(cherry picked from commit 17d1ebfc43c3b971d20ff2806acc634ee153eef6)
Related: RHEL-109488
---
src/basic/time-util.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index ac28dc9be6..c5b91fde66 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -664,21 +664,23 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
if (streq(t, "now"))
goto finish;
- else if (t[0] == '+') {
+ if (t[0] == '+') {
r = parse_sec(t+1, &plus);
if (r < 0)
return r;
goto finish;
+ }
- } else if (t[0] == '-') {
+ if (t[0] == '-') {
r = parse_sec(t+1, &minus);
if (r < 0)
return r;
goto finish;
+ }
- } else if ((k = endswith(t, " ago"))) {
+ if ((k = endswith(t, " ago"))) {
t = strndupa_safe(t, k - t);
r = parse_sec(t, &minus);
@@ -686,8 +688,9 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
return r;
goto finish;
+ }
- } else if ((k = endswith(t, " left"))) {
+ if ((k = endswith(t, " left"))) {
t = strndupa_safe(t, k - t);
r = parse_sec(t, &plus);

View File

@ -0,0 +1,50 @@
From 50a7d58a24a381e265436c14303e7bb368a4b147 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 14 Feb 2023 03:41:26 +0900
Subject: [PATCH] time-util: do not use strdupa()
The input string may come from command line, config files.
(cherry picked from commit 804537bdc420bb82e54b455b7a10d542c8f029dd)
Related: RHEL-109488
---
src/basic/time-util.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index c5b91fde66..64cdcea594 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -681,9 +681,13 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
}
if ((k = endswith(t, " ago"))) {
- t = strndupa_safe(t, k - t);
+ _cleanup_free_ char *buf = NULL;
- r = parse_sec(t, &minus);
+ buf = strndup(t, k - t);
+ if (!buf)
+ return -ENOMEM;
+
+ r = parse_sec(buf, &minus);
if (r < 0)
return r;
@@ -691,9 +695,13 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
}
if ((k = endswith(t, " left"))) {
- t = strndupa_safe(t, k - t);
+ _cleanup_free_ char *buf = NULL;
+
+ buf = strndup(t, k - t);
+ if (!buf)
+ return -ENOMEM;
- r = parse_sec(t, &plus);
+ r = parse_sec(buf, &plus);
if (r < 0)
return r;

View File

@ -0,0 +1,49 @@
From eabf4db441c885a78d5726838004bbb9436dbf91 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 14 Feb 2023 04:14:24 +0900
Subject: [PATCH] time-util: use result from startswith_no_case()
No functional change, just refactoring.
(cherry picked from commit f2ecfd8bc1e6d09173e9f98c5ac1b19b755a3c25)
Related: RHEL-109488
---
src/basic/time-util.c | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index 64cdcea594..047dad0fec 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -635,7 +635,6 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
time_t x;
usec_t usec, x_usec, plus = 0, minus = 0;
int r, weekday = -1, dst = -1;
- size_t i;
/* Allowed syntaxes:
*
@@ -775,18 +774,13 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
goto from_tm;
}
- for (i = 0; i < ELEMENTSOF(day_nr); i++) {
- size_t skip;
-
- if (!startswith_no_case(t, day_nr[i].name))
- continue;
-
- skip = strlen(day_nr[i].name);
- if (t[skip] != ' ')
+ for (size_t i = 0; i < ELEMENTSOF(day_nr); i++) {
+ k = startswith_no_case(t, day_nr[i].name);
+ if (!k || *k != ' ')
continue;
weekday = day_nr[i].nr;
- t += skip + 1;
+ t = k + 1;
break;
}

View File

@ -0,0 +1,47 @@
From 9f7d490b1c16f0444987dab7ce70287b59d98cf9 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 14 Feb 2023 04:27:27 +0900
Subject: [PATCH] time-util: use usec_add() and usec_sub_unsigned()
And move the check with USEC_TIMESTAMP_FORMATTABLE_MAX at the end,
as usec_add() can handle overflow correctly.
(cherry picked from commit db43717e982e1361eee4bdcd92167d6c47eb627c)
Related: RHEL-109488
---
src/basic/time-util.c | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index 047dad0fec..ba5e17bd9d 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -881,20 +881,17 @@ from_tm:
if (x < 0)
return -EINVAL;
- usec = (usec_t) x * USEC_PER_SEC + x_usec;
- if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
- return -EINVAL;
+ usec = usec_add(x * USEC_PER_SEC, x_usec);
finish:
- if (usec + plus < usec) /* overflow? */
- return -EINVAL;
- usec += plus;
- if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
+ usec = usec_add(usec, plus);
+
+ if (usec < minus)
return -EINVAL;
- if (usec >= minus)
- usec -= minus;
- else
+ usec = usec_sub_unsigned(usec, minus);
+
+ if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
return -EINVAL;
if (ret)

View File

@ -0,0 +1,72 @@
From 2dafd8e6be624f93c757ca9c739d502ed73d79c0 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 14 Feb 2023 04:27:52 +0900
Subject: [PATCH] time-util: shorten code a bit
No functional change, just refactoring.
(cherry picked from commit 1d2c42c5dc765c57b4fba6b7c629093aa20685a8)
Related: RHEL-109488
---
src/basic/time-util.c | 25 +++++++++----------------
1 file changed, 9 insertions(+), 16 deletions(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index ba5e17bd9d..6e1b34b025 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -633,8 +633,9 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
const char *k, *utc = NULL, *tzn = NULL;
struct tm tm, copy;
time_t x;
- usec_t usec, x_usec, plus = 0, minus = 0;
+ usec_t usec, plus = 0, minus = 0;
int r, weekday = -1, dst = -1;
+ unsigned fractional = 0;
/* Allowed syntaxes:
*
@@ -750,7 +751,6 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
}
x = (time_t) (usec / USEC_PER_SEC);
- x_usec = 0;
if (!localtime_or_gmtime_r(&x, &tm, utc))
return -EINVAL;
@@ -859,19 +859,12 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
return -EINVAL;
parse_usec:
- {
- unsigned add;
-
- k++;
- r = parse_fractional_part_u(&k, 6, &add);
- if (r < 0)
- return -EINVAL;
-
- if (*k)
- return -EINVAL;
-
- x_usec = add;
- }
+ k++;
+ r = parse_fractional_part_u(&k, 6, &fractional);
+ if (r < 0)
+ return -EINVAL;
+ if (*k != '\0')
+ return -EINVAL;
from_tm:
if (weekday >= 0 && tm.tm_wday != weekday)
@@ -881,7 +874,7 @@ from_tm:
if (x < 0)
return -EINVAL;
- usec = usec_add(x * USEC_PER_SEC, x_usec);
+ usec = usec_add(x * USEC_PER_SEC, fractional);
finish:
usec = usec_add(usec, plus);

View File

@ -0,0 +1,69 @@
From 5bfcf206bd16079ae8030a2c912ee8377a3f6d5f Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Wed, 15 Feb 2023 13:46:50 +0900
Subject: [PATCH] time-util: rename variables
No functional changes, just refactoring.
(cherry picked from commit a83c1baaeb510f1916d2d8cf0324d100708c7073)
Related: RHEL-109488
---
src/basic/time-util.c | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index 6e1b34b025..d468848d09 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -632,10 +632,10 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
const char *k, *utc = NULL, *tzn = NULL;
struct tm tm, copy;
- time_t x;
usec_t usec, plus = 0, minus = 0;
- int r, weekday = -1, dst = -1;
+ int r, weekday = -1, isdst = -1;
unsigned fractional = 0;
+ time_t sec;
/* Allowed syntaxes:
*
@@ -744,18 +744,18 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
if (IN_SET(j, 0, 1)) {
/* Found one of the two timezones specified. */
t = strndupa_safe(t, e - t - 1);
- dst = j;
+ isdst = j;
tzn = tzname[j];
}
}
}
- x = (time_t) (usec / USEC_PER_SEC);
+ sec = (time_t) (usec / USEC_PER_SEC);
- if (!localtime_or_gmtime_r(&x, &tm, utc))
+ if (!localtime_or_gmtime_r(&sec, &tm, utc))
return -EINVAL;
- tm.tm_isdst = dst;
+ tm.tm_isdst = isdst;
if (!with_tz && tzn)
tm.tm_zone = tzn;
@@ -870,11 +870,11 @@ from_tm:
if (weekday >= 0 && tm.tm_wday != weekday)
return -EINVAL;
- x = mktime_or_timegm(&tm, utc);
- if (x < 0)
+ sec = mktime_or_timegm(&tm, utc);
+ if (sec < 0)
return -EINVAL;
- usec = usec_add(x * USEC_PER_SEC, fractional);
+ usec = usec_add(sec * USEC_PER_SEC, fractional);
finish:
usec = usec_add(usec, plus);

View File

@ -0,0 +1,44 @@
From a1a7ab09cf96e30cfd0c89efaf8d03a5dc81e928 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Wed, 15 Feb 2023 13:51:15 +0900
Subject: [PATCH] time-util: drop unnecessary assignment of timezone name
As mktime() does not use timezone neme.
(cherry picked from commit 97c5f7ba1f50fcd7b982b995b46692c8cad4afaa)
Related: RHEL-109488
---
src/basic/time-util.c | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index d468848d09..92928e88a4 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -630,7 +630,7 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
{ "Sat", 6 },
};
- const char *k, *utc = NULL, *tzn = NULL;
+ const char *k, *utc = NULL;
struct tm tm, copy;
usec_t usec, plus = 0, minus = 0;
int r, weekday = -1, isdst = -1;
@@ -745,7 +745,6 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
/* Found one of the two timezones specified. */
t = strndupa_safe(t, e - t - 1);
isdst = j;
- tzn = tzname[j];
}
}
}
@@ -756,8 +755,6 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
return -EINVAL;
tm.tm_isdst = isdst;
- if (!with_tz && tzn)
- tm.tm_zone = tzn;
if (streq(t, "today")) {
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;

View File

@ -0,0 +1,275 @@
From e4b1932cd1c0ad2d42a6271cb651e6979b5962ed Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 14 Feb 2023 03:39:15 +0900
Subject: [PATCH] time-util: make parse_timestamp() use the RFC-822/ISO 8601
standard timezone spec
If the timezone is specified with a number e.g. +0900 or so, then
let's parse the time as a UTC, and adjust it with the specified time
shift.
Otherwise, if an area has timezone change, e.g.
---
Africa/Casablanca Sun Jun 17 01:59:59 2018 UT = Sun Jun 17 01:59:59 2018 +00 isdst=0 gmtoff=0
Africa/Casablanca Sun Jun 17 02:00:00 2018 UT = Sun Jun 17 03:00:00 2018 +01 isdst=1 gmtoff=3600
Africa/Casablanca Sun Oct 28 01:59:59 2018 UT = Sun Oct 28 02:59:59 2018 +01 isdst=1 gmtoff=3600
Africa/Casablanca Sun Oct 28 02:00:00 2018 UT = Sun Oct 28 03:00:00 2018 +01 isdst=0 gmtoff=3600
---
then we could not determine isdst from the timezone (+01 in the above)
and mktime() will provide wrong results.
Fixes #26370.
(cherry picked from commit 7a9afae6040af0417d893328cb44b622dcdcb94f)
Related: RHEL-109488
---
src/basic/time-util.c | 174 +++++++++++++++++++++++++++---------------
1 file changed, 112 insertions(+), 62 deletions(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index 92928e88a4..6fbbc2f655 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -609,7 +609,14 @@ char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
return buf;
}
-static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
+static int parse_timestamp_impl(
+ const char *t,
+ bool with_tz,
+ bool utc,
+ int isdst,
+ long gmtoff,
+ usec_t *ret) {
+
static const struct {
const char *name;
const int nr;
@@ -630,11 +637,11 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
{ "Sat", 6 },
};
- const char *k, *utc = NULL;
- struct tm tm, copy;
usec_t usec, plus = 0, minus = 0;
- int r, weekday = -1, isdst = -1;
+ int r, weekday = -1;
unsigned fractional = 0;
+ const char *k;
+ struct tm tm, copy;
time_t sec;
/* Allowed syntaxes:
@@ -707,46 +714,6 @@ static int parse_timestamp_impl(const char *t, usec_t *ret, bool with_tz) {
goto finish;
}
-
- /* See if the timestamp is suffixed with UTC */
- utc = endswith_no_case(t, " UTC");
- if (utc)
- t = strndupa_safe(t, utc - t);
- else {
- const char *e = NULL;
- int j;
-
- tzset();
-
- /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note
- * that we only support the local timezones here, nothing else. Not because we
- * wouldn't want to, but simply because there are no nice APIs available to cover
- * this. By accepting the local time zone strings, we make sure that all timestamps
- * written by format_timestamp() can be parsed correctly, even though we don't
- * support arbitrary timezone specifications. */
-
- for (j = 0; j <= 1; j++) {
-
- if (isempty(tzname[j]))
- continue;
-
- e = endswith_no_case(t, tzname[j]);
- if (!e)
- continue;
- if (e == t)
- continue;
- if (e[-1] != ' ')
- continue;
-
- break;
- }
-
- if (IN_SET(j, 0, 1)) {
- /* Found one of the two timezones specified. */
- t = strndupa_safe(t, e - t - 1);
- isdst = j;
- }
- }
}
sec = (time_t) (usec / USEC_PER_SEC);
@@ -864,9 +831,32 @@ parse_usec:
return -EINVAL;
from_tm:
+ assert(plus == 0);
+ assert(minus == 0);
+
if (weekday >= 0 && tm.tm_wday != weekday)
return -EINVAL;
+ if (gmtoff < 0) {
+ plus = -gmtoff * USEC_PER_SEC;
+
+ /* If gmtoff is negative, the string maye be too old to be parsed as UTC.
+ * E.g. 1969-12-31 23:00:00 -06 == 1970-01-01 05:00:00 UTC
+ * We assumed that gmtoff is in the range of -24:00…+24:00, hence the only date we need to
+ * handle here is 1969-12-31. So, let's shift the date with one day, then subtract the shift
+ * later. */
+ if (tm.tm_year == 69 && tm.tm_mon == 11 && tm.tm_mday == 31) {
+ /* Thu 1970-01-01-00:00:00 */
+ tm.tm_year = 70;
+ tm.tm_mon = 0;
+ tm.tm_mday = 1;
+ tm.tm_wday = 4;
+ tm.tm_yday = 0;
+ minus = USEC_PER_DAY;
+ }
+ } else
+ minus = gmtoff * USEC_PER_SEC;
+
sec = mktime_or_timegm(&tm, utc);
if (sec < 0)
return -EINVAL;
@@ -889,24 +879,93 @@ finish:
return 0;
}
+static int parse_timestamp_with_tz(const char *t, size_t len, bool utc, int isdst, long gmtoff, usec_t *ret) {
+ _cleanup_free_ char *buf = NULL;
+
+ assert(t);
+ assert(len > 0);
+
+ buf = strndup(t, len);
+ if (!buf)
+ return -ENOMEM;
+
+ return parse_timestamp_impl(buf, /* with_tz = */ true, utc, isdst, gmtoff, ret);
+}
+
+static int parse_timestamp_maybe_with_tz(const char *t, size_t len, bool valid_tz, usec_t *ret) {
+ assert(t);
+ assert(len > 0);
+
+ tzset();
+
+ for (int j = 0; j <= 1; j++) {
+ if (isempty(tzname[j]))
+ continue;
+
+ if (!streq(t + len + 1, tzname[j]))
+ continue;
+
+ /* The specified timezone matches tzname[] of the local timezone. */
+ return parse_timestamp_with_tz(t, len, /* utc = */ false, /* isdst = */ j, /* gmtoff = */ 0, ret);
+ }
+
+ if (valid_tz)
+ /* We know that the specified timezone is a valid zoneinfo (e.g. Asia/Tokyo). So, simply drop
+ * the timezone and parse the remaining string as a local time. */
+ return parse_timestamp_with_tz(t, len, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
+
+ return parse_timestamp_impl(t, /* with_tz = */ false, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
+}
+
typedef struct ParseTimestampResult {
usec_t usec;
int return_value;
} ParseTimestampResult;
int parse_timestamp(const char *t, usec_t *ret) {
- char *last_space, *tz = NULL;
ParseTimestampResult *shared, tmp;
+ const char *k, *tz, *space;
+ struct tm tm;
int r;
assert(t);
- last_space = strrchr(t, ' ');
- if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG))
- tz = last_space + 1;
+ space = strrchr(t, ' ');
+ if (!space)
+ return parse_timestamp_impl(t, /* with_tz = */ false, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
- if (!tz || endswith_no_case(t, " UTC"))
- return parse_timestamp_impl(t, ret, false);
+ /* The string starts with space. */
+ if (space == t)
+ return -EINVAL;
+
+ /* Shortcut, parse the string as UTC. */
+ if (streq(space + 1, "UTC"))
+ return parse_timestamp_with_tz(t, space - t, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret);
+
+ /* If the timezone is compatible with RFC-822/ISO 8601 (e.g. +06, or -03:00) then parse the string as
+ * UTC and shift the result. */
+ k = strptime(space + 1, "%z", &tm);
+ if (k && *k == '\0') {
+ /* glibc accepts gmtoff more than 24 hours, but we refuse it. */
+ if ((usec_t) labs(tm.tm_gmtoff) > USEC_PER_DAY / USEC_PER_SEC)
+ return -EINVAL;
+
+ return parse_timestamp_with_tz(t, space - t, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret);
+ }
+
+ /* If the last word is not a timezone file (e.g. Asia/Tokyo), then let's check if it matches
+ * tzname[] of the local timezone, e.g. JST or CEST. */
+ if (!timezone_is_valid(space + 1, LOG_DEBUG))
+ return parse_timestamp_maybe_with_tz(t, space - t, /* valid_tz = */ false, ret);
+
+ /* Shortcut. If the current $TZ is equivalent to the specified timezone, it is not necessary to fork
+ * the process. */
+ tz = getenv("TZ");
+ if (tz && *tz == ':' && streq(tz + 1, space + 1))
+ return parse_timestamp_maybe_with_tz(t, space - t, /* valid_tz = */ true, ret);
+
+ /* Otherwise, to avoid polluting the current environment variables, let's fork the process and set
+ * the specified timezone in the child process. */
shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if (shared == MAP_FAILED)
@@ -918,11 +977,10 @@ int parse_timestamp(const char *t, usec_t *ret) {
return r;
}
if (r == 0) {
- bool with_tz = true;
- char *colon_tz;
+ const char *colon_tz;
/* tzset(3) says $TZ should be prefixed with ":" if we reference timezone files */
- colon_tz = strjoina(":", tz);
+ colon_tz = strjoina(":", space + 1);
if (setenv("TZ", colon_tz, 1) != 0) {
shared->return_value = negative_errno();
@@ -931,15 +989,7 @@ int parse_timestamp(const char *t, usec_t *ret) {
tzset();
- /* If there is a timezone that matches the tzname fields, leave the parsing to the implementation.
- * Otherwise just cut it off. */
- with_tz = !STR_IN_SET(tz, tzname[0], tzname[1]);
-
- /* Cut off the timezone if we don't need it. */
- if (with_tz)
- t = strndupa_safe(t, last_space - t);
-
- shared->return_value = parse_timestamp_impl(t, &shared->usec, with_tz);
+ shared->return_value = parse_timestamp_maybe_with_tz(t, space - t, /* valid_tz = */ true, &shared->usec);
_exit(EXIT_SUCCESS);
}

View File

@ -0,0 +1,27 @@
From 008aed2736dac288700f1c177690904da9c5137d Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Fri, 3 Mar 2023 15:24:23 +0900
Subject: [PATCH] time-util: fix typo
Follow-up for 7a9afae6040af0417d893328cb44b622dcdcb94f.
(cherry picked from commit ca9c9d8d8e999fd80fc43d002c8d5b20c4c1a0a4)
Related: RHEL-109488
---
src/basic/time-util.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index 6fbbc2f655..66bf4c17bb 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -840,7 +840,7 @@ from_tm:
if (gmtoff < 0) {
plus = -gmtoff * USEC_PER_SEC;
- /* If gmtoff is negative, the string maye be too old to be parsed as UTC.
+ /* If gmtoff is negative, the string may be too old to be parsed as UTC.
* E.g. 1969-12-31 23:00:00 -06 == 1970-01-01 05:00:00 UTC
* We assumed that gmtoff is in the range of -24:00…+24:00, hence the only date we need to
* handle here is 1969-12-31. So, let's shift the date with one day, then subtract the shift

View File

@ -0,0 +1,36 @@
From 5e10617a3a450a2bb6fbe031d1861559192bc85c Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <fsumsal@redhat.com>
Date: Thu, 4 Sep 2025 15:31:27 +0200
Subject: [PATCH] ci: bump the tools tree to F42
To fix the failing SB key enrollment:
3h3hBdsDxe: loading Boot0001 "UEFI QEMU QEMU HARDDISK " from PciRoot(0x0)/Pci(0x4,0x0)/Scsi(0x1,0x0)
BdsDxe: starting Boot0001 "UEFI QEMU QEMU HARDDISK " from PciRoot(0x0)/Pci(0x4,0x0)/Scsi(0x1,0x0)
systemd-boot@0x7d189000,0x7d1a6000
Enrolling secure boot keys from directory: \loader\keys\auto
Failed to write PK secure boot variable: Security violation
systemd-stub@0x670fd000,0x67115000
Overlapping PE sections detected. Boot may fail due to image memory corruption!
EFI stub: Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path
EFI stub: Measured initrd data into PCR 9
rhel-only: ci
Related: RHEL-109488
---
.github/workflows/mkosi.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml
index 808ae0148e..2694ba14ec 100644
--- a/.github/workflows/mkosi.yml
+++ b/.github/workflows/mkosi.yml
@@ -83,7 +83,7 @@ jobs:
[Host]
ToolsTree=default
ToolsTreeDistribution=fedora
- ToolsTreeRelease=41
+ ToolsTreeRelease=42
# Sometimes we run on a host with /dev/kvm, but it is broken, so explicitly disable it
QemuKvm=no
EOF

View File

@ -0,0 +1,27 @@
From 8cec69eb3fe332ac618c34780dd5275d931d9bf8 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Sun, 1 Dec 2024 14:46:40 +0900
Subject: [PATCH] journald: extend STDOUT_STREAMS_MAX to 64k
Closes #35390.
(cherry picked from commit c576ba7182f54f352c03f0768c9178b173fb8bcb)
Resolves: RHEL-111065
---
src/journal/journald-stream.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c
index 8bdcd8c2ae..3ec6e7fcf3 100644
--- a/src/journal/journald-stream.c
+++ b/src/journal/journald-stream.c
@@ -38,7 +38,7 @@
#include "unit-name.h"
#include "user-util.h"
-#define STDOUT_STREAMS_MAX 4096
+#define STDOUT_STREAMS_MAX (64*1024)
/* During the "setup" protocol phase of the stream logic let's define a different maximum line length than
* during the actual operational phase. We want to allow users to specify very short line lengths after all,

View File

@ -0,0 +1,163 @@
From fa27bab14e2ede08dcac41ab78901ab3b653e556 Mon Sep 17 00:00:00 2001
From: Jan Macku <jamacku@redhat.com>
Date: Tue, 26 Aug 2025 15:30:49 +0200
Subject: [PATCH] Revert "Revert "udev-builtin-net_id: use firmware_node/sun
for ID_NET_NAME_SLOT""
This reverts commit ae92c09f0ddfea2ff6042e152eec9af86013da38.
Also introduce rhel-9.8 naming scheme.
rhel-only: policy
Resolves: RHEL-50103
---
man/systemd.net-naming-scheme.xml | 9 ++++-
src/shared/netif-naming-scheme.c | 1 +
src/shared/netif-naming-scheme.h | 2 +
src/udev/udev-builtin-net_id.c | 66 +++++++++++++++++++++++++++----
4 files changed, 70 insertions(+), 8 deletions(-)
diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml
index 34e8e46459..c6ee7b4b6e 100644
--- a/man/systemd.net-naming-scheme.xml
+++ b/man/systemd.net-naming-scheme.xml
@@ -526,6 +526,13 @@
<listitem><para>Same as naming scheme <constant>rhel-9.5</constant>.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><constant>rhel-9.8</constant></term>
+
+ <listitem>
+ <para>PCI slot number is now read from <constant>firmware_node/sun</constant> sysfs file.</para></listitem>
+ </varlistentry>
+
</variablelist>
<para>By default <constant>rhel-9.0</constant> is used.</para>
@@ -679,7 +686,7 @@ ID_NET_NAME_ONBOARD_LABEL=Ethernet Port 1
</example>
<example>
- <title>PCI Ethernet card in hotplug slot with firmware index number</title>
+ <title>PCI Ethernet card in slot with firmware index number</title>
<programlisting># /sys/devices/pci0000:00/0000:00:1c.3/0000:05:00.0/net/ens1
ID_NET_NAME_MAC=enx000000000466
diff --git a/src/shared/netif-naming-scheme.c b/src/shared/netif-naming-scheme.c
index a38c3f1e2f..4ed866491e 100644
--- a/src/shared/netif-naming-scheme.c
+++ b/src/shared/netif-naming-scheme.c
@@ -47,6 +47,7 @@ static const NamingScheme naming_schemes[] = {
{ "rhel-9.5", NAMING_RHEL_9_5 },
{ "rhel-9.6", NAMING_RHEL_9_6 },
{ "rhel-9.7", NAMING_RHEL_9_7 },
+ { "rhel-9.8", NAMING_RHEL_9_8 },
/* … add more schemes here, as the logic to name devices is updated … */
EXTRA_NET_NAMING_MAP
diff --git a/src/shared/netif-naming-scheme.h b/src/shared/netif-naming-scheme.h
index 3cba656707..c16476522a 100644
--- a/src/shared/netif-naming-scheme.h
+++ b/src/shared/netif-naming-scheme.h
@@ -42,6 +42,7 @@ typedef enum NamingSchemeFlags {
* This is disabled since rhel-9.5, as it seems not to work at least for some setups. See upstream issue #28929. */
NAMING_DEVICETREE_ALIASES = 1 << 15, /* Generate names from devicetree aliases */
NAMING_SR_IOV_R = 1 << 17, /* Use "r" suffix for SR-IOV VF representors */
+ NAMING_FIRMWARE_NODE_SUN = 1 << 18, /* Use firmware_node/sun to get PCI slot number */
/* And now the masks that combine the features above */
NAMING_V238 = 0,
@@ -76,6 +77,7 @@ typedef enum NamingSchemeFlags {
NAMING_RHEL_9_5 = NAMING_RHEL_9_4 & ~NAMING_BRIDGE_MULTIFUNCTION_SLOT,
NAMING_RHEL_9_6 = NAMING_RHEL_9_5,
NAMING_RHEL_9_7 = NAMING_RHEL_9_5,
+ NAMING_RHEL_9_8 = NAMING_RHEL_9_5 | NAMING_FIRMWARE_NODE_SUN,
EXTRA_NET_NAMING_SCHEMES
diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
index 16c9971876..291fb4ba36 100644
--- a/src/udev/udev-builtin-net_id.c
+++ b/src/udev/udev-builtin-net_id.c
@@ -442,6 +442,51 @@ static int pci_get_hotplug_slot(sd_device *dev, uint32_t *ret) {
return -ENOENT;
}
+static int get_device_firmware_node_sun(sd_device *dev, uint32_t *ret) {
+ const char *attr;
+ int r;
+
+ assert(dev);
+ assert(ret);
+
+ r = device_get_sysattr_value_filtered(dev, "firmware_node/sun", &attr);
+ if (r < 0)
+ return log_device_debug_errno(dev, r, "Failed to read firmware_node/sun, ignoring: %m");
+
+ r = safe_atou32(attr, ret);
+ if (r < 0)
+ return log_device_warning_errno(dev, r, "Failed to parse firmware_node/sun '%s', ignoring: %m", attr);
+
+ return 0;
+}
+
+static int pci_get_slot_from_firmware_node_sun(sd_device *dev, uint32_t *ret) {
+ int r;
+ sd_device *slot_dev;
+
+ assert(dev);
+ assert(ret);
+
+ /* Try getting the ACPI _SUN for the device */
+ if (get_device_firmware_node_sun(dev, ret) >= 0)
+ return 0;
+
+ r = sd_device_get_parent_with_subsystem_devtype(dev, "pci", NULL, &slot_dev);
+ if (r < 0)
+ return log_device_debug_errno(dev, r, "Failed to find pci parent, ignoring: %m");
+
+ if (is_pci_bridge(slot_dev) && is_pci_multifunction(dev) <= 0)
+ return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ESTALE),
+ "Not using slot information because the parent pcieport "
+ "is a bridge and the PCI device is not multifunction.");
+
+ /* Try getting the ACPI _SUN from the parent pcieport */
+ if (get_device_firmware_node_sun(slot_dev, ret) >= 0)
+ return 0;
+
+ return -ENOENT;
+}
+
static int dev_pci_slot(sd_device *dev, const LinkInfo *info, NetNames *names) {
const char *sysname, *attr;
unsigned domain, bus, slot, func;
@@ -517,13 +562,20 @@ static int dev_pci_slot(sd_device *dev, const LinkInfo *info, NetNames *names) {
domain, bus, slot, func, strempty(info->phys_port_name), dev_port,
special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), empty_to_na(names->pci_path));
- r = pci_get_hotplug_slot(names->pcidev, &hotplug_slot);
- if (r < 0)
- return r;
- if (r > 0)
- /* If the hotplug slot is found through the function ID, then drop the domain from the name.
- * See comments in parse_hotplug_slot_from_function_id(). */
- domain = 0;
+ if (naming_scheme_has(NAMING_FIRMWARE_NODE_SUN))
+ r = pci_get_slot_from_firmware_node_sun(names->pcidev, &hotplug_slot);
+ else
+ r = -1;
+ /* If we don't find a slot using firmware_node/sun, fallback to hotplug_slot */
+ if (r < 0) {
+ r = pci_get_hotplug_slot(names->pcidev, &hotplug_slot);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ /* If the hotplug slot is found through the function ID, then drop the domain from the name.
+ * See comments in parse_hotplug_slot_from_function_id(). */
+ domain = 0;
+ }
s = names->pci_slot;
l = sizeof(names->pci_slot);

View File

@ -0,0 +1,43 @@
From 743c0fbd5ea56f926b36d6bbfc2d609bacd4353e Mon Sep 17 00:00:00 2001
From: Etienne Champetier <e.champetier@ateme.com>
Date: Thu, 22 Aug 2024 16:30:56 -0400
Subject: [PATCH] udev-builtin-net_id: ignore firmware_node/sun == 0
Since ID_NET_NAME_SLOT was introduced we ignore slot == 0
https://github.com/systemd/systemd/blob/0035597a30d120f70df2dd7da3d6128fb8ba6051/src/udev/udev-builtin-net_id.c#L139
Qemu sets _SUN to PCI_SLOT() for all NICs, so _SUN is not unique.
https://gitlab.com/qemu-project/qemu/-/issues/2530
In my tests with libvirt I can only set 'slot="0x00"' in interface definition,
so all NICs end up with _SUN == 0, and this commit is enough to avoid the issue.
Fixes 0a4ecc54cb9f2d3418b970c51bfadb69c34ae9eb
(cherry picked from commit 448f9f81fd32f8658449101ada2eadd853f6b06b)
Resolves: RHEL-50103
---
src/udev/udev-builtin-net_id.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
index 291fb4ba36..e1895a38c0 100644
--- a/src/udev/udev-builtin-net_id.c
+++ b/src/udev/udev-builtin-net_id.c
@@ -453,10 +453,14 @@ static int get_device_firmware_node_sun(sd_device *dev, uint32_t *ret) {
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to read firmware_node/sun, ignoring: %m");
- r = safe_atou32(attr, ret);
+ uint32_t sun;
+ r = safe_atou32(attr, &sun);
if (r < 0)
return log_device_warning_errno(dev, r, "Failed to parse firmware_node/sun '%s', ignoring: %m", attr);
+ if (sun == 0)
+ return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "firmware_node/sun == 0, ignoring: %m");
+ *ret = sun;
return 0;
}

View File

@ -0,0 +1,36 @@
From c14ed0d0bf700b9959359004d5eef50d4d2db951 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Tue, 10 Jan 2023 14:08:41 +0100
Subject: [PATCH] fundamental: fix compile check for explicit_bzero
Our HAVE_* variables are defined to 0 or 1, so '#if defined(HAVE_*)' is always true.
The variable is not defined when compiling for EFI though, so we need the
additional guard.
Fixup for 3f92dc2fd4070b213e6bc85263a9bef06ec9a486.
(I don't want to do something like add -DHAVE_EXPLICIT_BZERO=0 to the commandline
in src/efi/boot/meson.build, because this quite verbose. Our compilation commandlines
are very long already. Let's instead keep this localized in this one spot in the
source file.)x
(cherry picked from commit 5deb391c6e6d2b8fd7b94234efea49cd6bee0d76)
Resolves: RHEL-108568
---
src/fundamental/memory-util-fundamental.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/fundamental/memory-util-fundamental.h b/src/fundamental/memory-util-fundamental.h
index 8f50d8b8e1..e0ae33dc0d 100644
--- a/src/fundamental/memory-util-fundamental.h
+++ b/src/fundamental/memory-util-fundamental.h
@@ -11,7 +11,7 @@
#include "macro-fundamental.h"
-#if defined(HAVE_EXPLICIT_BZERO)
+#if !defined(SD_BOOT) && HAVE_EXPLICIT_BZERO
static inline void *explicit_bzero_safe(void *p, size_t l) {
if (p && l > 0)
explicit_bzero(p, l);

View File

@ -0,0 +1,74 @@
From 9906687d228f71768ddf115799ebb39272d1f655 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Sun, 12 Mar 2023 20:57:16 +0900
Subject: [PATCH] time-util: make USEC_TIMESTAMP_FORMATTABLE_MAX for 32bit
system off by one day
As the same reason why we take one day off for 64bit case.
This also makes both upper bounds always defined for testing.
(cherry picked from commit bd5770da76ee157d3b31323ed2d22f5d9082bb36)
Related: RHEL-109488
---
src/basic/time-util.h | 14 +++++++++-----
src/test/test-date.c | 4 ++--
src/test/test-time-util.c | 2 +-
3 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/src/basic/time-util.h b/src/basic/time-util.h
index 9d44cac747..7f8bda0948 100644
--- a/src/basic/time-util.h
+++ b/src/basic/time-util.h
@@ -200,13 +200,17 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) {
return usec_sub_unsigned(timestamp, (usec_t) delta);
}
+/* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit
+ * year territory. However, since we want to stay away from this in all timezones we take one day off. */
+#define USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT ((usec_t) 253402214399000000) /* Thu 9999-12-30 23:59:59 UTC */
+/* With a 32bit time_t we can't go beyond 2038...
+ * We parse timestamp with RFC-822/ISO 8601 (e.g. +06, or -03:00) as UTC, hence the upper bound must be off
+ * by USEC_PER_DAY. See parse_timestamp() for more details. */
+#define USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT (((usec_t) INT32_MAX) * USEC_PER_SEC - USEC_PER_DAY)
#if SIZEOF_TIME_T == 8
- /* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit
- * year territory. However, since we want to stay away from this in all timezones we take one day off. */
-# define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000)
+# define USEC_TIMESTAMP_FORMATTABLE_MAX USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT
#elif SIZEOF_TIME_T == 4
-/* With a 32bit time_t we can't go beyond 2038... */
-# define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000)
+# define USEC_TIMESTAMP_FORMATTABLE_MAX USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT
#else
# error "Yuck, time_t is neither 4 nor 8 bytes wide?"
#endif
diff --git a/src/test/test-date.c b/src/test/test-date.c
index cc11bd999e..ef316eeca7 100644
--- a/src/test/test-date.c
+++ b/src/test/test-date.c
@@ -104,8 +104,8 @@ int main(int argc, char *argv[]) {
test_should_fail("9999-12-31 00:00:00 UTC");
test_should_fail("10000-01-01 00:00:00 UTC");
#elif SIZEOF_TIME_T == 4
- test_should_pass("2038-01-19 03:14:07 UTC");
- test_should_fail("2038-01-19 03:14:08 UTC");
+ test_should_pass("2038-01-18 03:14:07 UTC");
+ test_should_fail("2038-01-18 03:14:08 UTC");
#endif
return 0;
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index 21b05a3010..71ef5906ba 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -550,7 +550,7 @@ TEST(format_timestamp_utc) {
test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Thu 9999-12-30 23:59:59 UTC");
test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, "--- XXXX-XX-XX XX:XX:XX");
#elif SIZEOF_TIME_T == 4
- test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Tue 2038-01-19 03:14:07 UTC");
+ test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Mon 2038-01-18 03:14:07 UTC");
test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, "--- XXXX-XX-XX XX:XX:XX");
#endif

View File

@ -0,0 +1,99 @@
From 0abac4254115db7d86549b517163a13de9377346 Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Tue, 23 Sep 2025 14:28:33 +0200
Subject: [PATCH] test: rename TEST-53-ISSUE-16347 to TEST-53-TIMER
And split the existing test into a separate subtest.
(cherry picked from commit 953c347fb6f293acbd6da009646bfc071b68ddd7)
Related: RHEL-118215
---
.../Makefile | 0
.../test.sh | 0
test/units/testsuite-53.issue-16347.sh | 27 ++++++++++++++++++
test/units/testsuite-53.sh | 28 +++----------------
4 files changed, 31 insertions(+), 24 deletions(-)
rename test/{TEST-53-ISSUE-16347 => TEST-53-TIMER}/Makefile (100%)
rename test/{TEST-53-ISSUE-16347 => TEST-53-TIMER}/test.sh (100%)
create mode 100755 test/units/testsuite-53.issue-16347.sh
diff --git a/test/TEST-53-ISSUE-16347/Makefile b/test/TEST-53-TIMER/Makefile
similarity index 100%
rename from test/TEST-53-ISSUE-16347/Makefile
rename to test/TEST-53-TIMER/Makefile
diff --git a/test/TEST-53-ISSUE-16347/test.sh b/test/TEST-53-TIMER/test.sh
similarity index 100%
rename from test/TEST-53-ISSUE-16347/test.sh
rename to test/TEST-53-TIMER/test.sh
diff --git a/test/units/testsuite-53.issue-16347.sh b/test/units/testsuite-53.issue-16347.sh
new file mode 100755
index 0000000000..8b266145cd
--- /dev/null
+++ b/test/units/testsuite-53.issue-16347.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Reset host date to current time, 3 days in the past.
+date -s "-3 days"
+trap 'date -s "+3 days"' EXIT
+
+# Run a timer for every 15 minutes.
+systemd-run --unit test-timer --on-calendar "*:0/15:0" true
+
+next_elapsed=$(systemctl show test-timer.timer -p NextElapseUSecRealtime --value)
+next_elapsed=$(date -d "${next_elapsed}" +%s)
+now=$(date +%s)
+time_delta=$((next_elapsed - now))
+
+# Check that the timer will elapse in less than 20 minutes.
+if [[ "$time_delta" -lt 0 || "$time_delta" -gt 1200 ]]; then
+ echo 'Timer elapse outside of the expected 20 minute window.'
+ echo " next_elapsed=${next_elapsed}"
+ echo " now=${now}"
+ echo " time_delta=${time_delta}"
+ echo
+
+ exit 1
+fi
diff --git a/test/units/testsuite-53.sh b/test/units/testsuite-53.sh
index 84cd66129d..9c2a033aa9 100755
--- a/test/units/testsuite-53.sh
+++ b/test/units/testsuite-53.sh
@@ -3,29 +3,9 @@
set -eux
set -o pipefail
-: >/failed
+# shellcheck source=test/units/test-control.sh
+. "$(dirname "$0")"/test-control.sh
-# Reset host date to current time, 3 days in the past.
-date -s "-3 days"
+run_subtests
-# Run a timer for every 15 minutes.
-systemd-run --unit test-timer --on-calendar "*:0/15:0" true
-
-next_elapsed=$(systemctl show test-timer.timer -p NextElapseUSecRealtime --value)
-next_elapsed=$(date -d "${next_elapsed}" +%s)
-now=$(date +%s)
-time_delta=$((next_elapsed - now))
-
-# Check that the timer will elapse in less than 20 minutes.
-((0 < time_delta && time_delta < 1200)) || {
- echo 'Timer elapse outside of the expected 20 minute window.'
- echo " next_elapsed=${next_elapsed}"
- echo " now=${now}"
- echo " time_delta=${time_delta}"
- echo ''
-} >>/failed
-
-if test ! -s /failed ; then
- rm -f /failed
- touch /testok
-fi
+touch /testok

View File

@ -0,0 +1,101 @@
From 53b158318d6bfbb1e59b91bfad15e7d128622efb Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Tue, 23 Sep 2025 17:42:01 +0200
Subject: [PATCH] test: restarting elapsed timer shouldn't trigger the
corresponding service
Provides coverage for:
- https://github.com/systemd/systemd/issues/31231
- https://github.com/systemd/systemd/issues/35805
(cherry picked from commit 5730a400fd5ee82566fe03eb832121a0d4bc26b6)
Related: RHEL-118215
---
test/units/testsuite-53.restart-trigger.sh | 77 ++++++++++++++++++++++
1 file changed, 77 insertions(+)
create mode 100755 test/units/testsuite-53.restart-trigger.sh
diff --git a/test/units/testsuite-53.restart-trigger.sh b/test/units/testsuite-53.restart-trigger.sh
new file mode 100755
index 0000000000..057f379ddc
--- /dev/null
+++ b/test/units/testsuite-53.restart-trigger.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Restarting an already elapsed timer shouldn't immediately trigger the corresponding service unit.
+#
+# Provides coverage for:
+# - https://github.com/systemd/systemd/issues/31231
+# - https://github.com/systemd/systemd/issues/35805
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/test-control.sh
+. "$(dirname "$0")"/util.sh
+
+UNIT_NAME="timer-restart-$RANDOM"
+TEST_MESSAGE="Hello from timer $RANDOM"
+
+# Setup
+cat >"/run/systemd/system/$UNIT_NAME.timer" <<EOF
+[Timer]
+OnCalendar=$(date --date="+1 hour" "+%Y-%m-%d %H:%M:%S")
+AccuracySec=1s
+EOF
+
+cat >"/run/systemd/system/$UNIT_NAME.service" <<EOF
+[Service]
+ExecStart=echo "$TEST_MESSAGE"
+EOF
+
+systemctl daemon-reload
+
+JOURNAL_TS="$(date "+%s")"
+# Paranoia check that the test message is not already in the logs
+(! journalctl -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE")
+
+# Restart time timer and move time forward by 2 hours to trigger the timer
+systemctl restart "$UNIT_NAME.timer"
+systemctl status "$UNIT_NAME.timer"
+
+date -s '+2 hours'
+trap 'date -s "-2 hours"' EXIT
+sleep 1
+systemctl status "$UNIT_NAME.timer"
+assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE" | wc -l)" "1"
+
+# Restarting the timer unit shouldn't trigger neither the timer nor the service, so these
+# fields should remain constant through the following tests
+SERVICE_INV_ID="$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
+TIMER_LAST_TRIGGER="$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
+
+# Now restart the timer and check if the timer and the service weren't triggered again
+systemctl restart "$UNIT_NAME.timer"
+sleep 5
+assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE" | wc -l)" "1"
+assert_eq "$SERVICE_INV_ID" "$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
+assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
+
+# Set the timer into the past, restart it, and again check if it wasn't triggered
+TIMER_TS="$(date --date="-1 day" "+%Y-%m-%d %H:%M:%S")"
+mkdir "/run/systemd/system/$UNIT_NAME.timer.d/"
+cat >"/run/systemd/system/$UNIT_NAME.timer.d/99-override.conf" <<EOF
+[Timer]
+OnCalendar=$TIMER_TS
+EOF
+systemctl daemon-reload
+systemctl status "$UNIT_NAME.timer"
+assert_in "OnCalendar=$TIMER_TS" "$(systemctl show -P TimersCalendar "$UNIT_NAME".timer)"
+systemctl restart "$UNIT_NAME.timer"
+sleep 5
+assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE" | wc -l)" "1"
+assert_eq "$SERVICE_INV_ID" "$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
+assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
+
+# Cleanup
+systemctl stop "$UNIT_NAME".{timer,service}
+rm -f "/run/systemd/system/$UNIT_NAME".{timer,service}
+systemctl daemon-reload

View File

@ -0,0 +1,156 @@
From 14aa00df0638e8011dd7360eb58d3b0ac64a818a Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Tue, 23 Sep 2025 21:04:12 +0200
Subject: [PATCH] test: check the next elapse timer timestamp after
deserialization
When deserializing a serialized timer unit with RandomizedDelaySec= set,
systemd should use the last inactive exit timestamp instead of current
realtime to calculate the new next elapse, so the timer unit actually
runs in the given calendar window.
Provides coverage for:
- https://github.com/systemd/systemd/issues/18678
- https://github.com/systemd/systemd/pull/27752
(cherry picked from commit f4c3c107d9be4e922a080fc292ed3889c4e0f4a5)
Related: RHEL-118215
---
.../testsuite-53.RandomizedDelaySec-reload.sh | 97 +++++++++++++++++++
test/units/util.sh | 18 ++++
2 files changed, 115 insertions(+)
create mode 100755 test/units/testsuite-53.RandomizedDelaySec-reload.sh
diff --git a/test/units/testsuite-53.RandomizedDelaySec-reload.sh b/test/units/testsuite-53.RandomizedDelaySec-reload.sh
new file mode 100755
index 0000000000..08f4f469d6
--- /dev/null
+++ b/test/units/testsuite-53.RandomizedDelaySec-reload.sh
@@ -0,0 +1,97 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# When deserializing a serialized timer unit with RandomizedDelaySec= set, systemd should use the last
+# inactive exit timestamp instead of current realtime to calculate the new next elapse, so the timer unit
+# actually runs in the given calendar window.
+#
+# Provides coverage for:
+# - https://github.com/systemd/systemd/issues/18678
+# - https://github.com/systemd/systemd/pull/27752
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/test-control.sh
+. "$(dirname "$0")"/util.sh
+
+UNIT_NAME="timer-RandomizedDelaySec-$RANDOM"
+TARGET_TS="$(date --date="tomorrow 00:10")"
+TARGET_TS_S="$(date --date="$TARGET_TS" "+%s")"
+# Maximum possible next elapse timestamp: $TARGET_TS (OnCalendar=) + 22 hours (RandomizedDelaySec=)
+MAX_NEXT_ELAPSE_REALTIME_S="$((TARGET_TS_S + 22 * 60 * 60))"
+MAX_NEXT_ELAPSE_REALTIME="$(date --date="@$MAX_NEXT_ELAPSE_REALTIME_S")"
+
+# Let's make sure to return the date & time back to the original state once we're done with our time
+# shenigans. One way to do this would be to use hwclock, but the RTC in VMs can be unreliable or slow to
+# respond, causing unexpected test fails/timeouts.
+#
+# Instead, let's save the realtime timestamp before we start with the test together with a current monotonic
+# timestamp, after the test ends take the difference between the current monotonic timestamp and the "start"
+# one, add it to the originally saved realtime timestamp, and finally use that timestamp to set the system
+# time. This should advance the system time by the amount of time the test actually ran, and hence restore it
+# to some sane state after the time jumps performed by the test. It won't be perfect, but it should be close
+# enough for our needs.
+START_REALTIME="$(date "+%s")"
+START_MONOTONIC="$(cut -d . -f 1 /proc/uptime)"
+at_exit() {
+ : "Restore the system date to a sane state"
+ END_MONOTONIC="$(cut -d . -f 1 /proc/uptime)"
+ date --set="@$((START_REALTIME + END_MONOTONIC - START_MONOTONIC))"
+}
+trap at_exit EXIT
+
+# Set some predictable time so we can schedule the first timer elapse in a deterministic-ish way
+date --set="23:00"
+
+# Setup
+cat >"/run/systemd/system/$UNIT_NAME.timer" <<EOF
+[Timer]
+# Run this timer daily, ten minutes after midnight
+OnCalendar=*-*-* 00:10:00
+RandomizedDelaySec=22h
+AccuracySec=1ms
+EOF
+
+cat >"/run/systemd/system/$UNIT_NAME.service" <<EOF
+[Service]
+ExecStart=echo "Hello world"
+EOF
+
+systemctl daemon-reload
+
+check_elapse_timestamp() {
+ systemctl status "$UNIT_NAME.timer"
+ systemctl show -p InactiveExitTimestamp "$UNIT_NAME.timer"
+
+ NEXT_ELAPSE_REALTIME="$(systemctl show -P NextElapseUSecRealtime "$UNIT_NAME.timer")"
+ NEXT_ELAPSE_REALTIME_S="$(date --date="$NEXT_ELAPSE_REALTIME" "+%s")"
+ : "Next elapse timestamp should be $TARGET_TS <= $NEXT_ELAPSE_REALTIME <= $MAX_NEXT_ELAPSE_REALTIME"
+ assert_ge "$NEXT_ELAPSE_REALTIME_S" "$TARGET_TS_S"
+ assert_le "$NEXT_ELAPSE_REALTIME_S" "$MAX_NEXT_ELAPSE_REALTIME_S"
+}
+
+# Restart the timer unit and check the initial next elapse timestamp
+: "Initial next elapse timestamp"
+systemctl restart "$UNIT_NAME.timer"
+check_elapse_timestamp
+
+# Bump the system date to 1 minute after the original calendar timer would've expired (without any random
+# delay!) - systemd should recalculate the next elapse timestamp with a new randomized delay, but it should
+# use the original inactive exit timestamp as a "base", so the final timestamp should not end up beyond the
+# original calendar timestamp + randomized delay range.
+#
+# Similarly, do the same check after doing daemon-reload, as that also forces systemd to recalculate the next
+# elapse timestamp (this goes through a slightly different codepath that actually contained the original
+# issue).
+: "Next elapse timestamp after time jump"
+date -s "tomorrow 00:11"
+check_elapse_timestamp
+
+: "Next elapse timestamp after daemon-reload"
+systemctl daemon-reload
+check_elapse_timestamp
+
+# Cleanup
+systemctl stop "$UNIT_NAME".{timer,service}
+rm -f "/run/systemd/system/$UNIT_NAME".{timer,service}
+systemctl daemon-reload
diff --git a/test/units/util.sh b/test/units/util.sh
index 00b8c5e393..5728b324a9 100755
--- a/test/units/util.sh
+++ b/test/units/util.sh
@@ -26,6 +26,24 @@ assert_eq() {(
fi
)}
+assert_le() {(
+ set +ex
+
+ if [[ "${1:?}" -gt "${2:?}" ]]; then
+ echo "FAIL: '$1' > '$2'" >&2
+ exit 1
+ fi
+)}
+
+assert_ge() {(
+ set +ex
+
+ if [[ "${1:?}" -lt "${2:?}" ]]; then
+ echo "FAIL: '$1' < '$2'" >&2
+ exit 1
+ fi
+)}
+
assert_in() {(
set +ex

View File

@ -0,0 +1,29 @@
From a5e7446ae2442558f9c13d814e778f13a7018e23 Mon Sep 17 00:00:00 2001
From: Lukas Nykryn <lnykryn@redhat.com>
Date: Tue, 9 Sep 2025 15:24:22 +0200
Subject: [PATCH] timer: don't run service immediately after restart of a timer
When a timer is restarted, don't reset the last_trigger field.
This prevents the timer from triggering immediately.
Fixes: #31231
(cherry picked from commit 3fc44a0f68412b649e16f12ff2f97a36c615457d)
Resolves: RHEL-118215
---
src/core/timer.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/core/timer.c b/src/core/timer.c
index 60e8fea79f..2eadca4f1a 100644
--- a/src/core/timer.c
+++ b/src/core/timer.c
@@ -635,8 +635,6 @@ static int timer_start(Unit *u) {
if (r < 0)
return r;
- t->last_trigger = DUAL_TIMESTAMP_NULL;
-
/* Reenable all timers that depend on unit activation time */
LIST_FOREACH(value, v, t->values)
if (v->base == TIMER_ACTIVE)

View File

@ -0,0 +1,49 @@
From c603c6cb569f0900ddf07f0311ffa038a242fac8 Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Mon, 29 Sep 2025 16:11:27 +0200
Subject: [PATCH] test: store and compare just the property value
Follow-up for 5730a400fd5ee82566fe03eb832121a0d4bc26b6.
(cherry picked from commit 0cb252d50f35256bff569fa6213784f2d45ad6a1)
Related: RHEL-118215
---
test/units/testsuite-53.restart-trigger.sh | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/test/units/testsuite-53.restart-trigger.sh b/test/units/testsuite-53.restart-trigger.sh
index 057f379ddc..e5cd575d66 100755
--- a/test/units/testsuite-53.restart-trigger.sh
+++ b/test/units/testsuite-53.restart-trigger.sh
@@ -45,15 +45,15 @@ assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --
# Restarting the timer unit shouldn't trigger neither the timer nor the service, so these
# fields should remain constant through the following tests
-SERVICE_INV_ID="$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
-TIMER_LAST_TRIGGER="$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
+SERVICE_INV_ID="$(systemctl show -P InvocationID "$UNIT_NAME.service")"
+TIMER_LAST_TRIGGER="$(systemctl show -P LastTriggerUSec "$UNIT_NAME.timer")"
# Now restart the timer and check if the timer and the service weren't triggered again
systemctl restart "$UNIT_NAME.timer"
sleep 5
assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE" | wc -l)" "1"
-assert_eq "$SERVICE_INV_ID" "$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
-assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
+assert_eq "$SERVICE_INV_ID" "$(systemctl show -P InvocationID "$UNIT_NAME.service")"
+assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show -P LastTriggerUSec "$UNIT_NAME.timer")"
# Set the timer into the past, restart it, and again check if it wasn't triggered
TIMER_TS="$(date --date="-1 day" "+%Y-%m-%d %H:%M:%S")"
@@ -68,8 +68,8 @@ assert_in "OnCalendar=$TIMER_TS" "$(systemctl show -P TimersCalendar "$UNIT_NAME
systemctl restart "$UNIT_NAME.timer"
sleep 5
assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE" | wc -l)" "1"
-assert_eq "$SERVICE_INV_ID" "$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
-assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
+assert_eq "$SERVICE_INV_ID" "$(systemctl show -P InvocationID "$UNIT_NAME.service")"
+assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show -P LastTriggerUSec "$UNIT_NAME.timer")"
# Cleanup
systemctl stop "$UNIT_NAME".{timer,service}

View File

@ -0,0 +1,94 @@
From d4299294d40b7fe713d0c9df0f7a42c70654e886 Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Fri, 5 Apr 2024 12:18:58 +0200
Subject: [PATCH] test: make test-fd-util more lenient when using
fd_move_above_stdio()
On s390x this test fails when the SUT uses the z90crypt kernel module,
as it's an another FD the test doesn't account for:
/* test_rearrange_stdio */
Successfully forked off 'rearrange' as PID 57293.
test_rearrange_stdio: r=0
/proc/57293/fd:
total 0
lrwx------. 1 root root 64 Apr 5 06:18 0 -> /dev/pts/0
lrwx------. 1 root root 64 Apr 5 06:18 1 -> /dev/pts/0
lrwx------. 1 root root 64 Apr 5 06:18 2 -> /dev/pts/0
lrwx------. 1 root root 64 Apr 5 06:18 3 -> /dev/z90crypt
rearrange terminated by signal ABRT.
Debugging this was pain, since the child process didn't log anything
once we closed stdout/stderr (for obvious reasons). Let's fix both
issues by switching logging to kmsg once we close stdin/stdout/stderr,
and also by making the test work fine when there are some extra FDs in
the child's environment.
(cherry picked from commit a9805f8ca9c1561e373355fe7175579b31e1c08c)
Related: RHEL-114974
---
src/test/test-fd-util.c | 22 +++++++++++++---------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c
index 5b5a712469..5d6c5325a5 100644
--- a/src/test/test-fd-util.c
+++ b/src/test/test-fd-util.c
@@ -127,6 +127,7 @@ TEST(rearrange_stdio) {
if (r == 0) {
_cleanup_free_ char *path = NULL;
+ int pipe_read_fd, pair[2];
char buffer[10];
/* Child */
@@ -134,6 +135,10 @@ TEST(rearrange_stdio) {
safe_close(STDERR_FILENO); /* Let's close an fd < 2, to make it more interesting */
assert_se(rearrange_stdio(-1, -1, -1) >= 0);
+ /* Reconfigure logging after rearranging stdout/stderr, so we still log to somewhere if the
+ * following tests fail, making it slightly less annoying to debug */
+ log_set_target(LOG_TARGET_KMSG);
+ log_open();
assert_se(fd_get_path(STDIN_FILENO, &path) >= 0);
assert_se(path_equal(path, "/dev/null"));
@@ -151,13 +156,12 @@ TEST(rearrange_stdio) {
safe_close(STDOUT_FILENO);
safe_close(STDERR_FILENO);
- {
- int pair[2];
- assert_se(pipe(pair) >= 0);
- assert_se(pair[0] == 0);
- assert_se(pair[1] == 1);
- assert_se(fd_move_above_stdio(0) == 3);
- }
+ assert_se(pipe(pair) >= 0);
+ assert_se(pair[0] == 0);
+ assert_se(pair[1] == 1);
+ pipe_read_fd = fd_move_above_stdio(0);
+ assert_se(pipe_read_fd >= 3);
+
assert_se(open("/dev/full", O_WRONLY|O_CLOEXEC) == 0);
assert_se(acquire_data_fd("foobar", 6, 0) == 2);
@@ -165,7 +169,7 @@ TEST(rearrange_stdio) {
assert_se(write(1, "x", 1) < 0 && errno == ENOSPC);
assert_se(write(2, "z", 1) == 1);
- assert_se(read(3, buffer, sizeof(buffer)) == 1);
+ assert_se(read(pipe_read_fd, buffer, sizeof(buffer)) == 1);
assert_se(buffer[0] == 'z');
assert_se(read(0, buffer, sizeof(buffer)) == 6);
assert_se(memcmp(buffer, "foobar", 6) == 0);
@@ -173,7 +177,7 @@ TEST(rearrange_stdio) {
assert_se(rearrange_stdio(-1, 1, 2) >= 0);
assert_se(write(1, "a", 1) < 0 && errno == ENOSPC);
assert_se(write(2, "y", 1) == 1);
- assert_se(read(3, buffer, sizeof(buffer)) == 1);
+ assert_se(read(pipe_read_fd, buffer, sizeof(buffer)) == 1);
assert_se(buffer[0] == 'y');
assert_se(fd_get_path(0, &path) >= 0);

View File

@ -0,0 +1,51 @@
From 2c74cdb28bad7e8122bfd51a6d4897f792ef3e2f Mon Sep 17 00:00:00 2001
From: cpackham-atlnz <85916201+cpackham-atlnz@users.noreply.github.com>
Date: Tue, 12 Mar 2024 00:55:36 +1300
Subject: [PATCH] basic: add PIDFS magic (#31709)
Kernel commit cb12fd8e0dabb9a1c8aef55a6a41e2c255fcdf4b added pidfs.
Update filesystems-gperf.gperf and missing_magic.h accordingly.
This fixes the following error building against a bleeding edge kernel.
```
../src/basic/meson.build:234:8: ERROR: Problem encountered: Unknown filesystems defined in kernel headers:
Filesystem found in kernel header but not in filesystems-gperf.gperf: PID_FS_MAGIC
```
(cherry picked from commit ed01b92e1c92871bbd92711f280e2b2d15753f0e)
Resolves: RHEL-114974
---
src/basic/filesystems-gperf.gperf | 1 +
src/basic/missing_magic.h | 5 +++++
2 files changed, 6 insertions(+)
diff --git a/src/basic/filesystems-gperf.gperf b/src/basic/filesystems-gperf.gperf
index e8c5357f91..1cd66b5a5f 100644
--- a/src/basic/filesystems-gperf.gperf
+++ b/src/basic/filesystems-gperf.gperf
@@ -91,6 +91,7 @@ ocfs2, {OCFS2_SUPER_MAGIC}
openpromfs, {OPENPROM_SUPER_MAGIC}
orangefs, {ORANGEFS_DEVREQ_MAGIC}
overlay, {OVERLAYFS_SUPER_MAGIC}
+pidfs, {PID_FS_MAGIC}
pipefs, {PIPEFS_MAGIC}
ppc-cmm, {PPC_CMM_MAGIC}
proc, {PROC_SUPER_MAGIC}
diff --git a/src/basic/missing_magic.h b/src/basic/missing_magic.h
index c104fcfba3..82ede1873e 100644
--- a/src/basic/missing_magic.h
+++ b/src/basic/missing_magic.h
@@ -128,6 +128,11 @@
#define DEVMEM_MAGIC 0x454d444d
#endif
+/* cb12fd8e0dabb9a1c8aef55a6a41e2c255fcdf4b (6.8) */
+#ifndef PID_FS_MAGIC
+#define PID_FS_MAGIC 0x50494446
+#endif
+
/* Not in mainline but included in Ubuntu */
#ifndef SHIFTFS_MAGIC
#define SHIFTFS_MAGIC 0x6a656a62

View File

@ -0,0 +1,27 @@
From 5844efbf70321f5dd902f987947786d1ab4409c6 Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Wed, 8 Oct 2025 17:23:31 +0200
Subject: [PATCH] man: fix a missing word
Follow-up for 6d48c7cf736ced70c1c2fef1e1f03618911d04bc.
(cherry picked from commit 67111e1bd918f9e1b4b542d1e0fe84f1d571876e)
Resolves: RHEL-115182
---
man/systemd.resource-control.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
index 2a0e40a17d..9431fb20a1 100644
--- a/man/systemd.resource-control.xml
+++ b/man/systemd.resource-control.xml
@@ -365,7 +365,7 @@
an absolute number of tasks or a percentage value that is taken relative to the configured maximum
number of tasks on the system. If assigned the special value <literal>infinity</literal>, no tasks
limit is applied. This controls the <literal>pids.max</literal> control group attribute. For
- details about this control group attribute, the
+ details about this control group attribute, see the
<ulink url="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#pid">pids controller
</ulink>.</para>

View File

@ -0,0 +1,167 @@
From 26d6ea70cfb9232dc9ab66ee0927fb546fe0418b Mon Sep 17 00:00:00 2001
From: Ondrej Kozina <okozina@redhat.com>
Date: Wed, 31 Jan 2024 13:11:21 +0100
Subject: [PATCH] cryptsetup: Add optional support for linking volume key in
keyring.
cryptsetup 2.7.0 adds feature to link effective volume key in custom
kernel keyring during device activation. It can be used later to pass
linked volume key to other services.
For example: kdump enabled systems installed on LUKS2 device.
This feature allows it to store volume key linked in a kernel keyring
to the kdump reserved memory and reuse it to reactivate LUKS2 device
in case of kernel crash.
(cherry picked from commit c5daf14c88ba44cefabe052de93a29d28b6b0175)
Resolves: RHEL-97175
---
man/crypttab.xml | 21 ++++++++++++
meson.build | 3 +-
src/cryptsetup/cryptsetup.c | 65 +++++++++++++++++++++++++++++++++++++
3 files changed, 88 insertions(+), 1 deletion(-)
diff --git a/man/crypttab.xml b/man/crypttab.xml
index 1dd9bb1bb6..bd49e025fa 100644
--- a/man/crypttab.xml
+++ b/man/crypttab.xml
@@ -239,6 +239,27 @@
</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>link-volume-key=</option></term>
+
+ <listitem><para>Specifies the kernel keyring and key description
+ (see <citerefentry project='man-pages'><refentrytitle>keyrings</refentrytitle><manvolnum>7</manvolnum></citerefentry>)
+ where LUKS2 volume key gets linked during device activation. The kernel keyring
+ description and key description must be separated by <literal>::</literal>.</para>
+
+ <para>The kernel keyring part can be a string description or a predefined
+ kernel keyring prefixed with <literal>@</literal> (e.g.: to use <literal>@s</literal> session or
+ <literal>@u</literal> user keyring directly). The type prefix text in the kernel keyring description
+ is not required. The specified kernel keyring must already exist at the time of device activation.</para>
+
+ <para>The key part is a string description optionally prefixed by a <literal>%key_type:</literal>.
+ If no type is specified, the <literal>user</literal> type key is linked by default. See
+ <citerefentry project='man-pages'><refentrytitle>keyctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ for more information on key descriptions (KEY IDENTIFIERS section).</para>
+
+ <para>Note that the linked volume key is not cleaned up automatically when the device is detached.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>luks</option></term>
diff --git a/meson.build b/meson.build
index cbde702211..684324c6d7 100644
--- a/meson.build
+++ b/meson.build
@@ -1316,7 +1316,8 @@ if want_libcryptsetup != 'false' and not skip_deps
foreach ident : ['crypt_set_metadata_size',
'crypt_activate_by_signed_key',
- 'crypt_token_max']
+ 'crypt_token_max',
+ 'crypt_set_keyring_to_link']
have_ident = have and cc.has_function(
ident,
prefix : '#include <libcryptsetup.h>',
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
index 3f2cab1e41..f9130e2568 100644
--- a/src/cryptsetup/cryptsetup.c
+++ b/src/cryptsetup/cryptsetup.c
@@ -101,6 +101,9 @@ static bool arg_headless = false;
static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC;
static unsigned arg_tpm2_measure_pcr = UINT_MAX; /* This and the following field is about measuring the unlocked volume key to the local TPM */
static char **arg_tpm2_measure_banks = NULL;
+static char *arg_link_keyring = NULL;
+static char *arg_link_key_type = NULL;
+static char *arg_link_key_description = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_cipher, freep);
STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
@@ -113,6 +116,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_banks, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_link_keyring, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_link_key_type, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_link_key_description, freep);
static const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = {
[PASSPHRASE_REGULAR] = "passphrase",
@@ -486,6 +492,56 @@ static int parse_one_option(const char *option) {
if (r < 0)
log_warning_errno(r, "Failed to parse %s, ignoring: %m", option);
+ } else if ((val = startswith(option, "link-volume-key="))) {
+#ifdef HAVE_CRYPT_SET_KEYRING_TO_LINK
+ const char *sep, *c;
+ _cleanup_free_ char *keyring = NULL, *key_type = NULL, *key_description = NULL;
+
+ /* Stick with cryptsetup --link-vk-to-keyring format
+ * <keyring_description>::%<key_type>:<key_description>,
+ * where %<key_type> is optional and defaults to 'user'.
+ */
+ if (!(sep = strstr(val, "::")))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse link-volume-key= option value: %m");
+
+ /* cryptsetup (cli) supports <keyring_description> passed in various formats:
+ * - well-known keyrings prefixed with '@' (@u user, @s session, etc)
+ * - text descriptions prefixed with "%:" or "%keyring:".
+ * - text desription with no prefix.
+ * - numeric keyring id (ignored in current patch set). */
+ if (*val == '@' || *val == '%')
+ keyring = strndup(val, sep - val);
+ else
+ /* add type prefix if missing (crypt_set_keyring_to_link() expects it) */
+ keyring = strnappend("%:", val, sep - val);
+ if (!keyring)
+ return log_oom();
+
+ sep += 2;
+
+ /* %<key_type> is optional (and defaults to 'user') */
+ if (*sep == '%') {
+ /* must be separated by colon */
+ if (!(c = strchr(sep, ':')))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse link-volume-key= option value: %m");
+
+ key_type = strndup(sep + 1, c - sep - 1);
+ if (!key_type)
+ return log_oom();
+
+ sep = c + 1;
+ }
+
+ key_description = strdup(sep);
+ if (!key_description)
+ return log_oom();
+
+ free_and_replace(arg_link_keyring, keyring);
+ free_and_replace(arg_link_key_type, key_type);
+ free_and_replace(arg_link_key_description, key_description);
+#else
+ log_error("Build lacks libcryptsetup support for linking volume keys in user specified kernel keyrings upon device activation, ignoring: %s", option);
+#endif
} else if (!streq(option, "x-initrd.attach"))
log_warning("Encountered unknown /etc/crypttab option '%s', ignoring.", option);
@@ -2207,6 +2263,15 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd));
+/* since cryptsetup 2.7.0 (Jan 2024) */
+#if HAVE_CRYPT_SET_KEYRING_TO_LINK
+ if (arg_link_key_description) {
+ r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set keyring or key description to link volume key in, ignoring: %m");
+ }
+#endif
+
if (arg_header) {
r = crypt_set_data_device(cd, source);
if (r < 0)

View File

@ -0,0 +1,27 @@
From 44f65e9b9a0f67a69886d25367875e9707affc81 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Wed, 14 Feb 2024 04:01:36 +0900
Subject: [PATCH] cryptsetup: fix typo
Follow-up for c5daf14c88ba44cefabe052de93a29d28b6b0175.
(cherry picked from commit a14d3b48f7647676a0c43bceaecd56d9a77e3de6)
Resolves: RHEL-97175
---
src/cryptsetup/cryptsetup.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
index f9130e2568..1f672f19f1 100644
--- a/src/cryptsetup/cryptsetup.c
+++ b/src/cryptsetup/cryptsetup.c
@@ -507,7 +507,7 @@ static int parse_one_option(const char *option) {
/* cryptsetup (cli) supports <keyring_description> passed in various formats:
* - well-known keyrings prefixed with '@' (@u user, @s session, etc)
* - text descriptions prefixed with "%:" or "%keyring:".
- * - text desription with no prefix.
+ * - text description with no prefix.
* - numeric keyring id (ignored in current patch set). */
if (*val == '@' || *val == '%')
keyring = strndup(val, sep - val);

View File

@ -0,0 +1,27 @@
From be6acfdfd0ddd5625d68bdeb1fb5962d710557be Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Sun, 17 Aug 2025 21:05:24 +0900
Subject: [PATCH] cryptsetup: HAVE_CRYPT_SET_KEYRING_TO_LINK is always defined
Follow-up for c5daf14c88ba44cefabe052de93a29d28b6b0175 (v256).
(cherry picked from commit fb4aabf4432d523b97376099ce4353b5c268ae82)
Resolves: RHEL-97175
---
src/cryptsetup/cryptsetup.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
index 1f672f19f1..4dc315e810 100644
--- a/src/cryptsetup/cryptsetup.c
+++ b/src/cryptsetup/cryptsetup.c
@@ -493,7 +493,7 @@ static int parse_one_option(const char *option) {
log_warning_errno(r, "Failed to parse %s, ignoring: %m", option);
} else if ((val = startswith(option, "link-volume-key="))) {
-#ifdef HAVE_CRYPT_SET_KEYRING_TO_LINK
+#if HAVE_CRYPT_SET_KEYRING_TO_LINK
const char *sep, *c;
_cleanup_free_ char *keyring = NULL, *key_type = NULL, *key_description = NULL;

View File

@ -0,0 +1,34 @@
From 9109aaae160fe7dcb9390829db619e4e8f90274f Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Thu, 31 Oct 2024 17:02:59 +0100
Subject: [PATCH] coredump: make check that all argv[] meta data fields are
passed strict
Otherwise, if some field is not supplied we might end up parsing a NULL
string later. Let's catch that early.
(cherry picked from commit 098c3975acb3df61eedfe471fca27c21f13cf04c)
Related: RHEL-104138
---
src/coredump/coredump.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
index dca78fa72c..b24f4c8cc3 100644
--- a/src/coredump/coredump.c
+++ b/src/coredump/coredump.c
@@ -1067,9 +1067,10 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) {
}
}
- if (!context->meta[META_ARGV_PID])
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Failed to find the PID of crashing process");
+ /* The basic fields from argv[] should always be there, refuse early if not */
+ for (int i = 0; i < _META_ARGV_MAX; i++)
+ if (!context->meta[i])
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "A required (%s) has not been sent, aborting.", meta_field_names[i]);
r = parse_pid(context->meta[META_ARGV_PID], &context->pid);
if (r < 0)

View File

@ -0,0 +1,122 @@
From 38d7a52bcdad1cef1dba218f86e3905c24d51d9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Tue, 29 Apr 2025 14:47:59 +0200
Subject: [PATCH] coredump: restore compatibility with older patterns
This was broken in f45b8015513d38ee5f7cc361db9c5b88c9aae704. Unfortunately
the review does not talk about backward compatibility at all. There are
two places where it matters:
- During upgrades, the replacement of kernel.core_pattern is asynchronous.
For example, during rpm upgrades, it would be updated a post-transaction
file trigger. In other scenarios, the update might only happen after
reboot. We have a potentially long window where the old pattern is in
place. We need to capture coredumps during upgrades too.
- With --backtrace. The interface of --backtrace, in hindsight, is not
great. But there are users of --backtrace which were written to use
a specific set of arguments, and we can't just break compatiblity.
One example is systemd-coredump-python, but there are also reports of
users using --backtrace to generate coredump logs.
Thus, we require the original set of args, and will use the additional args if
found.
A test is added to verify that --backtrace works with and without the optional
args.
(cherry picked from commit ded0aac389e647d35bce7ec4a48e718d77c0435b)
Related: RHEL-104138
---
src/coredump/coredump.c | 23 +++++++++++++++--------
test/units/testsuite-74.coredump.sh | 18 +++++++++++-------
2 files changed, 26 insertions(+), 15 deletions(-)
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
index b24f4c8cc3..458857ffb2 100644
--- a/src/coredump/coredump.c
+++ b/src/coredump/coredump.c
@@ -94,8 +94,12 @@ enum {
META_ARGV_SIGNAL, /* %s: number of signal causing dump */
META_ARGV_TIMESTAMP, /* %t: time of dump, expressed as seconds since the Epoch (we expand this to µs granularity) */
META_ARGV_RLIMIT, /* %c: core file size soft resource limit */
- META_ARGV_HOSTNAME, /* %h: hostname */
+ _META_ARGV_REQUIRED,
+ /* The fields below were added to kernel/core_pattern at later points, so they might be missing. */
+ META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */
_META_ARGV_MAX,
+ /* If new fields are added, they should be added here, to maintain compatibility
+ * with callers which don't know about the new fields. */
/* The following indexes are cached for a couple of special fields we use (and
* thereby need to be retrieved quickly) for naming coredump files, and attaching
@@ -106,7 +110,7 @@ enum {
_META_MANDATORY_MAX,
/* The rest are similar to the previous ones except that we won't fail if one of
- * them is missing. */
+ * them is missing in a message sent over the socket. */
META_EXE = _META_MANDATORY_MAX,
META_UNIT,
@@ -1068,7 +1072,7 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) {
}
/* The basic fields from argv[] should always be there, refuse early if not */
- for (int i = 0; i < _META_ARGV_MAX; i++)
+ for (int i = 0; i < _META_ARGV_REQUIRED; i++)
if (!context->meta[i])
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "A required (%s) has not been sent, aborting.", meta_field_names[i]);
@@ -1286,14 +1290,17 @@ static int gather_pid_metadata_from_argv(
char *t;
/* We gather all metadata that were passed via argv[] into an array of iovecs that
- * we'll forward to the socket unit */
+ * we'll forward to the socket unit.
+ *
+ * We require at least _META_ARGV_REQUIRED args, but will accept more.
+ * We know how to parse _META_ARGV_MAX args. The rest will be ignored. */
- if (argc < _META_ARGV_MAX)
+ if (argc < _META_ARGV_REQUIRED)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Not enough arguments passed by the kernel (%i, expected %i).",
- argc, _META_ARGV_MAX);
+ "Not enough arguments passed by the kernel (%i, expected between %i and %i).",
+ argc, _META_ARGV_REQUIRED, _META_ARGV_MAX);
- for (int i = 0; i < _META_ARGV_MAX; i++) {
+ for (int i = 0; i < MIN(argc, _META_ARGV_MAX); i++) {
t = argv[i];
diff --git a/test/units/testsuite-74.coredump.sh b/test/units/testsuite-74.coredump.sh
index 1093cad8a9..0163131096 100755
--- a/test/units/testsuite-74.coredump.sh
+++ b/test/units/testsuite-74.coredump.sh
@@ -218,14 +218,18 @@ rm -f /tmp/core.{output,redirected}
(! "${UNPRIV_CMD[@]}" coredumpctl dump "$CORE_TEST_BIN" >/dev/null)
# --backtrace mode
-# Pass one of the existing journal coredump records to systemd-coredump and
-# use our PID as the source to make matching the coredump later easier
-# systemd-coredump args: PID UID GID SIGNUM TIMESTAMP CORE_SOFT_RLIMIT HOSTNAME
+# Pass one of the existing journal coredump records to systemd-coredump.
+# Use our PID as the source to be able to create a PIDFD and to make matching easier.
+# systemd-coredump args: PID UID GID SIGNUM TIMESTAMP CORE_SOFT_RLIMIT [HOSTNAME]
journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE="/usr/bin/test-dump" |
- /usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509994 12345 mymachine
-# Wait a bit for the coredump to get processed
-timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $$ | wc -l) -eq 0 ]]; do sleep 1; done"
-coredumpctl info "$$"
+ /usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509900 12345
+journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE="/usr/bin/test-dump" |
+ /usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509901 12345 mymachine
+# Wait a bit for the coredumps to get processed
+timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $$ | wc -l) -lt 2 ]]; do sleep 1; done"
+coredumpctl info $$
+coredumpctl info COREDUMP_TIMESTAMP=1679509900000000
+coredumpctl info COREDUMP_TIMESTAMP=1679509901000000
coredumpctl info COREDUMP_HOSTNAME="mymachine"
# This used to cause a stack overflow

View File

@ -0,0 +1,158 @@
From fbc5015c95298c71c806b5e80207e52688aad69a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Tue, 29 Apr 2025 14:47:59 +0200
Subject: [PATCH] coredump: use %d in kernel core pattern
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The kernel provides %d which is documented as
"dump mode—same as value returned by prctl(2) PR_GET_DUMPABLE".
We already query /proc/pid/auxv for this information, but unfortunately this
check is subject to a race, because the crashed process may be replaced by an
attacker before we read this data, for example replacing a SUID process that
was killed by a signal with another process that is not SUID, tricking us into
making the coredump of the original process readable by the attacker.
With this patch, we effectively add one more check to the list of conditions
that need be satisfied if we are to make the coredump accessible to the user.
Reportedy-by: Qualys Security Advisory <qsa@qualys.com>
In principle, %d might return a value other than 0, 1, or 2 in the future.
Thus, we accept those, but emit a notice.
(cherry picked from commit 0c49e0049b7665bb7769a13ef346fef92e1ad4d6)
Related: RHEL-104138
---
man/systemd-coredump.xml | 10 ++++++++++
src/coredump/coredump.c | 22 +++++++++++++++++++---
sysctl.d/50-coredump.conf.in | 2 +-
test/units/testsuite-74.coredump.sh | 5 +++++
4 files changed, 35 insertions(+), 4 deletions(-)
diff --git a/man/systemd-coredump.xml b/man/systemd-coredump.xml
index cb9f47745b..6cfa04f466 100644
--- a/man/systemd-coredump.xml
+++ b/man/systemd-coredump.xml
@@ -259,6 +259,16 @@ COREDUMP_FILENAME=/var/lib/systemd/coredump/core.Web….552351.….zst
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>COREDUMP_DUMPABLE=</varname></term>
+
+ <listitem><para>The <constant>PR_GET_DUMPABLE</constant> field as reported by the kernel, see
+ <citerefentry
+ project='man-pages'><refentrytitle>prctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>COREDUMP_OPEN_FDS=</varname></term>
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
index 458857ffb2..cd10678c43 100644
--- a/src/coredump/coredump.c
+++ b/src/coredump/coredump.c
@@ -97,7 +97,9 @@ enum {
_META_ARGV_REQUIRED,
/* The fields below were added to kernel/core_pattern at later points, so they might be missing. */
META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */
+ META_ARGV_DUMPABLE, /* %d: as set by the kernel */
_META_ARGV_MAX,
+
/* If new fields are added, they should be added here, to maintain compatibility
* with callers which don't know about the new fields. */
@@ -126,6 +128,7 @@ static const char * const meta_field_names[_META_MAX] = {
[META_ARGV_TIMESTAMP] = "COREDUMP_TIMESTAMP=",
[META_ARGV_RLIMIT] = "COREDUMP_RLIMIT=",
[META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=",
+ [META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=",
[META_COMM] = "COREDUMP_COMM=",
[META_EXE] = "COREDUMP_EXE=",
[META_UNIT] = "COREDUMP_UNIT=",
@@ -138,6 +141,7 @@ typedef struct Context {
pid_t pid;
uid_t uid;
gid_t gid;
+ unsigned dumpable;
bool is_pid1;
bool is_journald;
} Context;
@@ -453,14 +457,16 @@ static int grant_user_access(int core_fd, const Context *context) {
if (r < 0)
return r;
- /* We allow access if we got all the data and at_secure is not set and
- * the uid/gid matches euid/egid. */
+ /* We allow access if %d/dumpable on the command line was exactly 1, we got all the data,
+ * at_secure is not set, and the uid/gid match euid/egid. */
bool ret =
+ context->dumpable == 1 &&
at_secure == 0 &&
uid != UID_INVALID && euid != UID_INVALID && uid == euid &&
gid != GID_INVALID && egid != GID_INVALID && gid == egid;
- log_debug("Will %s access (uid="UID_FMT " euid="UID_FMT " gid="GID_FMT " egid="GID_FMT " at_secure=%s)",
+ log_debug("Will %s access (dumpable=%u uid="UID_FMT " euid="UID_FMT " gid="GID_FMT " egid="GID_FMT " at_secure=%s)",
ret ? "permit" : "restrict",
+ context->dumpable,
uid, euid, gid, egid, yes_no(at_secure));
return ret;
}
@@ -1089,6 +1095,16 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) {
return log_error_errno(r, "Failed to parse GID \"%s\": %m", context->meta[META_ARGV_GID]);
+ /* The value is set to contents of /proc/sys/fs/suid_dumpable, which we set to 2,
+ * if the process is marked as not dumpable, see PR_SET_DUMPABLE(2const). */
+ if (context->meta[META_ARGV_DUMPABLE]) {
+ r = safe_atou(context->meta[META_ARGV_DUMPABLE], &context->dumpable);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse dumpable field \"%s\": %m", context->meta[META_ARGV_DUMPABLE]);
+ if (context->dumpable > 2)
+ log_notice("Got unexpected %%d/dumpable value %u.", context->dumpable);
+ }
+
unit = context->meta[META_UNIT];
context->is_pid1 = streq(context->meta[META_ARGV_PID], "1") || streq_ptr(unit, SPECIAL_INIT_SCOPE);
context->is_journald = streq_ptr(unit, SPECIAL_JOURNALD_SERVICE);
diff --git a/sysctl.d/50-coredump.conf.in b/sysctl.d/50-coredump.conf.in
index 5fb551a8cf..9c10a89828 100644
--- a/sysctl.d/50-coredump.conf.in
+++ b/sysctl.d/50-coredump.conf.in
@@ -13,7 +13,7 @@
# the core dump.
#
# See systemd-coredump(8) and core(5).
-kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h
+kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d
# Allow 16 coredumps to be dispatched in parallel by the kernel.
# We collect metadata from /proc/%P/, and thus need to make sure the crashed
diff --git a/test/units/testsuite-74.coredump.sh b/test/units/testsuite-74.coredump.sh
index 0163131096..b72313672c 100755
--- a/test/units/testsuite-74.coredump.sh
+++ b/test/units/testsuite-74.coredump.sh
@@ -225,12 +225,17 @@ journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE
/usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509900 12345
journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE="/usr/bin/test-dump" |
/usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509901 12345 mymachine
+journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE="/usr/bin/test-dump" |
+ /usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509902 12345 youmachine 1
# Wait a bit for the coredumps to get processed
timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $$ | wc -l) -lt 2 ]]; do sleep 1; done"
coredumpctl info $$
coredumpctl info COREDUMP_TIMESTAMP=1679509900000000
coredumpctl info COREDUMP_TIMESTAMP=1679509901000000
coredumpctl info COREDUMP_HOSTNAME="mymachine"
+coredumpctl info COREDUMP_TIMESTAMP=1679509902000000
+coredumpctl info COREDUMP_HOSTNAME="youmachine"
+coredumpctl info COREDUMP_DUMPABLE="1"
# This used to cause a stack overflow
systemd-run -t --property CoredumpFilter=all ls /tmp

View File

@ -0,0 +1,230 @@
From e638eb667af0e8ac9d3d409edbbf51507a4eef0e Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Sat, 9 Sep 2023 09:29:27 +0200
Subject: [PATCH] pidref: add structure that can reference a pid via both pidfd
and pid_t
Let's start with the conversion of PID 1 to pidfds. Let's add a simple
structure with just two fields that can be used to maintain a reference
to arbitrary processes via both pid_t and pidfd.
This is an embeddable struct, to keep it in line with where we
previously used a pid_t directly to track a process.
Of course, since this might contain an fd on systems where we have pidfd
this structure has a proper lifecycle.
(Note that this is quite different from sd_event_add_child() event
source objects as that one is only for child processes and collects
process results, while this infra is much simpler and more generic and
can be used to reference any process, anywhere in the tree.)
(cherry picked from commit 3bda3f17fa84557eeb28fa7c330cbd3a3f876d47)
Related: RHEL-104138
---
src/basic/meson.build | 1 +
src/basic/pidref.c | 145 ++++++++++++++++++++++++++++++++++++++++++
src/basic/pidref.h | 29 +++++++++
3 files changed, 175 insertions(+)
create mode 100644 src/basic/pidref.c
create mode 100644 src/basic/pidref.h
diff --git a/src/basic/meson.build b/src/basic/meson.build
index 11053a5ecd..b8b4213c70 100644
--- a/src/basic/meson.build
+++ b/src/basic/meson.build
@@ -182,6 +182,7 @@ basic_sources = files(
'path-util.h',
'percent-util.c',
'percent-util.h',
+ 'pidref.c',
'prioq.c',
'prioq.h',
'proc-cmdline.c',
diff --git a/src/basic/pidref.c b/src/basic/pidref.c
new file mode 100644
index 0000000000..f41460938c
--- /dev/null
+++ b/src/basic/pidref.c
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "errno-util.h"
+#include "fd-util.h"
+#include "missing_syscall.h"
+#include "parse-util.h"
+#include "pidref.h"
+#include "process-util.h"
+
+int pidref_set_pid(PidRef *pidref, pid_t pid) {
+ int fd;
+
+ assert(pidref);
+
+ if (pid < 0)
+ return -ESRCH;
+ if (pid == 0)
+ pid = getpid_cached();
+
+ fd = pidfd_open(pid, 0);
+ if (fd < 0) {
+ /* Graceful fallback in case the kernel doesn't support pidfds or is out of fds */
+ if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno) && !ERRNO_IS_RESOURCE(errno))
+ return -errno;
+
+ fd = -EBADF;
+ }
+
+ *pidref = (PidRef) {
+ .fd = fd,
+ .pid = pid,
+ };
+
+ return 0;
+}
+
+int pidref_set_pidstr(PidRef *pidref, const char *pid) {
+ pid_t nr;
+ int r;
+
+ assert(pidref);
+
+ r = parse_pid(pid, &nr);
+ if (r < 0)
+ return r;
+
+ return pidref_set_pid(pidref, nr);
+}
+
+int pidref_set_pidfd(PidRef *pidref, int fd) {
+ int r;
+
+ assert(pidref);
+
+ if (fd < 0)
+ return -EBADF;
+
+ int fd_copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (fd_copy < 0) {
+ pid_t pid;
+
+ if (!ERRNO_IS_RESOURCE(errno))
+ return -errno;
+
+ /* Graceful fallback if we are out of fds */
+ r = pidfd_get_pid(fd, &pid);
+ if (r < 0)
+ return r;
+
+ *pidref = (PidRef) {
+ .fd = -EBADF,
+ .pid = pid,
+ };
+
+ return 0;
+ }
+
+ return pidref_set_pidfd_consume(pidref, fd_copy);
+}
+
+int pidref_set_pidfd_take(PidRef *pidref, int fd) {
+ pid_t pid;
+ int r;
+
+ assert(pidref);
+
+ if (fd < 0)
+ return -EBADF;
+
+ r = pidfd_get_pid(fd, &pid);
+ if (r < 0)
+ return r;
+
+ *pidref = (PidRef) {
+ .fd = fd,
+ .pid = pid,
+ };
+
+ return 0;
+}
+
+int pidref_set_pidfd_consume(PidRef *pidref, int fd) {
+ int r;
+
+ r = pidref_set_pidfd_take(pidref, fd);
+ if (r < 0)
+ safe_close(fd);
+
+ return r;
+}
+
+void pidref_done(PidRef *pidref) {
+ assert(pidref);
+
+ *pidref = (PidRef) {
+ .fd = safe_close(pidref->fd),
+ };
+}
+
+int pidref_kill(PidRef *pidref, int sig) {
+
+ if (!pidref)
+ return -ESRCH;
+
+ if (pidref->fd >= 0)
+ return RET_NERRNO(pidfd_send_signal(pidref->fd, sig, NULL, 0));
+
+ if (pidref->pid > 0)
+ return RET_NERRNO(kill(pidref->pid, sig));
+
+ return -ESRCH;
+}
+
+int pidref_kill_and_sigcont(PidRef *pidref, int sig) {
+ int r;
+
+ r = pidref_kill(pidref, sig);
+ if (r < 0)
+ return r;
+
+ if (!IN_SET(sig, SIGCONT, SIGKILL))
+ (void) pidref_kill(pidref, SIGCONT);
+
+ return 0;
+}
diff --git a/src/basic/pidref.h b/src/basic/pidref.h
new file mode 100644
index 0000000000..2411e510f1
--- /dev/null
+++ b/src/basic/pidref.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "macro.h"
+
+/* An embeddable structure carrying a reference to a process. Supposed to be used when tracking processes continously. */
+typedef struct PidRef {
+ pid_t pid; /* always valid */
+ int fd; /* only valid if pidfd are available in the kernel, and we manage to get an fd */
+} PidRef;
+
+#define PIDREF_NULL (PidRef) { .fd = -EBADF }
+
+static inline bool pidref_is_set(const PidRef *pidref) {
+ return pidref && pidref->pid > 0;
+}
+
+int pidref_set_pid(PidRef *pidref, pid_t pid);
+int pidref_set_pidstr(PidRef *pidref, const char *pid);
+int pidref_set_pidfd(PidRef *pidref, int fd);
+int pidref_set_pidfd_take(PidRef *pidref, int fd); /* takes ownership of the passed pidfd on success*/
+int pidref_set_pidfd_consume(PidRef *pidref, int fd); /* takes ownership of the passed pidfd in both success and failure */
+
+void pidref_done(PidRef *pidref);
+
+int pidref_kill(PidRef *pidref, int sig);
+int pidref_kill_and_sigcont(PidRef *pidref, int sig);
+
+#define TAKE_PIDREF(p) TAKE_GENERIC((p), PidRef, PIDREF_NULL)

View File

@ -0,0 +1,54 @@
From 8219e46540ddf0d6a7d3f97481debf297723a58f Mon Sep 17 00:00:00 2001
From: David Tardon <dtardon@redhat.com>
Date: Fri, 5 May 2023 08:09:14 +0200
Subject: [PATCH] fd-util: introduce parse_fd()
It's a simple wrapper for safe_atoi() that returns error if the parsed
fd is < 0 .
(cherry picked from commit b8f83d7f0c35dca6ca3a23c42215d566e2815ca5)
Related: RHEL-104138
---
src/basic/parse-util.c | 15 +++++++++++++++
src/basic/parse-util.h | 1 +
2 files changed, 16 insertions(+)
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
index 3b3efb0ab8..4161211c49 100644
--- a/src/basic/parse-util.c
+++ b/src/basic/parse-util.c
@@ -313,6 +313,21 @@ int parse_errno(const char *t) {
return e;
}
+int parse_fd(const char *t) {
+ int r, fd;
+
+ assert(t);
+
+ r = safe_atoi(t, &fd);
+ if (r < 0)
+ return r;
+
+ if (fd < 0)
+ return -ERANGE;
+
+ return fd;
+}
+
static const char *mangle_base(const char *s, unsigned *base) {
const char *k;
diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h
index 8d8d52327b..5c012d702a 100644
--- a/src/basic/parse-util.h
+++ b/src/basic/parse-util.h
@@ -20,6 +20,7 @@ int parse_mtu(int family, const char *s, uint32_t *ret);
int parse_size(const char *t, uint64_t base, uint64_t *size);
int parse_range(const char *t, unsigned *lower, unsigned *upper);
int parse_errno(const char *t);
+int parse_fd(const char *t);
#define SAFE_ATO_REFUSE_PLUS_MINUS (1U << 30)
#define SAFE_ATO_REFUSE_LEADING_ZERO (1U << 29)

View File

@ -0,0 +1,245 @@
From 27faf1af778849841d7c3140bd3d92aceaea2ee3 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <luca.boccassi@gmail.com>
Date: Sun, 13 Apr 2025 22:10:36 +0100
Subject: [PATCH] coredump: add support for new %F PIDFD specifier
A new core_pattern specifier was added, %F, to provide a PIDFD
to the usermode helper process referring to the crashed process.
This removes all possible race conditions, ensuring only the
crashed process gets inspected by systemd-coredump.
(cherry picked from commit 868d95577ec9f862580ad365726515459be582fc)
Resolves: RHEL-104138
---
man/systemd-coredump.xml | 9 ++++
src/coredump/coredump.c | 89 ++++++++++++++++++++++++++++++++----
sysctl.d/50-coredump.conf.in | 2 +-
3 files changed, 89 insertions(+), 11 deletions(-)
diff --git a/man/systemd-coredump.xml b/man/systemd-coredump.xml
index 6cfa04f466..b3d81d838a 100644
--- a/man/systemd-coredump.xml
+++ b/man/systemd-coredump.xml
@@ -186,6 +186,15 @@ COREDUMP_FILENAME=/var/lib/systemd/coredump/core.Web….552351.….zst
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>COREDUMP_BY_PIDFD=</varname></term>
+ <listitem><para>If the crashed process was analyzed using a PIDFD provided by the kernel (requires
+ kernel v6.16) then this field will be present and set to <literal>1</literal>. If this field is
+ not set, then the crashed process was analyzed via a PID, which is known to be subject to race
+ conditions.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>COREDUMP_TIMESTAMP=</varname></term>
<listitem><para>The time of the crash as reported by the kernel (in µs since the epoch).</para>
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
index cd10678c43..e0aac3c8d0 100644
--- a/src/coredump/coredump.c
+++ b/src/coredump/coredump.c
@@ -39,6 +39,7 @@
#include "mkdir-label.h"
#include "namespace-util.h"
#include "parse-util.h"
+#include "pidref.h"
#include "process-util.h"
#include "signal-util.h"
#include "socket-util.h"
@@ -98,8 +99,8 @@ enum {
/* The fields below were added to kernel/core_pattern at later points, so they might be missing. */
META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */
META_ARGV_DUMPABLE, /* %d: as set by the kernel */
+ META_ARGV_PIDFD, /* %F: pidfd of the process, since v6.16 */
_META_ARGV_MAX,
-
/* If new fields are added, they should be added here, to maintain compatibility
* with callers which don't know about the new fields. */
@@ -129,6 +130,7 @@ static const char * const meta_field_names[_META_MAX] = {
[META_ARGV_RLIMIT] = "COREDUMP_RLIMIT=",
[META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=",
[META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=",
+ [META_ARGV_PIDFD] = "COREDUMP_BY_PIDFD=",
[META_COMM] = "COREDUMP_COMM=",
[META_EXE] = "COREDUMP_EXE=",
[META_UNIT] = "COREDUMP_UNIT=",
@@ -136,6 +138,7 @@ static const char * const meta_field_names[_META_MAX] = {
};
typedef struct Context {
+ PidRef pidref;
const char *meta[_META_MAX];
size_t meta_size[_META_MAX];
pid_t pid;
@@ -146,6 +149,14 @@ typedef struct Context {
bool is_journald;
} Context;
+#define CONTEXT_NULL \
+ (Context) { \
+ .pidref = PIDREF_NULL, \
+ .uid = UID_INVALID, \
+ .gid = GID_INVALID, \
+ }
+
+
typedef enum CoredumpStorage {
COREDUMP_STORAGE_NONE,
COREDUMP_STORAGE_EXTERNAL,
@@ -171,6 +182,12 @@ static uint64_t arg_journal_size_max = JOURNAL_SIZE_MAX;
static uint64_t arg_keep_free = UINT64_MAX;
static uint64_t arg_max_use = UINT64_MAX;
+static void context_done(Context *c) {
+ assert(c);
+
+ pidref_done(&c->pidref);
+}
+
static int parse_config(void) {
static const ConfigTableItem items[] = {
{ "Coredump", "Storage", config_parse_coredump_storage, 0, &arg_storage },
@@ -1114,7 +1131,7 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) {
static int process_socket(int fd) {
_cleanup_close_ int input_fd = -EBADF, mntns_fd = -EBADF;
- Context context = {};
+ _cleanup_(context_done) Context context = CONTEXT_NULL;
struct iovec_wrapper iovw = {};
struct iovec iovec;
int iterations = 0, r;
@@ -1215,7 +1232,7 @@ static int process_socket(int fd) {
goto finish;
/* Make sure we received at least all fields we need. */
- for (int i = 0; i < _META_MANDATORY_MAX; i++)
+ for (int i = 0; i < _META_ARGV_REQUIRED; i++)
if (!context.meta[i]) {
r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"A mandatory argument (%i) has not been sent, aborting.",
@@ -1301,9 +1318,9 @@ static int gather_pid_metadata_from_argv(
Context *context,
int argc, char **argv) {
+ _cleanup_(pidref_done) PidRef local_pidref = PIDREF_NULL;
_cleanup_free_ char *free_timestamp = NULL;
- int r, signo;
- char *t;
+ int r, signo, kernel_fd = -EBADF;
/* We gather all metadata that were passed via argv[] into an array of iovecs that
* we'll forward to the socket unit.
@@ -1317,8 +1334,7 @@ static int gather_pid_metadata_from_argv(
argc, _META_ARGV_REQUIRED, _META_ARGV_MAX);
for (int i = 0; i < MIN(argc, _META_ARGV_MAX); i++) {
-
- t = argv[i];
+ const char *t = argv[i];
switch (i) {
@@ -1343,6 +1359,47 @@ static int gather_pid_metadata_from_argv(
break;
}
+ if (i == META_ARGV_PID) {
+ /* Store this so that we can check whether the core will be forwarded to a container
+ * even when the kernel doesn't provide a pidfd. Can be dropped once baseline is
+ * >= v6.16. */
+ r = pidref_set_pidstr(&local_pidref, t);
+ if (r < 0)
+ return log_error_errno(r, "Failed to initialize pidref from pid %s: %m", t);
+ }
+
+ if (i == META_ARGV_PIDFD) {
+ /* If the current kernel doesn't support the %F specifier (which resolves to a
+ * pidfd), but we included it in the core_pattern expression, we'll receive an empty
+ * string here. Deal with that gracefully. */
+ if (isempty(t))
+ continue;
+
+ assert(!pidref_is_set(&context->pidref));
+ assert(kernel_fd < 0);
+
+ kernel_fd = parse_fd(t);
+ if (kernel_fd < 0)
+ return log_error_errno(kernel_fd, "Failed to parse pidfd \"%s\": %m", t);
+
+ r = pidref_set_pidfd(&context->pidref, kernel_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to initialize pidref from pidfd %d: %m", kernel_fd);
+
+ /* If there are containers involved with different versions of the code they might
+ * not be using pidfds, so it would be wrong to set the metadata, skip it. */
+ r = in_same_namespace(getpid_cached(), context->pidref.pid, NAMESPACE_PID);
+ if (r < 0)
+ log_debug_errno(r, "Failed to check pidns of crashing process, ignoring: %m");
+ if (r <= 0)
+ continue;
+
+ /* We don't print the fd number in the journal as it's meaningless, but we still
+ * record that the parsing was done with a kernel-provided fd as it means it's safe
+ * from races, which is valuable information to provide in the journal record. */
+ t = "1";
+ }
+
r = iovw_put_string_field(iovw, meta_field_names[i], t);
if (r < 0)
return r;
@@ -1350,7 +1407,19 @@ static int gather_pid_metadata_from_argv(
/* Cache some of the process metadata we collected so far and that we'll need to
* access soon */
- return save_context(context, iovw);
+ r = save_context(context, iovw);
+ if (r < 0)
+ return r;
+
+ /* If the kernel didn't give us a PIDFD, then use the one derived from the
+ * PID immediately, given we have it. */
+ if (!pidref_is_set(&context->pidref))
+ context->pidref = TAKE_PIDREF(local_pidref);
+
+ /* Close the kernel-provided FD as the last thing after everything else succeeded. */
+ kernel_fd = safe_close(kernel_fd);
+
+ return 0;
}
static int gather_pid_metadata(struct iovec_wrapper *iovw, Context *context) {
@@ -1466,7 +1535,7 @@ static int gather_pid_metadata(struct iovec_wrapper *iovw, Context *context) {
}
static int process_kernel(int argc, char* argv[]) {
- Context context = {};
+ _cleanup_(context_done) Context context = CONTEXT_NULL;
struct iovec_wrapper *iovw;
int r, mntns_fd = -EBADF;
@@ -1543,7 +1612,7 @@ static int process_kernel(int argc, char* argv[]) {
}
static int process_backtrace(int argc, char *argv[]) {
- Context context = {};
+ _cleanup_(context_done) Context context = CONTEXT_NULL;
struct iovec_wrapper *iovw;
char *message;
int r;
diff --git a/sysctl.d/50-coredump.conf.in b/sysctl.d/50-coredump.conf.in
index 9c10a89828..1c6230ad93 100644
--- a/sysctl.d/50-coredump.conf.in
+++ b/sysctl.d/50-coredump.conf.in
@@ -13,7 +13,7 @@
# the core dump.
#
# See systemd-coredump(8) and core(5).
-kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d
+kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d %F
# Allow 16 coredumps to be dispatched in parallel by the kernel.
# We collect metadata from /proc/%P/, and thus need to make sure the crashed

View File

@ -0,0 +1,131 @@
From 69c124810e3b4bc4b7aa441cfed65d3d7594d443 Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Mon, 13 Oct 2025 17:36:55 +0200
Subject: [PATCH] timer: rebase the next elapse timestamp only if timer didn't
already run
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The test added in f4c3c107d9be4e922a080fc292ed3889c4e0f4a5 uncovered a
corner case while recalculating the next elapse timestamp of a timer unit
that uses RandomizedDelaySec= during deserialization.
If the scheduled time (without RandomizedDelaySec=) already elapsed,
systemd "rebases" the next elapse timestamp to the time when systemd
first started, to make the RandomizedDelaySec= feature work even at
boot. However, since it was done unconditionally, it always overrode the
next elapse timestamp, which could then cause the final next elapse
timestamp to fall out of the expected window.
With a couple of additional debug logs one of the test fail looks like
this:
[ 132.129815] TEST-53-TIMER.sh[384]: + : 'Next elapse timestamp after daemon-reload, try #328'
[ 132.129815] TEST-53-TIMER.sh[384]: + systemctl daemon-reload
[ 132.136352] systemd[1]: Reload requested from client PID 16399 ('systemctl') (unit TEST-53-TIMER.service)...
[ 132.136636] systemd[1]: Reloading...
[ 132.446160] systemd[1]: Rebasing next elapse timestamp
[ 132.446168] systemd[1]: v->next_elapse: Tue 2025-10-14 00:10:00 CEST
[ 132.446170] systemd[1]: rebased: Tue 2025-10-14 00:10:56 CEST
[ 132.446172] systemd[1]: v->next_elapse after rebase: Tue 2025-10-14 00:10:56 CEST
[ 132.447361] systemd[1]: Reloading finished in 310 ms.
[ 132.484041] TEST-53-TIMER.sh[384]: + check_elapse_timestamp
[ 132.484041] TEST-53-TIMER.sh[384]: + systemctl status timer-RandomizedDelaySec-16377.timer
[ 132.533657] TEST-53-TIMER.sh[16440]: ● timer-RandomizedDelaySec-16377.timer
[ 132.533657] TEST-53-TIMER.sh[16440]: Loaded: loaded (/run/systemd/system/timer-RandomizedDelaySec-16377.timer; static)
[ 132.533657] TEST-53-TIMER.sh[16440]: Active: active (waiting) since Mon 2025-10-13 23:00:00 CEST; 1h 13min ago
[ 132.533657] TEST-53-TIMER.sh[16440]: Invocation: 5555d4f060114a5493ff228013830d17
[ 132.533657] TEST-53-TIMER.sh[16440]: Trigger: Tue 2025-10-14 22:10:04 CEST; 21h left
[ 132.533657] TEST-53-TIMER.sh[16440]: Triggers: ● timer-RandomizedDelaySec-16377.service
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:07 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Changed dead -> waiting
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:07 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Adding 15h 35min 1.230173s random time.
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:07 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Realtime timer elapses at Tue 2025-10-14 15:45:58 CEST.
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:07 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Changed dead -> waiting
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Adding 16h 29min 44.084409s random time.
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Realtime timer elapses at Tue 2025-10-14 16:40:41 CEST.
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Changed dead -> waiting
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Adding 21h 59min 7.955828s random time.
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Realtime timer elapses at Tue 2025-10-14 22:10:04 CEST.
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Changed dead -> waiting
[ 132.535386] TEST-53-TIMER.sh[384]: + systemctl show -p InactiveExitTimestamp timer-RandomizedDelaySec-16377.timer
[ 132.537727] TEST-53-TIMER.sh[16442]: InactiveExitTimestamp=Mon 2025-10-13 23:00:00 CEST
[ 132.540317] TEST-53-TIMER.sh[16444]: ++ systemctl show -P NextElapseUSecRealtime timer-RandomizedDelaySec-16377.timer
[ 132.547745] TEST-53-TIMER.sh[384]: + NEXT_ELAPSE_REALTIME='Tue 2025-10-14 22:10:04 CEST'
[ 132.548020] TEST-53-TIMER.sh[16445]: ++ date '--date=Tue 2025-10-14 22:10:04 CEST' +%s
[ 132.550218] TEST-53-TIMER.sh[384]: + NEXT_ELAPSE_REALTIME_S=1760472604
[ 132.550218] TEST-53-TIMER.sh[384]: + : 'Next elapse timestamp should be Tue 2025-10-14 00:10:00 CEST <= Tue 2025-10-14 22:10:04 CEST <= Tue 2025-10-14 22:10:00 CEST'
[ 132.550218] TEST-53-TIMER.sh[384]: + assert_ge 1760472604 1760393400
[ 132.550555] TEST-53-TIMER.sh[16446]: + set +ex
[ 132.550702] TEST-53-TIMER.sh[384]: + assert_le 1760472604 1760472600
[ 132.550832] TEST-53-TIMER.sh[16447]: + set +ex
[ 132.551091] TEST-53-TIMER.sh[16447]: FAIL: '1760472604' > '1760472600'
Here the original next elapse timestamp was Tue 2025-10-14 00:10:00 CEST
as expected, but it was overridden by the rebased timestamp:
Tue 2025-10-14 00:10:56 CEST. And when a new randomized delay was added
to it (21h 59min 7.955828s) the final next elapse timestamp fell out of
the expected window, i.e. Tue 2025-10-14 00:10:00 (scheduled time) <
Tue 2025-10-14 22:10:04 CEST (rebased elapse timestamp + randomized
delay) < Tue 2025-10-14 22:10:00 CEST (scheduled time + maximum from
RandomizedDelaySec=, i.e. 22h).
By limiting the timestamp rebase only the case where the unit hasn't
already run should prevent this from happening during daemon-reload.
(cherry picked from commit bdb8e584f4509de0daebbe2357d23156160c3a90)
Related: RHEL-118215
---
src/core/timer.c | 25 +++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/src/core/timer.c b/src/core/timer.c
index 2eadca4f1a..4b0266bc68 100644
--- a/src/core/timer.c
+++ b/src/core/timer.c
@@ -392,7 +392,8 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
continue;
if (v->base == TIMER_CALENDAR) {
- usec_t b, rebased;
+ bool rebase_after_boot_time = false;
+ usec_t b;
/* If we know the last time this was
* triggered, schedule the job based relative
@@ -403,21 +404,25 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
b = t->last_trigger.realtime;
else if (dual_timestamp_is_set(&UNIT(t)->inactive_exit_timestamp))
b = UNIT(t)->inactive_exit_timestamp.realtime;
- else
+ else {
b = ts.realtime;
+ rebase_after_boot_time = true;
+ }
r = calendar_spec_next_usec(v->calendar_spec, b, &v->next_elapse);
if (r < 0)
continue;
- /* To make the delay due to RandomizedDelaySec= work even at boot, if the scheduled
- * time has already passed, set the time when systemd first started as the scheduled
- * time. Note that we base this on the monotonic timestamp of the boot, not the
- * realtime one, since the wallclock might have been off during boot. */
- rebased = map_clock_usec(UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].monotonic,
- CLOCK_MONOTONIC, CLOCK_REALTIME);
- if (v->next_elapse < rebased)
- v->next_elapse = rebased;
+ if (rebase_after_boot_time) {
+ /* To make the delay due to RandomizedDelaySec= work even at boot, if the scheduled
+ * time has already passed, set the time when systemd first started as the scheduled
+ * time. Note that we base this on the monotonic timestamp of the boot, not the
+ * realtime one, since the wallclock might have been off during boot. */
+ usec_t rebased = map_clock_usec(UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].monotonic,
+ CLOCK_MONOTONIC, CLOCK_REALTIME);
+ if (v->next_elapse < rebased)
+ v->next_elapse = rebased;
+ }
if (!found_realtime)
t->next_elapse_realtime = v->next_elapse;

View File

@ -0,0 +1,85 @@
From e16ede11dab405749b776aa6d58a9c7461a0dda5 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 28 Jan 2025 08:50:14 +0900
Subject: [PATCH] strv: introduce string_strv_hashmap_remove()
(cherry picked from commit c540875cd3b024f64980966376637ecc284d643c)
Related: RHEL-14112
---
src/basic/strv.c | 17 +++++++++++++++++
src/basic/strv.h | 5 +++++
src/test/test-hashmap-plain.c | 16 ++++++++++++++++
3 files changed, 38 insertions(+)
diff --git a/src/basic/strv.c b/src/basic/strv.c
index 66b70befd6..1f5d6f058f 100644
--- a/src/basic/strv.c
+++ b/src/basic/strv.c
@@ -920,6 +920,23 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) {
return 0;
}
+void string_strv_hashmap_remove(Hashmap *h, const char *key, const char *value) {
+ assert(key);
+
+ if (value) {
+ char **l = hashmap_get(h, key);
+ if (!l)
+ return;
+
+ strv_remove(l, value);
+ if (!strv_isempty(l))
+ return;
+ }
+
+ _unused_ _cleanup_free_ char *key_free = NULL;
+ strv_free(hashmap_remove2(h, key, (void**) &key_free));
+}
+
static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const char *value) {
char **l;
int r;
diff --git a/src/basic/strv.h b/src/basic/strv.h
index 6c9fa47943..9eb685fb86 100644
--- a/src/basic/strv.h
+++ b/src/basic/strv.h
@@ -261,6 +261,11 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space);
free_and_replace_full(a, b, strv_free)
extern const struct hash_ops string_strv_hash_ops;
+
+void string_strv_hashmap_remove(Hashmap *h, const char *key, const char *value);
+static inline void string_strv_ordered_hashmap_remove(OrderedHashmap *h, const char *key, const char *value) {
+ string_strv_hashmap_remove(PLAIN_HASHMAP(h), key, value);
+}
int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS);
int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS);
#define string_strv_hashmap_put(h, k, v) _string_strv_hashmap_put(h, k, v HASHMAP_DEBUG_SRC_ARGS)
diff --git a/src/test/test-hashmap-plain.c b/src/test/test-hashmap-plain.c
index 36a775012b..3bc96fc944 100644
--- a/src/test/test-hashmap-plain.c
+++ b/src/test/test-hashmap-plain.c
@@ -996,6 +996,22 @@ TEST(string_strv_hashmap) {
s = hashmap_get(m, "xxx");
assert_se(strv_equal(s, STRV_MAKE("bar", "BAR")));
+
+ string_strv_hashmap_remove(m, "foo", "bar");
+ ASSERT_NOT_NULL(s = hashmap_get(m, "foo"));
+ ASSERT_TRUE(strv_equal(s, STRV_MAKE("BAR")));
+
+ string_strv_hashmap_remove(m, "foo", "BAR");
+ ASSERT_NULL(hashmap_get(m, "foo"));
+
+ string_strv_hashmap_remove(m, "xxx", "BAR");
+ ASSERT_NOT_NULL(s = hashmap_get(m, "xxx"));
+ ASSERT_TRUE(strv_equal(s, STRV_MAKE("bar")));
+
+ string_strv_hashmap_remove(m, "xxx", "bar");
+ ASSERT_NULL(hashmap_get(m, "xxx"));
+
+ ASSERT_TRUE(hashmap_isempty(m));
}
/* Signal to test-hashmap.c that tests from this compilation unit were run. */

View File

@ -0,0 +1,230 @@
From fe5bad818a26875914f3b0c59fa3d4f5e6b3a41d Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 28 Jan 2025 09:55:12 +0900
Subject: [PATCH] unit-file: introduce unit_file_remove_from_name_map()
(cherry picked from commit d8b34aaef24599917d4e7fa04c78fffac3afe7cf)
Related: RHEL-14112
[msekleta: I've backported strv_equal_ignore_order() in the same commit
in order to get this to compile.]
---
src/basic/strv.c | 20 ++++++++++
src/basic/strv.h | 1 +
src/basic/unit-file.c | 35 +++++++++++++++++
src/basic/unit-file.h | 8 ++++
src/test/test-unit-file.c | 81 +++++++++++++++++++++++++++++++++++++++
5 files changed, 145 insertions(+)
diff --git a/src/basic/strv.c b/src/basic/strv.c
index 1f5d6f058f..47cc6931c1 100644
--- a/src/basic/strv.c
+++ b/src/basic/strv.c
@@ -775,6 +775,26 @@ int strv_compare(char * const *a, char * const *b) {
return 0;
}
+bool strv_equal_ignore_order(char **a, char **b) {
+
+ /* Just like strv_equal(), but doesn't care about the order of elements or about redundant entries
+ * (i.e. it's even ok if the number of entries in the array differ, as long as the difference just
+ * consists of repititions) */
+
+ if (a == b)
+ return true;
+
+ STRV_FOREACH(i, a)
+ if (!strv_contains(b, *i))
+ return false;
+
+ STRV_FOREACH(i, b)
+ if (!strv_contains(a, *i))
+ return false;
+
+ return true;
+}
+
void strv_print(char * const *l) {
STRV_FOREACH(s, l)
puts(*s);
diff --git a/src/basic/strv.h b/src/basic/strv.h
index 9eb685fb86..1de3c98e5c 100644
--- a/src/basic/strv.h
+++ b/src/basic/strv.h
@@ -160,6 +160,7 @@ bool strv_overlap(char * const *a, char * const *b) _pure_;
_STRV_FOREACH_PAIR(x, y, l, UNIQ_T(i, UNIQ))
char** strv_sort(char **l);
+bool strv_equal_ignore_order(char **a, char **b);
void strv_print(char * const *l);
#define strv_from_stdarg_alloca(first) \
diff --git a/src/basic/unit-file.c b/src/basic/unit-file.c
index c81c69db30..d7d7fd70f6 100644
--- a/src/basic/unit-file.c
+++ b/src/basic/unit-file.c
@@ -627,6 +627,41 @@ int unit_file_build_name_map(
return 1;
}
+int unit_file_remove_from_name_map(
+ const LookupPaths *lp,
+ uint64_t *cache_timestamp_hash,
+ Hashmap **unit_ids_map,
+ Hashmap **unit_names_map,
+ Set **path_cache,
+ const char *path) {
+
+ int r;
+
+ assert(path);
+
+ /* This assumes the specified path is already removed, and drops the relevant entries from the maps. */
+
+ /* If one of the lookup paths we are monitoring is already changed, let's rebuild the map. Then, the
+ * new map should not contain entries relevant to the specified path. */
+ r = unit_file_build_name_map(lp, cache_timestamp_hash, unit_ids_map, unit_names_map, path_cache);
+ if (r != 0)
+ return r;
+
+ /* If not, drop the relevant entries. */
+
+ _cleanup_free_ char *name = NULL;
+ r = path_extract_filename(path, &name);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to extract file name from '%s': %m", path);
+
+ _unused_ _cleanup_free_ char *key = NULL;
+ free(hashmap_remove2(*unit_ids_map, name, (void**) &key));
+ string_strv_hashmap_remove(*unit_names_map, name, name);
+ free(set_remove(*path_cache, path));
+
+ return 0;
+}
+
static int add_name(
const char *unit_name,
Set **names,
diff --git a/src/basic/unit-file.h b/src/basic/unit-file.h
index 1c43861f00..78f65dbc8e 100644
--- a/src/basic/unit-file.h
+++ b/src/basic/unit-file.h
@@ -52,6 +52,14 @@ int unit_file_build_name_map(
Hashmap **unit_names_map,
Set **path_cache);
+int unit_file_remove_from_name_map(
+ const LookupPaths *lp,
+ uint64_t *cache_timestamp_hash,
+ Hashmap **unit_ids_map,
+ Hashmap **unit_names_map,
+ Set **path_cache,
+ const char *path);
+
int unit_file_find_fragment(
Hashmap *unit_ids_map,
Hashmap *unit_name_map,
diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c
index dffa2822e6..389113c336 100644
--- a/src/test/test-unit-file.c
+++ b/src/test/test-unit-file.c
@@ -1,10 +1,15 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "fileio.h"
#include "path-lookup.h"
+#include "path-util.h"
+#include "random-util.h"
+#include "rm-rf.h"
#include "set.h"
#include "special.h"
#include "strv.h"
#include "tests.h"
+#include "tmpfile-util.h"
#include "unit-file.h"
TEST(unit_validate_alias_symlink_and_warn) {
@@ -85,6 +90,82 @@ TEST(unit_file_build_name_map) {
}
}
+static bool test_unit_file_remove_from_name_map_trail(const LookupPaths *lp, size_t trial) {
+ int r;
+
+ log_debug("/* %s(trial=%zu) */", __func__, trial);
+
+ _cleanup_hashmap_free_ Hashmap *unit_ids = NULL, *unit_names = NULL;
+ _cleanup_set_free_ Set *path_cache = NULL;
+ assert_se(unit_file_build_name_map(lp, NULL, &unit_ids, &unit_names, &path_cache) > 0);
+
+ _cleanup_free_ char *name = NULL;
+ for (size_t i = 0; i < 100; i++) {
+ ASSERT_OK(asprintf(&name, "test-unit-file-%"PRIx64".service", random_u64()));
+ if (!hashmap_contains(unit_ids, name))
+ break;
+ name = mfree(name);
+ }
+ ASSERT_NOT_NULL(name);
+
+ _cleanup_free_ char *path = path_join(lp->transient, name);
+ ASSERT_NOT_NULL(path);
+ ASSERT_OK(write_string_file(path, "[Unit]\n", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755));
+
+ uint64_t cache_timestamp_hash = 0;
+ assert_se(unit_file_build_name_map(lp, &cache_timestamp_hash, &unit_ids, &unit_names, &path_cache) > 0);
+
+ ASSERT_STREQ(hashmap_get(unit_ids, name), path);
+ ASSERT_TRUE(strv_equal(hashmap_get(unit_names, name), STRV_MAKE(name)));
+ ASSERT_TRUE(set_contains(path_cache, path));
+
+ assert_se(unlink(path) >= 0);
+
+ ASSERT_OK(r = unit_file_remove_from_name_map(lp, &cache_timestamp_hash, &unit_ids, &unit_names, &path_cache, path));
+ if (r > 0)
+ return false; /* someone touches unit files. Retrying. */
+
+ ASSERT_FALSE(hashmap_contains(unit_ids, name));
+ ASSERT_FALSE(hashmap_contains(unit_names, path));
+ ASSERT_FALSE(set_contains(path_cache, path));
+
+ _cleanup_hashmap_free_ Hashmap *unit_ids_2 = NULL, *unit_names_2 = NULL;
+ _cleanup_set_free_ Set *path_cache_2 = NULL;
+ assert_se(unit_file_build_name_map(lp, NULL, &unit_ids_2, &unit_names_2, &path_cache_2) > 0);
+
+ if (hashmap_size(unit_ids) != hashmap_size(unit_ids_2) ||
+ hashmap_size(unit_names) != hashmap_size(unit_names_2) ||
+ !set_equal(path_cache, path_cache_2))
+ return false;
+
+ const char *k, *v;
+ HASHMAP_FOREACH_KEY(v, k, unit_ids)
+ if (!streq_ptr(hashmap_get(unit_ids_2, k), v))
+ return false;
+
+ char **l;
+ HASHMAP_FOREACH_KEY(l, k, unit_names)
+ if (!strv_equal_ignore_order(hashmap_get(unit_names_2, k), l))
+ return false;
+
+ return true;
+}
+
+
+TEST(unit_file_remove_from_name_map) {
+ _cleanup_(rm_rf_physical_and_freep) char *d = NULL;
+
+ _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ ASSERT_OK(lookup_paths_init(&lp, LOOKUP_SCOPE_SYSTEM, LOOKUP_PATHS_TEMPORARY_GENERATED, NULL));
+ ASSERT_NOT_NULL(d = strdup(lp.temporary_dir));
+
+ for (size_t i = 0; i < 10; i++)
+ if (test_unit_file_remove_from_name_map_trail(&lp, i))
+ return;
+
+ assert_not_reached();
+}
+
TEST(runlevel_to_target) {
in_initrd_force(false);
assert_se(streq_ptr(runlevel_to_target(NULL), NULL));

View File

@ -0,0 +1,52 @@
From 4726233b421628eae405b3b3fb08222cf0befae4 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 28 Jan 2025 10:09:32 +0900
Subject: [PATCH] core/unit: remove path to transient unit file from unit name
maps on stop
Fixes #35190.
(cherry picked from commit fce94c5c563b8f6ede2b8f7f283d2d2faff4e062)
Resolves: RHEL-14112
---
src/core/unit.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/core/unit.c b/src/core/unit.c
index 9e349402ff..afe3fdab04 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -598,13 +598,11 @@ static void unit_clear_dependencies(Unit *u) {
static void unit_remove_transient(Unit *u) {
assert(u);
+ assert(u->manager);
if (!u->transient)
return;
- if (u->fragment_path)
- (void) unlink(u->fragment_path);
-
STRV_FOREACH(i, u->dropin_paths) {
_cleanup_free_ char *p = NULL, *pp = NULL;
@@ -621,6 +619,17 @@ static void unit_remove_transient(Unit *u) {
(void) unlink(*i);
(void) rmdir(p);
}
+
+ if (u->fragment_path) {
+ (void) unlink(u->fragment_path);
+ (void) unit_file_remove_from_name_map(
+ &u->manager->lookup_paths,
+ &u->manager->unit_cache_timestamp_hash,
+ &u->manager->unit_id_map,
+ &u->manager->unit_name_map,
+ &u->manager->unit_path_cache,
+ u->fragment_path);
+ }
}
static void unit_free_requires_mounts_for(Unit *u) {

View File

@ -0,0 +1,33 @@
From 1aa6c0d3bcac98d3442d07412f4296d5b9b18dc0 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Mon, 27 Jan 2025 22:24:16 +0900
Subject: [PATCH] TEST-07-PID1: add reprudcer for issue #35190
(cherry picked from commit 448e99251aa47a5986425a1783da44d1200fe733)
Related: RHEL-14112
---
test/units/testsuite-07.transient.sh | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100755 test/units/testsuite-07.transient.sh
diff --git a/test/units/testsuite-07.transient.sh b/test/units/testsuite-07.transient.sh
new file mode 100755
index 0000000000..ae71a38143
--- /dev/null
+++ b/test/units/testsuite-07.transient.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+journalctl --sync
+TS="$(date '+%H:%M:%S')"
+
+systemd-run -u hogehoge.service sleep infinity
+systemctl daemon-reload
+systemctl stop hogehoge.service
+
+journalctl --sync
+[[ -z "$(journalctl -b -q --since "$TS" -u hogehoge.service -p notice)" ]]

View File

@ -0,0 +1,34 @@
From 2fe492a2f0fefa0f782cb04a248fc9dcd5667bf0 Mon Sep 17 00:00:00 2001
From: Michal Sekletar <msekleta@redhat.com>
Date: Fri, 24 Oct 2025 12:55:20 +0200
Subject: [PATCH] coredump: handle ENOBUFS and EMSGSIZE the same way
Depending on the runtime configuration, e.g. sysctls
net.core.wmem_default= and net.core.rmem_default and on the actual
message size, sendmsg() can fail also with ENOBUFS. E.g. alloc_skb()
failure caused by net.core.[rw]mem_default=64MiB and huge fdinfo list
from process that has 90k opened FDs.
We should handle this case in the same way as EMSGSIZE and drop part of
the message.
(cherry picked from commit 28e62e684b631f928f1d857b04f45f0d34441675)
Resolves: RHEL-103801
---
src/coredump/coredump.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
index e0aac3c8d0..28dabf017b 100644
--- a/src/coredump/coredump.c
+++ b/src/coredump/coredump.c
@@ -1273,7 +1273,7 @@ static int send_iovec(const struct iovec_wrapper *iovw, int input_fd, int mntns_
if (sendmsg(fd, &mh, MSG_NOSIGNAL) >= 0)
break;
- if (errno == EMSGSIZE && mh.msg_iov[0].iov_len > 0) {
+ if (IN_SET(errno, EMSGSIZE, ENOBUFS) && mh.msg_iov[0].iov_len > 0) {
/* This field didn't fit? That's a pity. Given that this is
* just metadata, let's truncate the field at half, and try
* again. We append three dots, in order to show that this is

View File

@ -0,0 +1,32 @@
From 2a16be65ca89cf18adf63a99ae1b1748e63d6773 Mon Sep 17 00:00:00 2001
From: Li Tian <94442129+litian1992@users.noreply.github.com>
Date: Tue, 19 Aug 2025 05:43:41 +0800
Subject: [PATCH] ukify: rstrip and escape binary null characters from
'inspect' output (#38607)
SBAT section of UKI may contain \u000 null characters. Rstrip them, and if there's anything left in the middle,
escape them so they are displayed as text.
Fixes #38606
(cherry picked from commit 776991a3f349d9c99fd166a0c87fcd2bc1bf92a5)
Signed-off-by: Li Tian <litian@redhat.com>
Resolves: RHEL-109558
---
src/ukify/ukify.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py
index 08f505a271..2d5050eaca 100755
--- a/src/ukify/ukify.py
+++ b/src/ukify/ukify.py
@@ -986,7 +986,7 @@ def inspect_section(opts, section):
if ttype == 'text':
try:
- struct['text'] = data.decode()
+ struct['text'] = data.rstrip(b'\0').replace(b'\0', b'\\0').decode()
except UnicodeDecodeError as e:
print(f"Section {name!r} is not valid text: {e}")
struct['text'] = '(not valid UTF-8)'

View File

@ -0,0 +1,143 @@
From 6085358791b712a60fb22c7870abf0aa75c5f157 Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Wed, 19 Nov 2025 14:44:13 +0100
Subject: [PATCH] timer: rebase last_trigger timestamp if needed
After bdb8e584f4509de0daebbe2357d23156160c3a90 we stopped rebasing the
next elapse timestamp unconditionally and the only case where we'd do
that was when both last trigger and last inactive timestamps were empty.
This covered timer units during boot just fine, since they would have
neither of those timestamps set. However, persistent timers
(Persistent=yes) store their last trigger timestamp on a persistent
storage and load it back after reboot, so the rebasing was skipped in
this case.
To mitigate this, check the last_trigger timestamp is older than the
current machine boot - if so, that means that it came from a stamp file
of a persistent timer unit and we need to rebase it to make
RandomizedDelaySec= work properly.
Follow-up for bdb8e584f4509de0daebbe2357d23156160c3a90.
(cherry picked from commit 3605b3ba87833a9919bfde05952a7d9de10499a2)
Related: RHEL-118215
---
src/core/timer.c | 15 +++--
...tsuite-53.RandomizedDelaySec-persistent.sh | 67 +++++++++++++++++++
2 files changed, 78 insertions(+), 4 deletions(-)
create mode 100755 test/units/testsuite-53.RandomizedDelaySec-persistent.sh
diff --git a/src/core/timer.c b/src/core/timer.c
index 4b0266bc68..8fb79bc0cb 100644
--- a/src/core/timer.c
+++ b/src/core/timer.c
@@ -394,15 +394,23 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
if (v->base == TIMER_CALENDAR) {
bool rebase_after_boot_time = false;
usec_t b;
+ usec_t boot_monotonic = UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].monotonic;
/* If we know the last time this was
* triggered, schedule the job based relative
* to that. If we don't, just start from
* the activation time. */
- if (dual_timestamp_is_set(&t->last_trigger))
+ if (dual_timestamp_is_set(&t->last_trigger)) {
b = t->last_trigger.realtime;
- else if (dual_timestamp_is_set(&UNIT(t)->inactive_exit_timestamp))
+
+ /* Check if the last_trigger timestamp is older than the current machine
+ * boot. If so, this means the timestamp came from a stamp file of a
+ * persistent timer and we need to rebase it to make RandomizedDelaySec=
+ * work (see below). */
+ if (t->last_trigger.monotonic < boot_monotonic)
+ rebase_after_boot_time = true;
+ } else if (dual_timestamp_is_set(&UNIT(t)->inactive_exit_timestamp))
b = UNIT(t)->inactive_exit_timestamp.realtime;
else {
b = ts.realtime;
@@ -418,8 +426,7 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
* time has already passed, set the time when systemd first started as the scheduled
* time. Note that we base this on the monotonic timestamp of the boot, not the
* realtime one, since the wallclock might have been off during boot. */
- usec_t rebased = map_clock_usec(UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].monotonic,
- CLOCK_MONOTONIC, CLOCK_REALTIME);
+ usec_t rebased = map_clock_usec(boot_monotonic, CLOCK_MONOTONIC, CLOCK_REALTIME);
if (v->next_elapse < rebased)
v->next_elapse = rebased;
}
diff --git a/test/units/testsuite-53.RandomizedDelaySec-persistent.sh b/test/units/testsuite-53.RandomizedDelaySec-persistent.sh
new file mode 100755
index 0000000000..af22daecc7
--- /dev/null
+++ b/test/units/testsuite-53.RandomizedDelaySec-persistent.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Persistent timers (i.e. timers with Persitent=yes) save their last trigger timestamp to a persistent
+# storage (a stamp file), which is loaded during subsequent boots. As mentioned in the man page, such timers
+# should be still affected by RandomizedDelaySec= during boot even if they already elapsed and would be then
+# triggered immediately.
+#
+# This behavior was, however, broken by [0], which stopped rebasing the to-be next elapse timestamps
+# unconditionally and left that only for timers that have neither last trigger nor inactive exit timestamps
+# set, since rebasing is needed only during boot. This holds for regular timers during boot, but not for
+# persistent ones, since the last trigger timestamp is loaded from a persistent storage.
+#
+# Provides coverage for:
+# - https://github.com/systemd/systemd/issues/39739
+#
+# [0] bdb8e584f4509de0daebbe2357d23156160c3a90
+#
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/test-control.sh
+. "$(dirname "$0")"/util.sh
+
+UNIT_NAME="timer-RandomizedDelaySec-persistent-$RANDOM"
+STAMP_FILE="/var/lib/systemd/timers/stamp-$UNIT_NAME.timer"
+
+# Setup
+cat >"/run/systemd/system/$UNIT_NAME.timer" <<EOF
+[Timer]
+OnCalendar=daily
+Persistent=true
+RandomizedDelaySec=12h
+EOF
+
+cat >"/run/systemd/system/$UNIT_NAME.service" <<\EOF
+[Service]
+ExecStart=echo "Service ran at $(date)"
+EOF
+
+systemctl daemon-reload
+
+# Create timer's state file with an old-enough timestamp (~2 days ago), so it'd definitely elapse if the next
+# elapse timestamp wouldn't get rebased
+mkdir -p "$(dirname "$STAMP_FILE")"
+touch -d "2 days ago" "$STAMP_FILE"
+stat "$STAMP_FILE"
+SAVED_LAST_TRIGGER_S="$(stat --format="%Y" "$STAMP_FILE")"
+
+# Start the timer and verify that its last trigger timestamp didn't change
+#
+# The last trigger timestamp should get rebased before it gets used as a base for the next elapse timestamp
+# (since it pre-dates the machine boot time). This should then add a RandomizedDelaySec= to the rebased
+# timestamp and the timer unit should not get triggered immediately after starting.
+systemctl start "$UNIT_NAME.timer"
+systemctl status "$UNIT_NAME.timer"
+
+TIMER_LAST_TRIGGER="$(systemctl show --property=LastTriggerUSec --value "$UNIT_NAME.timer")"
+TIMER_LAST_TRIGGER_S="$(date --date="$TIMER_LAST_TRIGGER" "+%s")"
+: "The timer should not be triggered immediately, hence the last trigger timestamp should not change"
+assert_eq "$SAVED_LAST_TRIGGER_S" "$TIMER_LAST_TRIGGER_S"
+
+# Cleanup
+systemctl stop "$UNIT_NAME".{timer,service}
+systemctl clean --what=state "$UNIT_NAME.timer"
+rm -f "/run/systemd/system/$UNIT_NAME".{timer,service}
+systemctl daemon-reload

View File

@ -0,0 +1,130 @@
From 01826a6ded513adea1dabeccc6b860baee277482 Mon Sep 17 00:00:00 2001
From: David Tardon <dtardon@redhat.com>
Date: Thu, 30 May 2024 10:44:36 +0200
Subject: [PATCH] cryptsetup-generator: refactor add_crypttab_devices()
Move the processing of a crypttab entry to a separate function.
No functional changes, just refactoring.
(cherry picked from commit a07cb7d404582f9c0bfaedb9dd07f93848aa91c6)
Related: RHEL-127859
---
src/cryptsetup/cryptsetup-generator.c | 87 +++++++++++++++------------
1 file changed, 49 insertions(+), 38 deletions(-)
diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c
index 9e8e7e746f..6ab3b85b6b 100644
--- a/src/cryptsetup/cryptsetup-generator.c
+++ b/src/cryptsetup/cryptsetup-generator.c
@@ -779,6 +779,52 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
return 0;
}
+static int add_crypttab_device(const char *name, const char *device, const char *keyspec, const char *options) {
+ _cleanup_free_ char *keyfile = NULL, *keydev = NULL, *headerdev = NULL, *filtered_header = NULL;
+ crypto_device *d = NULL;
+ char *uuid;
+ int r;
+
+ uuid = startswith(device, "UUID=");
+ if (!uuid)
+ uuid = path_startswith(device, "/dev/disk/by-uuid/");
+ if (!uuid)
+ uuid = startswith(name, "luks-");
+ if (uuid)
+ d = hashmap_get(arg_disks, uuid);
+
+ if (arg_allow_list && !d) {
+ log_info("Not creating device '%s' because it was not specified on the kernel command line.", name);
+ return 0;
+ }
+
+ r = split_locationspec(keyspec, &keyfile, &keydev);
+ if (r < 0)
+ return r;
+
+ if (options && (!d || !d->options)) {
+ r = filter_header_device(options, &headerdev, &filtered_header);
+ if (r < 0)
+ return r;
+ options = filtered_header;
+ }
+
+ r = create_disk(name,
+ device,
+ keyfile,
+ keydev,
+ (d && d->options) ? d->headerdev : headerdev,
+ (d && d->options) ? d->options : options,
+ arg_crypttab);
+ if (r < 0)
+ return r;
+
+ if (d)
+ d->create = false;
+
+ return 0;
+}
+
static int add_crypttab_devices(void) {
_cleanup_fclose_ FILE *f = NULL;
unsigned crypttab_line = 0;
@@ -795,10 +841,8 @@ static int add_crypttab_devices(void) {
}
for (;;) {
- _cleanup_free_ char *line = NULL, *name = NULL, *device = NULL, *keyspec = NULL, *options = NULL,
- *keyfile = NULL, *keydev = NULL, *headerdev = NULL, *filtered_header = NULL;
- crypto_device *d = NULL;
- char *l, *uuid;
+ _cleanup_free_ char *line = NULL, *name = NULL, *device = NULL, *keyspec = NULL, *options = NULL;
+ char *l;
int k;
r = read_line(f, LONG_LINE_MAX, &line);
@@ -819,42 +863,9 @@ static int add_crypttab_devices(void) {
continue;
}
- uuid = startswith(device, "UUID=");
- if (!uuid)
- uuid = path_startswith(device, "/dev/disk/by-uuid/");
- if (!uuid)
- uuid = startswith(name, "luks-");
- if (uuid)
- d = hashmap_get(arg_disks, uuid);
-
- if (arg_allow_list && !d) {
- log_info("Not creating device '%s' because it was not specified on the kernel command line.", name);
- continue;
- }
-
- r = split_locationspec(keyspec, &keyfile, &keydev);
+ r = add_crypttab_device(name, device, keyspec, options);
if (r < 0)
return r;
-
- if (options && (!d || !d->options)) {
- r = filter_header_device(options, &headerdev, &filtered_header);
- if (r < 0)
- return r;
- free_and_replace(options, filtered_header);
- }
-
- r = create_disk(name,
- device,
- keyfile,
- keydev,
- (d && d->options) ? d->headerdev : headerdev,
- (d && d->options) ? d->options : options,
- arg_crypttab);
- if (r < 0)
- return r;
-
- if (d)
- d->create = false;
}
return 0;

View File

@ -0,0 +1,43 @@
From 238dadc16fb2bb6ad2fef5602dac5cd2c9aa31ed Mon Sep 17 00:00:00 2001
From: David Tardon <dtardon@redhat.com>
Date: Thu, 30 May 2024 10:46:13 +0200
Subject: [PATCH] cryptsetup-generator: continue parsing after error
Let's make the crypttab parser more robust and continue even if parsing
of a line failed.
(cherry picked from commit 83813bae7ae471862ff84b038b5e4eaefae41c98)
Resolves: RHEL-127859
---
src/cryptsetup/cryptsetup-generator.c | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c
index 6ab3b85b6b..924a403ee5 100644
--- a/src/cryptsetup/cryptsetup-generator.c
+++ b/src/cryptsetup/cryptsetup-generator.c
@@ -828,7 +828,7 @@ static int add_crypttab_device(const char *name, const char *device, const char
static int add_crypttab_devices(void) {
_cleanup_fclose_ FILE *f = NULL;
unsigned crypttab_line = 0;
- int r;
+ int r, ret = 0;
if (!arg_read_crypttab)
return 0;
@@ -863,12 +863,10 @@ static int add_crypttab_devices(void) {
continue;
}
- r = add_crypttab_device(name, device, keyspec, options);
- if (r < 0)
- return r;
+ RET_GATHER(ret, add_crypttab_device(name, device, keyspec, options));
}
- return 0;
+ return ret;
}
static int add_proc_cmdline_devices(void) {

View File

@ -0,0 +1,39 @@
From 25a4e8e1d411f56fcee5b53d1620c42f3bba16e6 Mon Sep 17 00:00:00 2001
From: David Tardon <dtardon@redhat.com>
Date: Thu, 30 May 2024 13:32:20 +0200
Subject: [PATCH] cryptsetup-generator: parse all cmdline devices too
(cherry picked from commit 47c703d949e84997d11d657fade68064c04a46c8)
Related: RHEL-127859
---
src/cryptsetup/cryptsetup-generator.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c
index 924a403ee5..1136f5aed7 100644
--- a/src/cryptsetup/cryptsetup-generator.c
+++ b/src/cryptsetup/cryptsetup-generator.c
@@ -870,7 +870,7 @@ static int add_crypttab_devices(void) {
}
static int add_proc_cmdline_devices(void) {
- int r;
+ int r, ret = 0;
crypto_device *d;
HASHMAP_FOREACH(d, arg_disks) {
@@ -896,11 +896,10 @@ static int add_proc_cmdline_devices(void) {
d->headerdev,
d->options ?: arg_default_options,
"/proc/cmdline");
- if (r < 0)
- return r;
+ RET_GATHER(ret, r);
}
- return 0;
+ return ret;
}
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(crypt_device_hash_ops, char, string_hash_func, string_compare_func,

View File

@ -0,0 +1,33 @@
From 1ba4f74ed15a3b715eba0f21a12239af6e44146f Mon Sep 17 00:00:00 2001
From: David Tardon <dtardon@redhat.com>
Date: Thu, 30 May 2024 13:33:57 +0200
Subject: [PATCH] cryptsetup-generator: always process cmdline devices
(cherry picked from commit d181939e2e382631d9b067e0b4cfbf11b709a297)
Related: RHEL-127859
---
src/cryptsetup/cryptsetup-generator.c | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c
index 1136f5aed7..06292f7f73 100644
--- a/src/cryptsetup/cryptsetup-generator.c
+++ b/src/cryptsetup/cryptsetup-generator.c
@@ -925,14 +925,9 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
return 0;
r = add_crypttab_devices();
- if (r < 0)
- return r;
-
- r = add_proc_cmdline_devices();
- if (r < 0)
- return r;
+ RET_GATHER(r, add_proc_cmdline_devices());
- return 0;
+ return r;
}
DEFINE_MAIN_GENERATOR_FUNCTION(run);

View File

@ -0,0 +1,44 @@
From a48488d06e60af0d02387488d4de0abbaddf93ad Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Mon, 27 Nov 2023 18:39:02 +0100
Subject: [PATCH] logind: add "background-light" session class
This is the same as the "background" class, but does *not* pull in a
service manager. It might be useful for things like select cron jobs
that do not intend to call per-user IPC calls.
Replaces: #23569
Fixes: #23978
(cherry picked from commit b5100c736f1fce2b6b22c07cf2725e4ec3764a75)
Related: RHEL-109833
---
src/login/logind-session.c | 1 +
src/login/logind-session.h | 1 +
2 files changed, 2 insertions(+)
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index 8c8dd0d43e..5ba1e690ac 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -1525,6 +1525,7 @@ static const char* const session_class_table[_SESSION_CLASS_MAX] = {
[SESSION_GREETER] = "greeter",
[SESSION_LOCK_SCREEN] = "lock-screen",
[SESSION_BACKGROUND] = "background",
+ [SESSION_BACKGROUND_LIGHT] = "background-light",
};
DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass);
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
index 5ee059aa4f..a02d72c211 100644
--- a/src/login/logind-session.h
+++ b/src/login/logind-session.h
@@ -23,6 +23,7 @@ typedef enum SessionClass {
SESSION_GREETER,
SESSION_LOCK_SCREEN,
SESSION_BACKGROUND,
+ SESSION_BACKGROUND_LIGHT, /* Like SESSION_BACKGROUND, but without the service manager */
_SESSION_CLASS_MAX,
_SESSION_CLASS_INVALID = -EINVAL,
} SessionClass;

View File

@ -0,0 +1,118 @@
From d5d08290cf66a0c491a875345902d5c3bfeb6c5a Mon Sep 17 00:00:00 2001
From: Michal Sekletar <msekleta@redhat.com>
Date: Mon, 25 Aug 2025 15:09:36 +0200
Subject: [PATCH] pam_systemd: honor session class provided via PAM environment
Replaces #38638
Co-authored-by: Lennart Poettering <lennart@poettering.net>
(cherry picked from commit cf2630acaa87ded5ad99ea30ed4bd895e71ca503)
Resolves: RHEL-109833
[msekleta: this is absolutely minimal version of the ideas implemented in
https://github.com/systemd/systemd/pull/30884. At this point I want to avoid
big/risky backports and what I am proposing here should suffice.]
---
man/pam_systemd.xml | 11 ++++++++++-
src/login/logind-session.c | 5 +++--
src/login/logind-user.c | 16 +++++++++++++++-
src/login/pam_systemd.c | 6 ++++--
4 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/man/pam_systemd.xml b/man/pam_systemd.xml
index 60b8577822..55239ea3d7 100644
--- a/man/pam_systemd.xml
+++ b/man/pam_systemd.xml
@@ -95,8 +95,17 @@
<literal>lock-screen</literal> or <literal>background</literal>. See
<citerefentry><refentrytitle>sd_session_get_class</refentrytitle><manvolnum>3</manvolnum></citerefentry> for
details about the session class.</para></listitem>
- </varlistentry>
+ <para>If no session class is specified via either the PAM module option or via the
+ <varname>$XDG_SESSION_CLASS</varname> environment variable, the class is automatically chosen, depending on
+ various session parameters, such as the session type (if known), whether the session has a TTY or X11
+ display, and the user disposition. Note that various tools allow setting the session class for newly
+ allocated PAM sessions explicitly by means of the <varname>$XDG_SESSION_CLASS</varname> environment variable.
+ For example, classic UNIX cronjobs support environment variable assignments (see
+ <citerefentry project='man-pages'><refentrytitle>crontab</refentrytitle><manvolnum>5</manvolnum></citerefentry>),
+ which may be used to choose between the <constant>background</constant> and
+ <constant>background-light</constant> session class individually per cronjob.</para>
+ </varlistentry>
<varlistentry>
<term><varname>type=</varname></term>
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index 5ba1e690ac..2ad05e3798 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -680,8 +680,9 @@ static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_er
s->user->slice,
description,
/* These two have StopWhenUnneeded= set, hence add a dep towards them */
- STRV_MAKE(s->user->runtime_dir_service,
- s->user->service),
+ s->class == SESSION_BACKGROUND_LIGHT ?
+ STRV_MAKE(s->user->runtime_dir_service) :
+ STRV_MAKE(s->user->runtime_dir_service, s->user->service),
after,
user_record_home_directory(s->user->user_record),
properties,
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
index e02ad754ee..ffa32c6ce5 100644
--- a/src/login/logind-user.c
+++ b/src/login/logind-user.c
@@ -441,6 +441,19 @@ static int user_update_slice(User *u) {
return 0;
}
+static bool user_wants_service_manager(User *u) {
+ assert(u);
+
+ LIST_FOREACH(sessions_by_user, s, u->sessions)
+ if (s->class != SESSION_BACKGROUND_LIGHT)
+ return true;
+
+ if (user_check_linger_file(u) > 0)
+ return true;
+
+ return false;
+}
+
int user_start(User *u) {
assert(u);
@@ -464,7 +477,8 @@ int user_start(User *u) {
(void) user_update_slice(u);
/* Start user@UID.service */
- user_start_service(u);
+ if (user_wants_service_manager(u))
+ user_start_service(u);
if (!u->started) {
if (!dual_timestamp_is_set(&u->timestamp))
diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c
index a288b3602a..c7377e21a8 100644
--- a/src/login/pam_systemd.c
+++ b/src/login/pam_systemd.c
@@ -753,14 +753,16 @@ _public_ PAM_EXTERN int pam_sm_open_session(
* (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked
* off processes.) */
type = "unspecified";
- class = "background";
+ if (isempty(class))
+ class = "background";
tty = NULL;
} else if (streq(tty, "ssh")) {
/* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further
* details look for "PAM_TTY_KLUDGE" in the openssh sources). */
type ="tty";
- class = "user";
+ if (isempty(class))
+ class = "user";
tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually
* associated with a pty — won't be tracked by their tty in logind. This is because ssh
* does the PAM session registration early for new connections, and registers a pty only

View File

@ -0,0 +1,36 @@
From 52defa44074113197a8caade1254a61cfdcfa363 Mon Sep 17 00:00:00 2001
From: Florian Schmaus <flo@geekplace.eu>
Date: Thu, 9 Nov 2023 08:59:59 +0100
Subject: [PATCH] core: fix array size in unit_log_resources()
In 0531bded79dc ("core: include peak memory in unit_log_resources()") new log
messages where added, however the size of the according arrays to hold the
messages was not adjusted.
Fixes: 0531bded79dc ("core: include peak memory in unit_log_resources()")
(cherry picked from commit 893028523469b3ec459388428ddc466942cdaf4d)
Resolves: RHEL-131338
---
src/core/unit.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/core/unit.c b/src/core/unit.c
index afe3fdab04..009f416280 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -2242,12 +2242,12 @@ static int raise_level(int log_level, bool condition_info, bool condition_notice
}
static int unit_log_resources(Unit *u) {
- struct iovec iovec[1 + _CGROUP_IP_ACCOUNTING_METRIC_MAX + _CGROUP_IO_ACCOUNTING_METRIC_MAX + 4];
+ struct iovec iovec[1 + 1 + _CGROUP_IP_ACCOUNTING_METRIC_MAX + _CGROUP_IO_ACCOUNTING_METRIC_MAX + 4];
bool any_traffic = false, have_ip_accounting = false, any_io = false, have_io_accounting = false;
_cleanup_free_ char *igress = NULL, *egress = NULL, *rr = NULL, *wr = NULL;
int log_level = LOG_DEBUG; /* May be raised if resources consumed over a threshold */
size_t n_message_parts = 0, n_iovec = 0;
- char* message_parts[1 + 2 + 2 + 1], *t;
+ char* message_parts[1 + 1 + 2 + 2 + 1], *t;
nsec_t nsec = NSEC_INFINITY;
uint64_t memory_peak = UINT64_MAX;
int r;

View File

@ -0,0 +1,63 @@
From 8e0da3f5c5518350215a7186dfa748207ba921e8 Mon Sep 17 00:00:00 2001
From: Luca Boccassi <bluca@debian.org>
Date: Mon, 5 Dec 2022 21:05:54 +0000
Subject: [PATCH] pid1: add env var to override default mount rate limit burst
I am hitting the rate limit on a busy system with low resources, and
it stalls the boot process which is Very Bad (TM).
(cherry picked from commit 24a4542cfa674ee80b54afcc223f2490a011966b)
Related: RHEL-129153
---
docs/ENVIRONMENT.md | 7 +++++++
src/core/mount.c | 11 ++++++++++-
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md
index 54b779d312..88e6f5b372 100644
--- a/docs/ENVIRONMENT.md
+++ b/docs/ENVIRONMENT.md
@@ -281,6 +281,13 @@ All tools:
type as unsupported may not prevent loading some units of that type if they
are referenced by other units of another supported type.
+* `$SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_BURST` — can be set to override the mount
+ units burst rate limit for parsing `/proc/self/mountinfo`. On a system with
+ few resources but many mounts the rate limit may be hit, which will cause the
+ processing of mount units to stall. The burst limit may be adjusted when the
+ default is not appropriate for a given system. Defaults to `5`, accepts
+ positive integers.
+
`systemd-remount-fs`:
* `$SYSTEMD_REMOUNT_ROOT_RW=1` — if set and no entry for the root directory
diff --git a/src/core/mount.c b/src/core/mount.c
index 79772fb6f1..a8e101aa64 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -1910,6 +1910,7 @@ static void mount_enumerate(Manager *m) {
mnt_init_debug(0);
if (!m->mount_monitor) {
+ unsigned mount_rate_limit_burst = 5;
int fd;
m->mount_monitor = mnt_new_monitor();
@@ -1949,7 +1950,15 @@ static void mount_enumerate(Manager *m) {
goto fail;
}
- r = sd_event_source_set_ratelimit(m->mount_event_source, 1 * USEC_PER_SEC, 5);
+ /* Let users override the default (5 in 1s), as it stalls the boot sequence on busy systems. */
+ const char *e = secure_getenv("SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_BURST");
+ if (e) {
+ r = safe_atou(e, &mount_rate_limit_burst);
+ if (r < 0)
+ log_debug("Invalid value in $SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_BURST, ignoring: %s", e);
+ }
+
+ r = sd_event_source_set_ratelimit(m->mount_event_source, 1 * USEC_PER_SEC, mount_rate_limit_burst);
if (r < 0) {
log_error_errno(r, "Failed to enable rate limit for mount events: %m");
goto fail;

View File

@ -0,0 +1,75 @@
From 4f880e4dfc1b2e25046be380182535c39a931109 Mon Sep 17 00:00:00 2001
From: xujing <xujing125@huawei.com>
Date: Wed, 16 Oct 2024 15:19:09 +0800
Subject: [PATCH] pid1: add env var to override default mount rate limit
interval
Similar to 24a4542c. 24a4542c can only be set 1 in 1s at most,
sometimes we may need to set to something else(such as 1 in 2s).
So it's best to let the user decide.
This also allows users to solve #34690.
(cherry picked from commit cc2030f928981947db8fb9ec185a82024abab2c4)
Related: RHEL-129153
---
docs/ENVIRONMENT.md | 7 +++++++
src/core/mount.c | 14 +++++++++++---
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md
index 88e6f5b372..711364a2f7 100644
--- a/docs/ENVIRONMENT.md
+++ b/docs/ENVIRONMENT.md
@@ -288,6 +288,13 @@ All tools:
default is not appropriate for a given system. Defaults to `5`, accepts
positive integers.
+* `$SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_INTERVAL_SEC` — can be set to override the mount
+ units interval rate limit for parsing `/proc/self/mountinfo`. Similar to
+ `$SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_BURST`, the interval limit maybe adjusted when
+ the default is not appropriate for a given system. The default value is 1 and the
+ default application time unit is second, and the time unit can beoverriden as usual
+ by specifying it explicitly, see the systemd.time(7) man page.
+
`systemd-remount-fs`:
* `$SYSTEMD_REMOUNT_ROOT_RW=1` — if set and no entry for the root directory
diff --git a/src/core/mount.c b/src/core/mount.c
index a8e101aa64..be6fbf4cc4 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -1910,6 +1910,7 @@ static void mount_enumerate(Manager *m) {
mnt_init_debug(0);
if (!m->mount_monitor) {
+ usec_t mount_rate_limit_interval = 1 * USEC_PER_SEC;
unsigned mount_rate_limit_burst = 5;
int fd;
@@ -1951,14 +1952,21 @@ static void mount_enumerate(Manager *m) {
}
/* Let users override the default (5 in 1s), as it stalls the boot sequence on busy systems. */
- const char *e = secure_getenv("SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_BURST");
+ const char *e = secure_getenv("SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_INTERVAL_SEC");
+ if (e) {
+ r = parse_sec(e, &mount_rate_limit_interval);
+ if (r < 0)
+ log_debug_errno(r, "Invalid value in $SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_INTERVAL_SEC, ignoring: %s", e);
+ }
+
+ e = secure_getenv("SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_BURST");
if (e) {
r = safe_atou(e, &mount_rate_limit_burst);
if (r < 0)
- log_debug("Invalid value in $SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_BURST, ignoring: %s", e);
+ log_debug_errno(r, "Invalid value in $SYSTEMD_DEFAULT_MOUNT_RATE_LIMIT_BURST, ignoring: %s", e);
}
- r = sd_event_source_set_ratelimit(m->mount_event_source, 1 * USEC_PER_SEC, mount_rate_limit_burst);
+ r = sd_event_source_set_ratelimit(m->mount_event_source, mount_rate_limit_interval, mount_rate_limit_burst);
if (r < 0) {
log_error_errno(r, "Failed to enable rate limit for mount events: %m");
goto fail;

View File

@ -0,0 +1,28 @@
From de696eb8fc5caf5d5ad0a314fa21f8ca78bf8071 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Tue, 9 May 2023 00:21:20 +0900
Subject: [PATCH] core/service: fix error cause in the log
Fixes a bug caused by a5648b809457d120500b2acb18b31e2168a4817a.
Fixes #27575.
(cherry picked from commit f86a388de339bc9fd3bc90df7de0d9693b52369f)
Resolves: RHEL-138414
---
src/core/service.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/core/service.c b/src/core/service.c
index 305f3b7170..9c938aee91 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -989,7 +989,7 @@ static int service_load_pid_file(Service *s, bool may_warn) {
r = chase_symlinks(s->pid_file, NULL, 0, NULL, &fd);
}
if (r < 0)
- return log_unit_full_errno(UNIT(s), prio, fd,
+ return log_unit_full_errno(UNIT(s), prio, r,
"Can't open PID file %s (yet?) after %s: %m", s->pid_file, service_state_to_string(s->state));
/* Let's read the PID file now that we chased it down. But we need to convert the O_PATH fd

View File

@ -0,0 +1,45 @@
From 7640cebb70cc13ada4f0b6e3e26b7973be6d1b23 Mon Sep 17 00:00:00 2001
From: Mike Yuan <me@yhndnzj.com>
Date: Fri, 26 Jan 2024 00:47:23 +0800
Subject: [PATCH] fstab-generator: drop assertions for mount opts
fstab_filter_options accepts NULL and (with later changes)
might even return NULL.
(cherry picked from commit c521ce42b43ad542a8e3c6e5e83ceb653ca6a71e)
Related: RHEL-92752
---
src/fstab-generator/fstab-generator.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c
index b9606a5341..fe0283b4e7 100644
--- a/src/fstab-generator/fstab-generator.c
+++ b/src/fstab-generator/fstab-generator.c
@@ -486,7 +486,6 @@ static int add_mount(
assert(what);
assert(where);
- assert(opts);
assert(target_unit);
assert(source);
@@ -797,6 +796,9 @@ static int add_sysusr_sysroot_usr_bind_mount(const char *source) {
static MountPointFlags fstab_options_to_flags(const char *options, bool is_swap) {
MountPointFlags flags = 0;
+ if (isempty(options))
+ return 0;
+
if (fstab_test_option(options, "x-systemd.makefs\0"))
flags |= MOUNT_MAKEFS;
if (fstab_test_option(options, "x-systemd.growfs\0"))
@@ -872,7 +874,6 @@ static int parse_fstab_one(
assert(what_original);
assert(fstype);
- assert(options);
if (prefix_sysroot && !mount_in_initrd(where_original, options, accept_root))
return 0;

View File

@ -0,0 +1,114 @@
From 0a4f0be757c73e3320d1c611de9845f7713b10d0 Mon Sep 17 00:00:00 2001
From: Jules Lamur <contact@juleslamur.fr>
Date: Mon, 7 Apr 2025 18:49:26 +0200
Subject: [PATCH] fstab-generator: fix options in systemd.mount-extra= arg
Fixes a bug introduced by 55365b0a233ae3024411fd0815ad930e20f6a3d6 (v254).
The arguments `(rd.)systemd.mount-extra` take a value that looks like
`WHAT:WHERE[:FSTYPE[:OPTIONS]]`. The `OPTIONS` were parsed into a nulstr
where a comma-separated c-string was expected. This leads to a bug where
only the first option was taken into account by the generator.
For example, if you passed `systemd.mount-extra=/x:/y:baz:ro,defaults`
to the kernel, `systemd-fstab-generator` would translate that into a
nulstr: `ro\0defaults\0`.
Since methods processing options in the generator expected a
comma-separated c-string, they would only see the first option, `ro` in
this case.
(cherry picked from commit 06fadc4286fee6a7505a88659e5ae2e6f3ee60ba)
Resolves: RHEL-92752
---
src/fstab-generator/fstab-generator.c | 21 ++++---------------
.../hoge-withx20space.mount | 2 +-
.../dev-sdy3.swap | 2 +-
.../dev-sdy3.swap | 0
4 files changed, 6 insertions(+), 19 deletions(-)
rename test/test-fstab-generator/test-20-swap-from-cmdline.expected/{swap.target.requires => swap.target.wants}/dev-sdy3.swap (100%)
diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c
index fe0283b4e7..28677a2f39 100644
--- a/src/fstab-generator/fstab-generator.c
+++ b/src/fstab-generator/fstab-generator.c
@@ -105,15 +105,15 @@ static int mount_array_add_internal(
char *in_what,
char *in_where,
const char *in_fstype,
- const char *in_options) {
+ char *in_options) {
_cleanup_free_ char *what = NULL, *where = NULL, *fstype = NULL, *options = NULL;
- int r;
/* This takes what and where. */
what = ASSERT_PTR(in_what);
where = in_where;
+ options = in_options;
fstype = strdup(isempty(in_fstype) ? "auto" : in_fstype);
if (!fstype)
@@ -122,19 +122,6 @@ static int mount_array_add_internal(
if (streq(fstype, "swap"))
where = mfree(where);
- if (!isempty(in_options)) {
- _cleanup_strv_free_ char **options_strv = NULL;
-
- r = strv_split_full(&options_strv, in_options, ",", 0);
- if (r < 0)
- return r;
-
- r = strv_make_nulstr(options_strv, &options, NULL);
- } else
- r = strv_make_nulstr(STRV_MAKE("defaults"), &options, NULL);
- if (r < 0)
- return r;
-
if (!GREEDY_REALLOC(arg_mounts, arg_n_mounts + 1))
return -ENOMEM;
@@ -164,7 +151,7 @@ static int mount_array_add(bool for_initrd, const char *str) {
if (!isempty(str))
return -EINVAL;
- return mount_array_add_internal(for_initrd, TAKE_PTR(what), TAKE_PTR(where), fstype, options);
+ return mount_array_add_internal(for_initrd, TAKE_PTR(what), TAKE_PTR(where), fstype, TAKE_PTR(options));
}
static int mount_array_add_swap(bool for_initrd, const char *str) {
@@ -182,7 +169,7 @@ static int mount_array_add_swap(bool for_initrd, const char *str) {
if (!isempty(str))
return -EINVAL;
- return mount_array_add_internal(for_initrd, TAKE_PTR(what), NULL, "swap", options);
+ return mount_array_add_internal(for_initrd, TAKE_PTR(what), NULL, "swap", TAKE_PTR(options));
}
static int write_options(FILE *f, const char *options) {
diff --git a/test/test-fstab-generator/test-19-mounts-from-cmdline.expected/hoge-withx20space.mount b/test/test-fstab-generator/test-19-mounts-from-cmdline.expected/hoge-withx20space.mount
index e9ffb4bbd9..d3797c9706 100644
--- a/test/test-fstab-generator/test-19-mounts-from-cmdline.expected/hoge-withx20space.mount
+++ b/test/test-fstab-generator/test-19-mounts-from-cmdline.expected/hoge-withx20space.mount
@@ -9,4 +9,4 @@ Before=remote-fs.target
What=//foo￾bar
Where=/hoge/with space
Type=cifs
-Options=rw
+Options=rw,seclabel
diff --git a/test/test-fstab-generator/test-20-swap-from-cmdline.expected/dev-sdy3.swap b/test/test-fstab-generator/test-20-swap-from-cmdline.expected/dev-sdy3.swap
index 3b6563d216..1b4b53c9b8 100644
--- a/test/test-fstab-generator/test-20-swap-from-cmdline.expected/dev-sdy3.swap
+++ b/test/test-fstab-generator/test-20-swap-from-cmdline.expected/dev-sdy3.swap
@@ -7,4 +7,4 @@ After=blockdev@dev-sdy3.target
[Swap]
What=/dev/sdy3
-Options=x-systemd.makefs
+Options=x-systemd.makefs,nofail
diff --git a/test/test-fstab-generator/test-20-swap-from-cmdline.expected/swap.target.requires/dev-sdy3.swap b/test/test-fstab-generator/test-20-swap-from-cmdline.expected/swap.target.wants/dev-sdy3.swap
similarity index 100%
rename from test/test-fstab-generator/test-20-swap-from-cmdline.expected/swap.target.requires/dev-sdy3.swap
rename to test/test-fstab-generator/test-20-swap-from-cmdline.expected/swap.target.wants/dev-sdy3.swap

View File

@ -0,0 +1,57 @@
From 88dfaa167328461ac18e8e764c97e19632b34161 Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <frantisek@sumsal.cz>
Date: Thu, 29 Jun 2023 13:31:19 +0200
Subject: [PATCH] core: reorder systemd arguments on reexec
When reexecuting system let's put our arguments carrying deserialization
info first followed by any existing arguments to make sure they get
parsed in case we get weird stuff from the kernel cmdline (like --).
See: https://github.com/systemd/systemd/issues/28184
(cherry picked from commit 06afda6b38d5d730fca3c65449096425933272bc)
Resolves: RHEL-111135
---
src/core/main.c | 6 +++++-
test/TEST-01-BASIC/test.sh | 5 +++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/core/main.c b/src/core/main.c
index f230270340..2eba3a3c50 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -1814,13 +1814,17 @@ static int do_reexecute(
xsprintf(sfd, "%i", fileno(arg_serialization));
i = 1; /* Leave args[0] empty for now. */
- filter_args(args, &i, argv, argc);
+ /* Put our stuff first to make sure it always gets parsed in case
+ * we get weird stuff from the kernel cmdline (like --) */
if (switch_root_dir)
args[i++] = "--switched-root";
args[i++] = arg_system ? "--system" : "--user";
args[i++] = "--deserialize";
args[i++] = sfd;
+
+ filter_args(args, &i, argv, argc);
+
args[i++] = NULL;
assert(i <= args_size);
diff --git a/test/TEST-01-BASIC/test.sh b/test/TEST-01-BASIC/test.sh
index cc6d0651c1..d0e714ac30 100755
--- a/test/TEST-01-BASIC/test.sh
+++ b/test/TEST-01-BASIC/test.sh
@@ -8,6 +8,11 @@ RUN_IN_UNPRIVILEGED_CONTAINER=${RUN_IN_UNPRIVILEGED_CONTAINER:-yes}
TEST_REQUIRE_INSTALL_TESTS=0
TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED=0
+# Check if we can correctly deserialize if the kernel cmdline contains "weird" stuff
+# like an invalid argument, "end of arguments" separator, or a sysvinit argument (-z)
+# See: https://github.com/systemd/systemd/issues/28184
+KERNEL_APPEND="foo -- -z bar --- baz $KERNEL_APPEND"
+
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,68 @@
From 2728e6821ab6f5c0c5316a367bb1aa626b036779 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Thu, 9 Mar 2023 17:41:25 +0100
Subject: [PATCH] runtime-scope: add helper that turns RuntimeScope enum into
--system/--user string
(cherry picked from commit 40d73340faabb6073602ba3ff41896f3478a2cbf)
Related: RHEL-137252
---
src/basic/runtime-scope.c | 8 ++++++++
src/basic/runtime-scope.h | 2 ++
src/core/main.c | 2 +-
src/systemctl/systemctl-start-special.c | 2 +-
4 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/basic/runtime-scope.c b/src/basic/runtime-scope.c
index 88afb53d0b..3d653d6cef 100644
--- a/src/basic/runtime-scope.c
+++ b/src/basic/runtime-scope.c
@@ -10,3 +10,11 @@ static const char* const runtime_scope_table[_RUNTIME_SCOPE_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP(runtime_scope, RuntimeScope);
+
+static const char* const runtime_scope_cmdline_option_table[_RUNTIME_SCOPE_MAX] = {
+ [RUNTIME_SCOPE_SYSTEM] = "--system",
+ [RUNTIME_SCOPE_USER] = "--user",
+ [RUNTIME_SCOPE_GLOBAL] = "--global",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(runtime_scope_cmdline_option, RuntimeScope);
diff --git a/src/basic/runtime-scope.h b/src/basic/runtime-scope.h
index 6a7f9e65d4..6553e4c199 100644
--- a/src/basic/runtime-scope.h
+++ b/src/basic/runtime-scope.h
@@ -15,3 +15,5 @@ typedef enum RuntimeScope {
const char *runtime_scope_to_string(RuntimeScope scope) _const_;
RuntimeScope runtime_scope_from_string(const char *s) _const_;
+
+const char *runtime_scope_cmdline_option_to_string(RuntimeScope scope) _const_;
diff --git a/src/core/main.c b/src/core/main.c
index 3ef613a8b1..18f5781126 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -1821,7 +1821,7 @@ static int do_reexecute(
* we get weird stuff from the kernel cmdline (like --) */
if (switch_root_dir)
args[i++] = "--switched-root";
- args[i++] = arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "--system" : "--user";
+ args[i++] = runtime_scope_cmdline_option_to_string(arg_runtime_scope);
args[i++] = "--deserialize";
args[i++] = sfd;
diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c
index 503d69f2a0..8373dabe15 100644
--- a/src/systemctl/systemctl-start-special.c
+++ b/src/systemctl/systemctl-start-special.c
@@ -260,7 +260,7 @@ int verb_start_system_special(int argc, char *argv[], void *userdata) {
if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Bad action for %s mode.",
- arg_runtime_scope == RUNTIME_SCOPE_GLOBAL ? "--global" : "--user");
+ runtime_scope_cmdline_option_to_string(arg_runtime_scope));
return verb_start_special(argc, argv, userdata);
}

View File

@ -0,0 +1,79 @@
From 3be3354126953a51625015b43ab5abc11315cd40 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Mon, 26 Jun 2023 18:55:14 +0200
Subject: [PATCH] sd-path: add support for XDG_STATE_HOME
(cherry picked from commit 9a653235d12a795a8bd6adf6289ea735ccae71af)
Related: RHEL-137252
---
man/sd_path_lookup.xml | 1 +
src/libsystemd/sd-path/sd-path.c | 3 +++
src/path/path.c | 2 ++
src/systemd/sd-path.h | 5 ++++-
4 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/man/sd_path_lookup.xml b/man/sd_path_lookup.xml
index 01fb1ed8f1..c2ea6469a1 100644
--- a/man/sd_path_lookup.xml
+++ b/man/sd_path_lookup.xml
@@ -55,6 +55,7 @@
<constant>SD_PATH_USER_CONFIGURATION</constant>,
<constant>SD_PATH_USER_RUNTIME</constant>,
+ <constant>SD_PATH_USER_STATE_PRIVATE</constant>,
<constant>SD_PATH_USER_STATE_CACHE</constant>,
<constant>SD_PATH_USER</constant>,
diff --git a/src/libsystemd/sd-path/sd-path.c b/src/libsystemd/sd-path/sd-path.c
index 2c8181fbfb..73a51aebc2 100644
--- a/src/libsystemd/sd-path/sd-path.c
+++ b/src/libsystemd/sd-path/sd-path.c
@@ -281,6 +281,9 @@ static int get_path(uint64_t type, char **buffer, const char **ret) {
case SD_PATH_USER_STATE_CACHE:
return from_home_dir("XDG_CACHE_HOME", ".cache", buffer, ret);
+ case SD_PATH_USER_STATE_PRIVATE:
+ return from_home_dir("XDG_STATE_HOME", ".local/state", buffer, ret);
+
case SD_PATH_USER:
r = get_home_dir(buffer);
if (r < 0)
diff --git a/src/path/path.c b/src/path/path.c
index 0024a60611..9d9b24d5e2 100644
--- a/src/path/path.c
+++ b/src/path/path.c
@@ -41,6 +41,8 @@ static const char* const path_table[_SD_PATH_MAX] = {
[SD_PATH_USER_CONFIGURATION] = "user-configuration",
[SD_PATH_USER_RUNTIME] = "user-runtime",
[SD_PATH_USER_STATE_CACHE] = "user-state-cache",
+ [SD_PATH_USER_STATE_PRIVATE] = "user-state-private",
+
[SD_PATH_USER] = "user",
[SD_PATH_USER_DOCUMENTS] = "user-documents",
[SD_PATH_USER_MUSIC] = "user-music",
diff --git a/src/systemd/sd-path.h b/src/systemd/sd-path.h
index 161a8e0c8d..0c04e7c22e 100644
--- a/src/systemd/sd-path.h
+++ b/src/systemd/sd-path.h
@@ -53,9 +53,10 @@ enum {
SD_PATH_USER_SHARED,
/* User configuration, state, runtime ... */
- SD_PATH_USER_CONFIGURATION, /* takes both actual configuration (like /etc) and state (like /var/lib) */
+ SD_PATH_USER_CONFIGURATION,
SD_PATH_USER_RUNTIME,
SD_PATH_USER_STATE_CACHE,
+ /* → SD_PATH_USER_STATE_PRIVATE is added at the bottom */
/* User resources */
SD_PATH_USER, /* $HOME itself */
@@ -110,6 +111,8 @@ enum {
/* systemd-networkd search paths */
SD_PATH_SYSTEMD_SEARCH_NETWORK,
+ SD_PATH_USER_STATE_PRIVATE,
+
_SD_PATH_MAX
};

Some files were not shown because too many files have changed in this diff Show More