diff --git a/man/rules/meson.build b/man/rules/meson.build index 84f0442b1e2e3..2096222c50da3 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -718,7 +718,7 @@ manpages = [ ['sd_machine_get_class', '3', ['sd_machine_get_ifindices'], ''], ['sd_notify', '3', - ['sd_notifyf', 'sd_pid_notify', 'sd_pid_notify_with_fds', 'sd_pid_notifyf'], + ['sd_notifyf', 'sd_pid_notify', 'sd_pid_notify_with_fds', 'sd_pid_notifyf', 'sd_notify_barrier'], ''], ['sd_path_lookup', '3', ['sd_path_lookup_strv'], ''], ['sd_pid_get_owner_uid', diff --git a/man/sd_notify.xml b/man/sd_notify.xml index 3046ca88ee728..dcc8fce69531b 100644 --- a/man/sd_notify.xml +++ b/man/sd_notify.xml @@ -22,6 +22,7 @@ sd_pid_notify sd_pid_notifyf sd_pid_notify_with_fds + sd_notify_barrier Notify service manager about start-up completion and other service status changes @@ -65,6 +66,12 @@ const int *fds unsigned n_fds + + + int sd_notify_barrier + int unset_environment + uint64_t timeout + @@ -251,6 +258,17 @@ submitted name does not follow these restrictions, it is ignored. + + BARRIER=1 + + Tells the service manager that the client is explicitly requesting synchronization by means of + closing the file descriptor sent with this command. The service manager gurantees that the processing of a + BARRIER=1 command will only happen after all previous notification messages sent before this command + have been processed. Hence, this command accompanied with a single file descriptor can be used to synchronize + against reception of all previous status messages. Note that this command cannot be mixed with other notifications, + and has to be sent in a separate message to the service manager, otherwise all assignments will be ignored. Note that + sending 0 or more than 1 file descriptor with this command is a violation of the protocol. + It is recommended to prefix variable names that are not @@ -272,6 +290,13 @@ attribute the message to the unit, and thus will ignore it, even if NotifyAccess= is set for it. + Hence, to eliminate all race conditions involving lookup of the client's unit and attribution of notifications + to units correctly, sd_notify_barrier() may be used. This call acts as a synchronization point + and ensures all notifications sent before this call have been picked up by the service manager when it returns + successfully. Use of sd_notify_barrier() is needed for clients which are not invoked by the + service manager, otherwise this synchronization mechanism is unnecessary for attribution of notifications to the + unit. + sd_notifyf() is similar to sd_notify() but takes a printf()-like format string plus @@ -302,6 +327,14 @@ to the service manager on messages that do not expect them (i.e. without FDSTORE=1) they are immediately closed on reception. + + sd_notify_barrier() allows the caller to + synchronize against reception of previously sent notification messages + and uses the BARRIER=1 command. It takes a relative + timeout value in microseconds which is passed to + ppoll2 + . A value of UINT64_MAX is interpreted as infinite timeout. + @@ -392,6 +425,22 @@ sd_pid_notify_with_fds(0, 0, "FDSTORE=1\nFDNAME=foobar", &fd, 1); + + + Eliminating race conditions + + When the client sending the notifications is not spawned + by the service manager, it may exit too quickly and the service + manager may fail to attribute them correctly to the unit. To + prevent such races, use sd_notify_barrier() + to synchronize against reception of all notifications sent before + this call is made. + + sd_notify(0, "READY=1"); + /* set timeout to 5 seconds */ + sd_notify_barrier(0, 5 * 1000000); + + diff --git a/src/core/manager.c b/src/core/manager.c index 43b8a02e488ab..501e37339b82f 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -2284,6 +2284,20 @@ static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, ui return 0; } +static bool manager_process_barrier_fd(const char *buf, FDSet *fds) { + assert(buf); + + /* nothing else must be sent when using BARRIER=1 */ + if (STR_IN_SET(buf, "BARRIER=1", "BARRIER=1\n")) { + if (fdset_size(fds) != 1) + log_warning("Got incorrect number of fds with BARRIER=1, closing them."); + return true; + } else if (startswith(buf, "BARRIER=1\n") || strstr(buf, "\nBARRIER=1\n") || endswith(buf, "\nBARRIER=1")) + log_warning("Extra notification messages sent with BARRIER=1, ignoring everything."); + + return false; +} + static void manager_invoke_notify_message( Manager *m, Unit *u, @@ -2417,6 +2431,10 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t /* Make sure it's NUL-terminated. */ buf[n] = 0; + /* possibly a barrier fd, let's see */ + if (manager_process_barrier_fd(buf, fds)) + return 0; + /* Increase the generation counter used for filtering out duplicate unit invocations. */ m->notifygen++; diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c index 4cd71cb2d3a02..587a1f2595bfe 100644 --- a/src/libsystemd/sd-daemon/sd-daemon.c +++ b/src/libsystemd/sd-daemon/sd-daemon.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include "process-util.h" #include "socket-util.h" #include "strv.h" +#include "time-util.h" #include "util.h" #define SNDBUF_SIZE (8*1024*1024) @@ -551,6 +553,34 @@ _public_ int sd_pid_notify_with_fds( return r; } +_public_ int sd_notify_barrier(int unset_environment, uint64_t timeout) { + _cleanup_close_pair_ int pipe_fd[2] = { -1, -1 }; + struct timespec ts; + int r; + + if (pipe2(pipe_fd, O_CLOEXEC) < 0) + return -errno; + + r = sd_pid_notify_with_fds(0, unset_environment, "BARRIER=1", &pipe_fd[1], 1); + if (r <= 0) + return r; + + pipe_fd[1] = safe_close(pipe_fd[1]); + + struct pollfd pfd = { + .fd = pipe_fd[0], + /* POLLHUP is implicit */ + .events = 0, + }; + r = ppoll(&pfd, 1, timeout == UINT64_MAX ? NULL : timespec_store(&ts, timeout), NULL); + if (r < 0) + return -errno; + if (r == 0) + return -ETIMEDOUT; + + return 1; +} + _public_ int sd_pid_notify(pid_t pid, int unset_environment, const char *state) { return sd_pid_notify_with_fds(pid, unset_environment, state, NULL, 0); } diff --git a/src/notify/notify.c b/src/notify/notify.c index 6c2dedd53fd6f..69d473401da66 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -18,6 +18,7 @@ #include "string-util.h" #include "strv.h" #include "terminal-util.h" +#include "time-util.h" #include "user-util.h" #include "util.h" @@ -27,6 +28,7 @@ static const char *arg_status = NULL; static bool arg_booted = false; static uid_t arg_uid = UID_INVALID; static gid_t arg_gid = GID_INVALID; +static bool arg_no_block = false; static int help(void) { _cleanup_free_ char *link = NULL; @@ -45,6 +47,7 @@ static int help(void) { " --uid=USER Set user to send from\n" " --status=TEXT Set status text\n" " --booted Check if the system was booted up with systemd\n" + " --no-block Do not wait until operation finished\n" "\nSee the %s for details.\n" , program_invocation_short_name , ansi_highlight(), ansi_normal() @@ -83,6 +86,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_STATUS, ARG_BOOTED, ARG_UID, + ARG_NO_BLOCK }; static const struct option options[] = { @@ -93,6 +97,7 @@ static int parse_argv(int argc, char *argv[]) { { "status", required_argument, NULL, ARG_STATUS }, { "booted", no_argument, NULL, ARG_BOOTED }, { "uid", required_argument, NULL, ARG_UID }, + { "no-block", no_argument, NULL, ARG_NO_BLOCK }, {} }; @@ -157,6 +162,10 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_NO_BLOCK: + arg_no_block = true; + break; + case '?': return -EINVAL; @@ -256,6 +265,16 @@ static int run(int argc, char* argv[]) { if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "No status data could be sent: $NOTIFY_SOCKET was not set"); + + if (!arg_no_block) { + r = sd_notify_barrier(0, 5 * USEC_PER_SEC); + if (r < 0) + return log_error_errno(r, "Failed to invoke barrier: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "No status data could be sent: $NOTIFY_SOCKET was not set"); + } + return 0; } diff --git a/src/systemd/sd-daemon.h b/src/systemd/sd-daemon.h index 62b0f723c7a1a..b47b15a445a96 100644 --- a/src/systemd/sd-daemon.h +++ b/src/systemd/sd-daemon.h @@ -286,6 +286,19 @@ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) _s */ int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds); +/* + Returns > 0 if synchronization with systemd succeeded. Returns < 0 + on error. Returns 0 if $NOTIFY_SOCKET was not set. Note that the + timeout parameter of this function call takes the timeout in µs, and + will be passed to ppoll(2), hence the behaviour will be similar to + ppoll(2). This function can be called after sending a status message + to systemd, if one needs to synchronize against reception of the + status messages sent before this call is made. Therefore, this + cannot be used to know if the status message was processed + successfully, but to only synchronize against its consumption. +*/ +int sd_notify_barrier(int unset_environment, uint64_t timeout); + /* Returns > 0 if the system was booted with systemd. Returns < 0 on error. Returns 0 if the system was not booted with systemd. Note