From f64d331351e33199c4096b2ae4a4b9d24d127661 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Jan 2023 16:49:23 +0100 Subject: [PATCH] pid1: add new Type=notify-reload service type Fixes: #6162 (cherry picked from commit 3bd28bf721dc70722ff1c675026ed0b44ad968a3) Resolves: RHEL-6090 --- man/org.freedesktop.systemd1.xml | 6 + src/basic/unit-def.c | 2 + src/basic/unit-def.h | 4 +- src/core/dbus-service.c | 5 + src/core/load-fragment-gperf.gperf.in | 1 + src/core/service.c | 226 ++++++++++++++++++-------- src/core/service.h | 18 +- src/shared/bus-unit-util.c | 3 +- 8 files changed, 189 insertions(+), 76 deletions(-) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 13a84af747..c18428a092 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -2570,6 +2570,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { readonly u NRestarts = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s OOMPolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly i ReloadSignal = ...; readonly t ExecMainStartTimestamp = ...; readonly t ExecMainStartTimestampMonotonic = ...; readonly t ExecMainExitTimestamp = ...; @@ -3163,6 +3165,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -3715,6 +3719,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index 94cd603e32..bdb1860246 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -188,6 +188,8 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = { [SERVICE_RUNNING] = "running", [SERVICE_EXITED] = "exited", [SERVICE_RELOAD] = "reload", + [SERVICE_RELOAD_SIGNAL] = "reload-signal", + [SERVICE_RELOAD_NOTIFY] = "reload-notify", [SERVICE_STOP] = "stop", [SERVICE_STOP_WATCHDOG] = "stop-watchdog", [SERVICE_STOP_SIGTERM] = "stop-sigterm", diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index 5fcd51c095..bae132ea09 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -132,7 +132,9 @@ typedef enum ServiceState { SERVICE_START_POST, SERVICE_RUNNING, SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */ - SERVICE_RELOAD, + SERVICE_RELOAD, /* Reloading via ExecReload= */ + SERVICE_RELOAD_SIGNAL, /* Reloading via SIGHUP requested */ + SERVICE_RELOAD_NOTIFY, /* Waiting for READY=1 after RELOADING=1 notify */ SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */ SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index 6e4bc0bd1a..3d130db66a 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -228,6 +228,7 @@ const sd_bus_vtable bus_service_vtable[] = { SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("NRestarts", "u", bus_property_get_unsigned, offsetof(Service, n_restarts), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("OOMPolicy", "s", bus_property_get_oom_policy, offsetof(Service, oom_policy), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReloadSignal", "i", bus_property_get_int, offsetof(Service, reload_signal), SD_BUS_VTABLE_PROPERTY_CONST), BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), BUS_EXEC_COMMAND_LIST_VTABLE("ExecCondition", offsetof(Service, exec_command[SERVICE_EXEC_CONDITION]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), @@ -374,6 +375,7 @@ static BUS_DEFINE_SET_TRANSIENT_PARSE(service_restart, ServiceRestart, service_r static BUS_DEFINE_SET_TRANSIENT_PARSE(oom_policy, OOMPolicy, oom_policy_from_string); static BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(bus_name, sd_bus_service_name_is_valid); static BUS_DEFINE_SET_TRANSIENT_PARSE(timeout_failure_mode, ServiceTimeoutFailureMode, service_timeout_failure_mode_from_string); +static BUS_DEFINE_SET_TRANSIENT_TO_STRING(reload_signal, "i", int32_t, int, "%" PRIi32, signal_to_string_with_check); static int bus_service_set_transient_property( Service *s, @@ -532,6 +534,9 @@ static int bus_service_set_transient_property( if (streq(name, "StandardErrorFileDescriptor")) return bus_set_transient_std_fd(u, name, &s->stderr_fd, &s->exec_context.stdio_as_fds, message, flags, error); + if (streq(name, "ReloadSignal")) + return bus_set_transient_reload_signal(u, name, &s->reload_signal, message, flags, error); + return 0; } diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 81a5971339..53089d5590 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -424,6 +424,7 @@ Service.BusPolicy, config_parse_warn_compat, Service.USBFunctionDescriptors, config_parse_unit_path_printf, 0, offsetof(Service, usb_function_descriptors) Service.USBFunctionStrings, config_parse_unit_path_printf, 0, offsetof(Service, usb_function_strings) Service.OOMPolicy, config_parse_oom_policy, 0, offsetof(Service, oom_policy) +Service.ReloadSignal, config_parse_signal, 0, offsetof(Service, reload_signal) {{ EXEC_CONTEXT_CONFIG_ITEMS('Service') }} {{ CGROUP_CONTEXT_CONFIG_ITEMS('Service') }} {{ KILL_CONTEXT_CONFIG_ITEMS('Service') }} diff --git a/src/core/service.c b/src/core/service.c index aa76b4ad9a..902948905f 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -54,6 +54,8 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { [SERVICE_RUNNING] = UNIT_ACTIVE, [SERVICE_EXITED] = UNIT_ACTIVE, [SERVICE_RELOAD] = UNIT_RELOADING, + [SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING, + [SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING, [SERVICE_STOP] = UNIT_DEACTIVATING, [SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING, [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, @@ -78,6 +80,8 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = [SERVICE_RUNNING] = UNIT_ACTIVE, [SERVICE_EXITED] = UNIT_ACTIVE, [SERVICE_RELOAD] = UNIT_RELOADING, + [SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING, + [SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING, [SERVICE_STOP] = UNIT_DEACTIVATING, [SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING, [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, @@ -124,6 +128,8 @@ static void service_init(Unit *u) { s->watchdog_original_usec = USEC_INFINITY; s->oom_policy = _OOM_POLICY_INVALID; + s->reload_begin_usec = USEC_INFINITY; + s->reload_signal = SIGHUP; } static void service_unwatch_control_pid(Service *s) { @@ -765,7 +771,7 @@ static int service_add_extras(Service *s) { /* If the service needs the notify socket, let's enable it automatically. */ if (s->notify_access == NOTIFY_NONE && - (s->type == SERVICE_NOTIFY || s->watchdog_usec > 0 || s->n_fd_store_max > 0)) + (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) || s->watchdog_usec > 0 || s->n_fd_store_max > 0)) s->notify_access = NOTIFY_MAIN; /* If no OOM policy was explicitly set, then default to the configure default OOM policy. Except when @@ -830,7 +836,8 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { "%sRestart: %s\n" "%sNotifyAccess: %s\n" "%sNotifyState: %s\n" - "%sOOMPolicy: %s\n", + "%sOOMPolicy: %s\n" + "%sReloadSignal: %s\n", prefix, service_state_to_string(s->state), prefix, service_result_to_string(s->result), prefix, service_result_to_string(s->reload_result), @@ -843,7 +850,8 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { prefix, service_restart_to_string(s->restart), prefix, notify_access_to_string(s->notify_access), prefix, notify_state_to_string(s->notify_state), - prefix, oom_policy_to_string(s->oom_policy)); + prefix, oom_policy_to_string(s->oom_policy), + prefix, signal_to_string(s->reload_signal)); if (s->control_pid > 0) fprintf(f, @@ -1088,7 +1096,7 @@ static void service_set_state(Service *s, ServiceState state) { if (!IN_SET(state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, - SERVICE_RELOAD, + SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, SERVICE_AUTO_RESTART, @@ -1097,7 +1105,8 @@ static void service_set_state(Service *s, ServiceState state) { if (!IN_SET(state, SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, SERVICE_RELOAD, + SERVICE_RUNNING, + SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { service_unwatch_main_pid(s); @@ -1106,7 +1115,7 @@ static void service_set_state(Service *s, ServiceState state) { if (!IN_SET(state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RELOAD, + SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, SERVICE_CLEANING)) { @@ -1122,7 +1131,8 @@ static void service_set_state(Service *s, ServiceState state) { if (!IN_SET(state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, SERVICE_RELOAD, + SERVICE_RUNNING, + SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) && !(state == SERVICE_DEAD && UNIT(s)->job)) @@ -1131,7 +1141,7 @@ static void service_set_state(Service *s, ServiceState state) { if (state != SERVICE_START) s->exec_fd_event_source = sd_event_source_disable_unref(s->exec_fd_event_source); - if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) + if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY)) service_stop_watchdog(s); /* For the inactive states unit_notify() will trim the cgroup, @@ -1157,6 +1167,8 @@ static usec_t service_coldplug_timeout(Service *s) { case SERVICE_START: case SERVICE_START_POST: case SERVICE_RELOAD: + case SERVICE_RELOAD_SIGNAL: + case SERVICE_RELOAD_NOTIFY: return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_start_usec); case SERVICE_RUNNING: @@ -1203,7 +1215,8 @@ static int service_coldplug(Unit *u) { pid_is_unwaited(s->main_pid) && (IN_SET(s->deserialized_state, SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, SERVICE_RELOAD, + SERVICE_RUNNING, + SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))) { r = unit_watch_pid(UNIT(s), s->main_pid, false); @@ -1215,7 +1228,7 @@ static int service_coldplug(Unit *u) { pid_is_unwaited(s->control_pid) && IN_SET(s->deserialized_state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RELOAD, + SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, SERVICE_CLEANING)) { @@ -1230,7 +1243,7 @@ static int service_coldplug(Unit *u) { (void) unit_setup_exec_runtime(u); } - if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) + if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY)) service_start_watchdog(s); if (UNIT_ISSET(s->accept_socket)) { @@ -2255,7 +2268,7 @@ static void service_enter_start(Service *s) { s->control_pid = pid; service_set_state(s, SERVICE_START); - } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY, SERVICE_EXEC)) { + } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD, SERVICE_EXEC)) { /* For oneshot services we wait until the start process exited, too, but it is our main process. */ @@ -2399,7 +2412,7 @@ static void service_enter_reload_by_notify(Service *s) { assert(s); service_arm_timer(s, /* relative= */ true, s->timeout_start_usec); - service_set_state(s, SERVICE_RELOAD); + service_set_state(s, SERVICE_RELOAD_NOTIFY); /* service_enter_reload_by_notify is never called during a reload, thus no loops are possible. */ r = manager_propagate_reload(UNIT(s)->manager, UNIT(s), JOB_FAIL, &error); @@ -2408,6 +2421,7 @@ static void service_enter_reload_by_notify(Service *s) { } static void service_enter_reload(Service *s) { + bool killed = false; int r; assert(s); @@ -2415,6 +2429,18 @@ static void service_enter_reload(Service *s) { service_unwatch_control_pid(s); s->reload_result = SERVICE_SUCCESS; + usec_t ts = now(CLOCK_MONOTONIC); + + if (s->type == SERVICE_NOTIFY_RELOAD && s->main_pid > 0) { + r = kill_and_sigcont(s->main_pid, s->reload_signal); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to send reload signal: %m"); + goto fail; + } + + killed = true; + } + s->control_command = s->exec_command[SERVICE_EXEC_RELOAD]; if (s->control_command) { s->control_command_id = SERVICE_EXEC_RELOAD; @@ -2424,17 +2450,28 @@ static void service_enter_reload(Service *s) { s->timeout_start_usec, EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_CONTROL_CGROUP, &s->control_pid); - if (r < 0) + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to run 'reload' task: %m"); goto fail; + } service_set_state(s, SERVICE_RELOAD); - } else + } else if (killed) { + service_arm_timer(s, /* relative= */ true, s->timeout_start_usec); + service_set_state(s, SERVICE_RELOAD_SIGNAL); + } else { service_enter_running(s, SERVICE_SUCCESS); + return; + } + /* Store the timestamp when we started reloading: when reloading via SIGHUP we won't leave the reload + * state until we received both RELOADING=1 and READY=1 with MONOTONIC_USEC= set to a value above + * this. Thus we know for sure the reload cycle was executed *after* we requested it, and is not one + * that was already in progress before. */ + s->reload_begin_usec = ts; return; fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'reload' task: %m"); s->reload_result = SERVICE_FAILURE_RESOURCES; service_enter_running(s, SERVICE_SUCCESS); } @@ -2597,9 +2634,8 @@ static int service_stop(Unit *u) { return 0; } - /* If there's already something running we go directly into - * kill mode. */ - if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_STOP_WATCHDOG)) { + /* If there's already something running we go directly into kill mode. */ + if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP_WATCHDOG)) { service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); return 0; } @@ -2632,7 +2668,8 @@ _pure_ static bool service_can_reload(Unit *u) { assert(s); - return !!s->exec_command[SERVICE_EXEC_RELOAD]; + return s->exec_command[SERVICE_EXEC_RELOAD] || + s->type == SERVICE_NOTIFY_RELOAD; } static unsigned service_exec_command_index(Unit *u, ServiceExecCommand id, ExecCommand *current) { @@ -2808,6 +2845,9 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { if (s->watchdog_original_usec != USEC_INFINITY) (void) serialize_item_format(f, "watchdog-original-usec", USEC_FMT, s->watchdog_original_usec); + if (s->reload_begin_usec != USEC_INFINITY) + (void) serialize_item_format(f, "reload-begin-usec", USEC_FMT, s->reload_begin_usec); + return 0; } @@ -3146,6 +3186,10 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, log_unit_debug_errno(u, r, "Failed to parse serialized flush restart counter setting '%s': %m", value); else s->flush_n_restarts = r; + } else if (streq(key, "reload-begin-usec")) { + r = deserialize_usec(value, &s->reload_begin_usec); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to parse serialized reload begin timestamp '%s', ignoring: %m", value); } else log_unit_debug(u, "Unknown serialization key: %s", key); @@ -3349,7 +3393,7 @@ static void service_notify_cgroup_empty_event(Unit *u) { * SIGCHLD for. */ case SERVICE_START: - if (s->type == SERVICE_NOTIFY && + if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) && main_pid_good(s) == 0 && control_pid_good(s) == 0) { /* No chance of getting a ready notification anymore */ @@ -3553,17 +3597,19 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { } else { s->main_command = NULL; - /* Services with ExitType=cgroup do not act on main PID exiting, - * unless the cgroup is already empty */ + /* Services with ExitType=cgroup do not act on main PID exiting, unless the cgroup is + * already empty */ if (s->exit_type == SERVICE_EXIT_MAIN || cgroup_good(s) <= 0) { /* The service exited, so the service is officially gone. */ switch (s->state) { case SERVICE_START_POST: case SERVICE_RELOAD: - /* If neither main nor control processes are running then - * the current state can never exit cleanly, hence immediately - * terminate the service. */ + case SERVICE_RELOAD_SIGNAL: + case SERVICE_RELOAD_NOTIFY: + /* If neither main nor control processes are running then the current + * state can never exit cleanly, hence immediately terminate the + * service. */ if (control_pid_good(s) <= 0) service_enter_stop(s, f); @@ -3582,7 +3628,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { else service_enter_signal(s, SERVICE_STOP_SIGTERM, f); break; - } else if (s->type == SERVICE_NOTIFY) { + } else if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD)) { /* Only enter running through a notification, so that the * SERVICE_START state signifies that no ready notification * has been received */ @@ -3675,15 +3721,13 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { s->control_command->command_next && f == SERVICE_SUCCESS) { - /* There is another command to * - * execute, so let's do that. */ + /* There is another command to * execute, so let's do that. */ log_unit_debug(u, "Running next control command for state %s.", service_state_to_string(s->state)); service_run_next_control(s); } else { - /* No further commands for this step, so let's - * figure out what to do next */ + /* No further commands for this step, so let's figure out what to do next */ s->control_command = NULL; s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; @@ -3761,12 +3805,22 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { break; case SERVICE_RELOAD: + case SERVICE_RELOAD_SIGNAL: + case SERVICE_RELOAD_NOTIFY: if (f == SERVICE_SUCCESS) if (service_load_pid_file(s, true) < 0) service_search_main_pid(s); s->reload_result = f; - service_enter_running(s, SERVICE_SUCCESS); + + /* If the last notification we received from the service process indiciates + * we are still reloading, then don't leave reloading state just yet, just + * transition into SERVICE_RELOAD_NOTIFY, to wait for the READY=1 coming, + * too. */ + if (s->notify_state == NOTIFY_RELOADING) + service_set_state(s, SERVICE_RELOAD_NOTIFY); + else + service_enter_running(s, SERVICE_SUCCESS); break; case SERVICE_STOP: @@ -3869,6 +3923,8 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us break; case SERVICE_RELOAD: + case SERVICE_RELOAD_SIGNAL: + case SERVICE_RELOAD_NOTIFY: log_unit_warning(UNIT(s), "Reload operation timed out. Killing reload process."); service_kill_control_process(s); s->reload_result = SERVICE_FAILURE_TIMEOUT; @@ -4094,6 +4150,7 @@ static void service_notify_message( Service *s = SERVICE(u); bool notify_dbus = false; + usec_t monotonic_usec = USEC_INFINITY; const char *e; int r; @@ -4112,7 +4169,7 @@ static void service_notify_message( /* Interpret MAINPID= */ e = strv_find_startswith(tags, "MAINPID="); - if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) { + if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY)) { pid_t new_main_pid; if (parse_pid(e, &new_main_pid) < 0) @@ -4141,43 +4198,73 @@ static void service_notify_message( } } - /* Interpret READY=/STOPPING=/RELOADING=. Last one wins. */ - STRV_FOREACH_BACKWARDS(i, tags) { + /* Parse MONOTONIC_USEC= */ + e = strv_find_startswith(tags, "MONOTONIC_USEC="); + if (e) { + r = safe_atou64(e, &monotonic_usec); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to parse MONOTONIC_USEC= field in notification message, ignoring: %s", e); + } - if (streq(*i, "READY=1")) { - s->notify_state = NOTIFY_READY; + /* Interpret READY=/STOPPING=/RELOADING=. STOPPING= wins over the others, and READY= over RELOADING= */ + if (strv_contains(tags, "STOPPING=1")) { + s->notify_state = NOTIFY_STOPPING; - /* Type=notify services inform us about completed - * initialization with READY=1 */ - if (s->type == SERVICE_NOTIFY && s->state == SERVICE_START) - service_enter_start_post(s); + if (IN_SET(s->state, SERVICE_RUNNING, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY)) + service_enter_stop_by_notify(s); - /* Sending READY=1 while we are reloading informs us - * that the reloading is complete */ - if (s->state == SERVICE_RELOAD && s->control_pid == 0) - service_enter_running(s, SERVICE_SUCCESS); + notify_dbus = true; - notify_dbus = true; - break; + } else if (strv_contains(tags, "READY=1")) { - } else if (streq(*i, "RELOADING=1")) { - s->notify_state = NOTIFY_RELOADING; + s->notify_state = NOTIFY_READY; - if (s->state == SERVICE_RUNNING) - service_enter_reload_by_notify(s); + /* Type=notify services inform us about completed initialization with READY=1 */ + if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) && + s->state == SERVICE_START) + service_enter_start_post(s); - notify_dbus = true; - break; + /* Sending READY=1 while we are reloading informs us that the reloading is complete. */ + if (s->state == SERVICE_RELOAD_NOTIFY) + service_enter_running(s, SERVICE_SUCCESS); - } else if (streq(*i, "STOPPING=1")) { - s->notify_state = NOTIFY_STOPPING; + /* Combined RELOADING=1 and READY=1? Then this is indication that the service started and + * immediately finished reloading. */ + if (s->state == SERVICE_RELOAD_SIGNAL && + strv_contains(tags, "RELOADING=1") && + monotonic_usec != USEC_INFINITY && + monotonic_usec >= s->reload_begin_usec) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - if (s->state == SERVICE_RUNNING) - service_enter_stop_by_notify(s); + /* Propagate a reload explicitly */ + r = manager_propagate_reload(UNIT(s)->manager, UNIT(s), JOB_FAIL, &error); + if (r < 0) + log_unit_warning(UNIT(s), "Failed to schedule propagation of reload, ignoring: %s", bus_error_message(&error, r)); - notify_dbus = true; - break; + service_enter_running(s, SERVICE_SUCCESS); } + + notify_dbus = true; + + } else if (strv_contains(tags, "RELOADING=1")) { + + s->notify_state = NOTIFY_RELOADING; + + /* Sending RELOADING=1 after we send SIGHUP to request a reload will transition + * things to "reload-notify" state, where we'll wait for READY=1 to let us know the + * reload is done. Note that we insist on a timestamp being sent along here, so that + * we know for sure this is a reload cycle initiated *after* we sent the signal */ + if (s->state == SERVICE_RELOAD_SIGNAL && + monotonic_usec != USEC_INFINITY && + monotonic_usec >= s->reload_begin_usec) + /* Note, we don't call service_enter_reload_by_notify() here, because we + * don't need reload propagation nor do we want to restart the time-out. */ + service_set_state(s, SERVICE_RELOAD_NOTIFY); + + if (s->state == SERVICE_RUNNING) + service_enter_reload_by_notify(s); + + notify_dbus = true; } /* Interpret STATUS= */ @@ -4307,7 +4394,9 @@ static bool pick_up_pid_from_bus_name(Service *s) { SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, - SERVICE_RELOAD); + SERVICE_RELOAD, + SERVICE_RELOAD_SIGNAL, + SERVICE_RELOAD_NOTIFY); } static int bus_name_pid_lookup_callback(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { @@ -4514,6 +4603,8 @@ static bool service_needs_console(Unit *u) { SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, + SERVICE_RELOAD_SIGNAL, + SERVICE_RELOAD_NOTIFY, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, @@ -4636,13 +4727,14 @@ static const char* const service_restart_table[_SERVICE_RESTART_MAX] = { DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart); static const char* const service_type_table[_SERVICE_TYPE_MAX] = { - [SERVICE_SIMPLE] = "simple", - [SERVICE_FORKING] = "forking", - [SERVICE_ONESHOT] = "oneshot", - [SERVICE_DBUS] = "dbus", - [SERVICE_NOTIFY] = "notify", - [SERVICE_IDLE] = "idle", - [SERVICE_EXEC] = "exec", + [SERVICE_SIMPLE] = "simple", + [SERVICE_FORKING] = "forking", + [SERVICE_ONESHOT] = "oneshot", + [SERVICE_DBUS] = "dbus", + [SERVICE_NOTIFY] = "notify", + [SERVICE_NOTIFY_RELOAD] = "notify-reload", + [SERVICE_IDLE] = "idle", + [SERVICE_EXEC] = "exec", }; DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); diff --git a/src/core/service.h b/src/core/service.h index 91e02e6d7e..194067f0e1 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -24,13 +24,14 @@ typedef enum ServiceRestart { } ServiceRestart; typedef enum ServiceType { - SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */ - SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */ - SERVICE_ONESHOT, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */ - SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */ - SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */ - SERVICE_IDLE, /* much like simple, but delay exec() until all jobs are dispatched. */ - SERVICE_EXEC, /* we fork and wait until we execute exec() (this means our own setup is waited for) */ + SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */ + SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */ + SERVICE_ONESHOT, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */ + SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */ + SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */ + SERVICE_NOTIFY_RELOAD, /* just like SERVICE_NOTIFY, but also implements a reload protocol via SIGHUP */ + SERVICE_IDLE, /* much like simple, but delay exec() until all jobs are dispatched. */ + SERVICE_EXEC, /* we fork and wait until we execute exec() (this means our own setup is waited for) */ _SERVICE_TYPE_MAX, _SERVICE_TYPE_INVALID = -EINVAL, } ServiceType; @@ -215,6 +216,9 @@ struct Service { bool flush_n_restarts; OOMPolicy oom_policy; + + int reload_signal; + usec_t reload_begin_usec; }; static inline usec_t service_timeout_abort_usec(Service *s) { diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 922011eccd..a9844e1cc3 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2065,7 +2065,8 @@ static int bus_append_kill_property(sd_bus_message *m, const char *field, const if (STR_IN_SET(field, "KillSignal", "RestartKillSignal", "FinalKillSignal", - "WatchdogSignal")) + "WatchdogSignal", + "ReloadSignal")) return bus_append_signal_from_string(m, field, eq); return 0;