From ac08033068fa6d954ad73ea5860cf7f3100b72c7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 1 Nov 2026 11:21:36 +0900 Subject: [PATCH] systemctl-wait: do not fail for non-existent unit as non-root user Although the man page says > A unit that does not exist is treated like a unit that terminated > successfully, and a message about this is shown. systemctl wait fails as follows when invoked by a non-root user: ``` $ systemctl wait nonexistent.service Unit nonexistent.service has been removed while waiting for it. Failed to wait for unit nonexistent.service to terminate. $ echo $? 1 ``` This makes the unit be treated as non-existent when the initial GetAll() call returns LoadState=not-found. With this change, ``` $ systemctl wait nonexistent.service Unit nonexistent.service does not exist, treating as terminated. $ echo $? 0 ``` Follow-up for 0e51517319663b8d36b4ea254f1fdd18ce7d947c. --- src/shared/bus-wait-for-units.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/shared/bus-wait-for-units.c b/src/shared/bus-wait-for-units.c index 5f7fee5d00c..db301a60309 100644 --- a/src/shared/bus-wait-for-units.c +++ b/src/shared/bus-wait-for-units.c @@ -455,6 +455,10 @@ static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) assert(item); assert(m); + /* LoadState is a constant property, hence it is only contained in the reply to GetAll(), but never in + * PropertiesChanged signals. */ + bool had_load_state = item->load_state; + r = bus_message_map_all_properties(m, map, BUS_MAP_STRDUP, /* reterr_error= */ NULL, item); if (r < 0) return r; @@ -481,6 +485,28 @@ static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) item->removed = false; } + if (!had_load_state && streq_ptr(item->load_state, "not-found")) { + /* The unit did not exist from the beginning. Treat it as terminated successfully. */ + log_debug("Unit %s is not loaded.", item->name); + + /* No result is collected for a unit that does not exist. Hence, create a fake result that + * only contains the load state, and pass it to the callback, so that the caller can tell + * that the unit does not exist. */ + if (FLAGS_SET(item->flags, BUS_WAIT_COLLECT_RESULT)) { + r = free_and_strdup(&item->result.load_state, item->load_state); + if (r < 0) { + log_oom_debug(); + wait_for_item_fail(item); + return 0; + } + } + + BusWaitForUnits *d = ASSERT_PTR(item->parent); + call_unit_callback_and_wait(d, item, /* good= */ true); + bus_wait_for_units_check_ready(d); + return 0; + } + wait_for_item_check_ready(item); return 0; }