From 19dff6914dee94b36320dbfda94f60af30ac65c1 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 26 Jan 2023 17:44:03 +0800 Subject: [PATCH] core: support overriding NOTIFYACCESS= through sd-notify during runtime Closes #25963 --- man/org.freedesktop.systemd1.xml | 1 - man/sd_notify.xml | 10 +++++ src/core/dbus-service.c | 4 +- src/core/service.c | 67 +++++++++++++++++++++++++++----- src/core/service.h | 6 +++ src/systemd/sd-daemon.h | 4 ++ 6 files changed, 80 insertions(+), 12 deletions(-) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 141fde05b42..bc5f8ea388e 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -2560,7 +2560,6 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { readonly s Restart = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s PIDFile = '...'; - @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s NotifyAccess = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t RestartUSec = ...; diff --git a/man/sd_notify.xml b/man/sd_notify.xml index 8a091128657..cb07add95eb 100644 --- a/man/sd_notify.xml +++ b/man/sd_notify.xml @@ -154,6 +154,16 @@ system check… + + NOTIFYACCESS=… + + Reset the access to the service status notification + socket during runtime, overriding NotifyAccess= setting + in the service unit file. See systemd.service5 + for details, specifically NotifyAccess= for a list of + accepted values. + + ERRNO=… diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index 079cb9b92d2..24297b52a01 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -31,8 +31,8 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exit_type, service_exit_type, ServiceExitType); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, service_result, ServiceResult); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_notify_access, notify_access, NotifyAccess); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction); +static BUS_DEFINE_PROPERTY_GET2(property_get_notify_access, "s", Service, service_get_notify_access, notify_access_to_string); static BUS_DEFINE_PROPERTY_GET(property_get_timeout_abort_usec, "t", Service, service_timeout_abort_usec); static BUS_DEFINE_PROPERTY_GET(property_get_watchdog_usec, "t", Service, service_get_watchdog_usec); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_timeout_failure_mode, service_timeout_failure_mode, ServiceTimeoutFailureMode); @@ -223,7 +223,7 @@ const sd_bus_vtable bus_service_vtable[] = { SD_BUS_PROPERTY("ExitType", "s", property_get_exit_type, offsetof(Service, exit_type), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Restart", "s", property_get_restart, offsetof(Service, restart), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PIDFile", "s", NULL, offsetof(Service, pid_file), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, offsetof(Service, notify_access), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("RestartUSec", "t", bus_property_get_usec, offsetof(Service, restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/src/core/service.c b/src/core/service.c index addd4e66a73..f8f5d6b1ec8 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -125,6 +125,8 @@ static void service_init(Unit *u) { s->exec_context.keyring_mode = MANAGER_IS_SYSTEM(u->manager) ? EXEC_KEYRING_PRIVATE : EXEC_KEYRING_INHERIT; + s->notify_access_override = _NOTIFY_ACCESS_INVALID; + s->watchdog_original_usec = USEC_INFINITY; s->oom_policy = _OOM_POLICY_INVALID; @@ -202,6 +204,15 @@ void service_close_socket_fd(Service *s) { s->socket_peer = socket_peer_unref(s->socket_peer); } +static void service_override_notify_access(Service *s, NotifyAccess notify_access_override) { + assert(s); + + s->notify_access_override = notify_access_override; + + log_unit_debug(UNIT(s), "notify_access=%s", notify_access_to_string(s->notify_access)); + log_unit_debug(UNIT(s), "notify_access_override=%s", notify_access_to_string(s->notify_access_override)); +} + static void service_stop_watchdog(Service *s) { assert(s); @@ -850,7 +861,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { prefix, yes_no(s->guess_main_pid), prefix, service_type_to_string(s->type), prefix, service_restart_to_string(s->restart), - prefix, notify_access_to_string(s->notify_access), + prefix, notify_access_to_string(service_get_notify_access(s)), prefix, notify_state_to_string(s->notify_state), prefix, oom_policy_to_string(s->oom_policy), prefix, signal_to_string(s->reload_signal)); @@ -1465,11 +1476,11 @@ static bool service_exec_needs_notify_socket(Service *s, ExecFlags flags) { if (flags & EXEC_IS_CONTROL) /* A control process */ - return IN_SET(s->notify_access, NOTIFY_EXEC, NOTIFY_ALL); + return IN_SET(service_get_notify_access(s), NOTIFY_EXEC, NOTIFY_ALL); /* We only spawn main processes and control processes, so any * process that is not a control process is a main process */ - return s->notify_access != NOTIFY_NONE; + return service_get_notify_access(s) != NOTIFY_NONE; } static Service *service_get_triggering_service(Service *s) { @@ -1910,6 +1921,9 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) /* The next restart might not be a manual stop, hence reset the flag indicating manual stops */ s->forbid_restart = false; + /* Reset NotifyAccess override */ + s->notify_access_override = _NOTIFY_ACCESS_INVALID; + /* We want fresh tmpdirs in case service is started again immediately */ s->exec_runtime = exec_runtime_unref(s->exec_runtime, true); @@ -2404,6 +2418,8 @@ static void service_enter_restart(Service *s) { s->n_restarts ++; s->flush_n_restarts = false; + s->notify_access_override = _NOTIFY_ACCESS_INVALID; + log_unit_struct(UNIT(s), LOG_INFO, "MESSAGE_ID=" SD_MESSAGE_UNIT_RESTART_SCHEDULED_STR, LOG_UNIT_INVOCATION_ID(UNIT(s)), @@ -2613,6 +2629,7 @@ static int service_start(Unit *u) { s->status_text = mfree(s->status_text); s->status_errno = 0; + s->notify_access_override = _NOTIFY_ACCESS_INVALID; s->notify_state = NOTIFY_UNKNOWN; s->watchdog_original_usec = s->watchdog_usec; @@ -2864,6 +2881,9 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { } } + if (s->notify_access_override >= 0) + (void) serialize_item(f, "notify-access-override", notify_access_to_string(s->notify_access_override)); + (void) serialize_dual_timestamp(f, "watchdog-timestamp", &s->watchdog_timestamp); (void) serialize_bool(f, "forbid-restart", s->forbid_restart); @@ -3144,7 +3164,15 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, deserialize_dual_timestamp(value, &s->main_exec_status.start_timestamp); else if (streq(key, "main-exec-status-exit")) deserialize_dual_timestamp(value, &s->main_exec_status.exit_timestamp); - else if (streq(key, "watchdog-timestamp")) + else if (streq(key, "notify-access-override")) { + NotifyAccess notify_access; + + notify_access = notify_access_from_string(value); + if (notify_access < 0) + log_unit_debug(u, "Failed to parse notify-access-override value: %s", value); + else + s->notify_access_override = notify_access; + } else if (streq(key, "watchdog-timestamp")) deserialize_dual_timestamp(value, &s->watchdog_timestamp); else if (streq(key, "forbid-restart")) { int b; @@ -3670,7 +3698,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { * has been received */ if (f != SERVICE_SUCCESS) service_enter_signal(s, SERVICE_STOP_SIGTERM, f); - else if (!s->remain_after_exit || s->notify_access == NOTIFY_MAIN) + else if (!s->remain_after_exit || service_get_notify_access(s) == NOTIFY_MAIN) /* The service has never been and will never be active */ service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_PROTOCOL); break; @@ -4137,12 +4165,14 @@ static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void static bool service_notify_message_authorized(Service *s, pid_t pid, FDSet *fds) { assert(s); - if (s->notify_access == NOTIFY_NONE) { + NotifyAccess notify_access = service_get_notify_access(s); + + if (notify_access == NOTIFY_NONE) { log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception is disabled.", pid); return false; } - if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) { + if (notify_access == NOTIFY_MAIN && pid != s->main_pid) { if (s->main_pid != 0) log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid); else @@ -4151,7 +4181,7 @@ static bool service_notify_message_authorized(Service *s, pid_t pid, FDSet *fds) return false; } - if (s->notify_access == NOTIFY_EXEC && pid != s->main_pid && pid != s->control_pid) { + if (notify_access == NOTIFY_EXEC && pid != s->main_pid && pid != s->control_pid) { if (s->main_pid != 0 && s->control_pid != 0) log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT" and control PID "PID_FMT, pid, s->main_pid, s->control_pid); @@ -4193,7 +4223,7 @@ static void service_notify_message( assert(u); assert(ucred); - if (!service_notify_message_authorized(SERVICE(u), ucred->pid, fds)) + if (!service_notify_message_authorized(s, ucred->pid, fds)) return; if (DEBUG_LOGGING) { @@ -4328,6 +4358,25 @@ static void service_notify_message( } } + /* Interpret NOTIFYACCESS= */ + e = strv_find_startswith(tags, "NOTIFYACCESS="); + if (e) { + NotifyAccess notify_access; + + notify_access = notify_access_from_string(e); + if (notify_access < 0) + log_unit_warning_errno(u, notify_access, + "Failed to parse NOTIFYACCESS= field value '%s' in notification message, ignoring: %m", e); + + /* We don't need to check whether the new access mode is more strict than what is + * already in use, since only the privileged process is allowed to change it + * in the first place. */ + if (service_get_notify_access(s) != notify_access) { + service_override_notify_access(s, notify_access); + notify_dbus = true; + } + } + /* Interpret ERRNO= */ e = strv_find_startswith(tags, "ERRNO="); if (e) { diff --git a/src/core/service.h b/src/core/service.h index fd242a7cbfd..7663f26f70a 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -195,6 +195,7 @@ struct Service { PathSpec *pid_file_pathspec; NotifyAccess notify_access; + NotifyAccess notify_access_override; NotifyState notify_state; sd_bus_slot *bus_name_pid_lookup_slot; @@ -229,6 +230,11 @@ static inline usec_t service_timeout_abort_usec(Service *s) { return s->timeout_abort_set ? s->timeout_abort_usec : s->timeout_stop_usec; } +static inline NotifyAccess service_get_notify_access(Service *s) { + assert(s); + return s->notify_access_override < 0 ? s->notify_access : s->notify_access_override; +} + static inline usec_t service_get_watchdog_usec(Service *s) { assert(s); return s->watchdog_override_enable ? s->watchdog_override_usec : s->watchdog_original_usec; diff --git a/src/systemd/sd-daemon.h b/src/systemd/sd-daemon.h index 53a1d7ccfe9..6f88cf02fae 100644 --- a/src/systemd/sd-daemon.h +++ b/src/systemd/sd-daemon.h @@ -195,6 +195,10 @@ int sd_is_mq(int fd, const char *path); readable error message. Example: "STATUS=Completed 66% of file system check..." + NOTIFYACCESS=... + Reset the access to the service status notification socket. + Example: "NOTIFYACCESS=main" + ERRNO=... If a daemon fails, the errno-style error code, formatted as string. Example: "ERRNO=2" for ENOENT.