diff --git a/TODO b/TODO
index 424a75df1a..f7ed2627ba 100644
--- a/TODO
+++ b/TODO
@@ -58,8 +58,6 @@ Features:
* rework ExecOutput and ExecInput enums so that EXEC_OUTPUT_NULL loses its
magic meaning and is no longer upgraded to something else if set explicitly.
-* add a way to remove fds from the fdstore by name, and make logind use it
-
* in the long run: permit a system with /etc/machine-id linked to /dev/null, to
make it lose its identity, i.e. be anonymous. For this we'd have to patch
through the whole tree to make all code deal with the case where no machine
diff --git a/man/sd_notify.xml b/man/sd_notify.xml
index 078f1ff7e0..a53b1ab33e 100644
--- a/man/sd_notify.xml
+++ b/man/sd_notify.xml
@@ -124,12 +124,10 @@
READY=1
- Tells the service manager that service startup
- is finished. This is only used by systemd if the service
- definition file has Type=notify set. Since there is little
- value in signaling non-readiness, the only value services
- should send is READY=1 (i.e.
- READY=0 is not defined).
+ Tells the service manager that service startup is finished, or the service finished loading its
+ configuration. This is only used by systemd if the service definition file has Type=notify
+ set. Since there is little value in signaling non-readiness, the only value services should send is
+ READY=1 (i.e. READY=0 is not defined).
@@ -204,6 +202,14 @@
watchdog is enabled.
+
+ WATCHDOG_USEC=…
+
+ Reset watchdog_usec value during runtime.
+ Notice that this is not available when using sd_event_set_watchdog()
+ or sd_watchdog_enabled().
+ Example : WATCHDOG_USEC=20000000
+
FDSTORE=1
@@ -229,33 +235,25 @@
- FDNAME=…
+ FDSTOREREMOVE=1
- When used in combination with
- FDSTORE=1, specifies a name for the
- submitted file descriptors. This name is passed to the service
- during activation, and may be queried using
- sd_listen_fds_with_names3. File
- descriptors submitted without this field set, will implicitly
- get the name stored assigned. Note that, if
- multiple file descriptors are submitted at once, the specified
- name will be assigned to all of them. In order to assign
- different names to submitted file descriptors, submit them in
- separate invocations of
- sd_pid_notify_with_fds(). The name may
- consist of any ASCII character, but must not contain control
- characters or :. It may not be longer than
- 255 characters. If a submitted name does not follow these
- restrictions, it is ignored.
+ Removes file descriptors from the file descriptor store. This field needs to be combined with
+ FDNAME= to specify the name of the file descriptors to remove.
- WATCHDOG_USEC=…
+ FDNAME=…
- Reset watchdog_usec value during runtime.
- Notice that this is not available when using sd_event_set_watchdog()
- or sd_watchdog_enabled().
- Example : WATCHDOG_USEC=20000000
+ When used in combination with FDSTORE=1, specifies a name for the submitted
+ file descriptors. When used with FDSTOREREMOVE=1, specifies the name for the file
+ descriptors to remove. This name is passed to the service during activation, and may be queried using
+ sd_listen_fds_with_names3. File
+ descriptors submitted without this field set, will implicitly get the name stored
+ assigned. Note that, if multiple file descriptors are submitted at once, the specified name will be assigned to
+ all of them. In order to assign different names to submitted file descriptors, submit them in separate
+ invocations of sd_pid_notify_with_fds(). The name may consist of arbitrary ASCII
+ characters except control characters or :. It may not be longer than 255 characters. If a
+ submitted name does not follow these restrictions, it is ignored.
diff --git a/src/core/service.c b/src/core/service.c
index 672eaa9199..e7a829a564 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -458,6 +458,21 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) {
return 0;
}
+static void service_remove_fd_store(Service *s, const char *name) {
+ ServiceFDStore *fs, *n;
+
+ assert(s);
+ assert(name);
+
+ LIST_FOREACH_SAFE(fd_store, fs, n, s->fd_store) {
+ if (!streq(fs->fdname, name))
+ continue;
+
+ log_unit_debug(UNIT(s), "Got explicit request to remove fd %i (%s), closing.", fs->fd, name);
+ service_fd_store_unlink(fs);
+ }
+}
+
static int service_arm_timer(Service *s, usec_t usec) {
int r;
@@ -3310,38 +3325,57 @@ static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void
return 0;
}
+static bool service_notify_message_authorized(Service *s, pid_t pid, char **tags, FDSet *fds) {
+ assert(s);
+
+ if (s->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 (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
+ log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid);
+
+ return false;
+ }
+
+ if (s->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);
+ else 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 if (s->control_pid != 0)
+ log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for control PID "PID_FMT, pid, s->control_pid);
+ else
+ log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID and control PID which are currently not known", pid);
+
+ return false;
+ }
+
+ return true;
+}
+
static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds) {
Service *s = SERVICE(u);
- _cleanup_free_ char *cc = NULL;
bool notify_dbus = false;
const char *e;
+ char **i;
assert(u);
- cc = strv_join(tags, ", ");
+ if (!service_notify_message_authorized(SERVICE(u), pid, tags, fds))
+ return;
- if (s->notify_access == NOTIFY_NONE) {
- log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception is disabled.", pid);
- return;
- } else if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) {
- if (s->main_pid != 0)
- log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid);
- else
- log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid);
- return;
- } else if (s->notify_access == NOTIFY_EXEC && pid != s->main_pid && pid != s->control_pid) {
- if (s->main_pid != 0 && s->control_pid != 0)
- log_unit_warning(u, "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);
- else if (s->main_pid != 0)
- log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid);
- else if (s->control_pid != 0)
- log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for control PID "PID_FMT, pid, s->control_pid);
- else
- log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID and control PID which are currently not known", pid);
- return;
- } else
+ if (log_get_max_level() >= LOG_DEBUG) {
+ _cleanup_free_ char *cc = NULL;
+
+ cc = strv_join(tags, ", ");
log_unit_debug(u, "Got notification message from PID "PID_FMT" (%s)", pid, isempty(cc) ? "n/a" : cc);
+ }
/* Interpret MAINPID= */
e = strv_find_startswith(tags, "MAINPID=");
@@ -3352,51 +3386,50 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds)
log_unit_warning(u, "A control process cannot also be the main process");
else if (pid == getpid_cached() || pid == 1)
log_unit_warning(u, "Service manager can't be main process, ignoring sd_notify() MAINPID= field");
- else {
+ else if (pid != s->main_pid) {
service_set_main_pid(s, pid);
unit_watch_pid(UNIT(s), pid);
notify_dbus = true;
}
}
- /* Interpret RELOADING= */
- if (strv_find(tags, "RELOADING=1")) {
+ /* Interpret READY=/STOPPING=/RELOADING=. Last one wins. */
+ STRV_FOREACH_BACKWARDS(i, tags) {
- s->notify_state = NOTIFY_RELOADING;
+ if (streq(*i, "READY=1")) {
+ 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 (s->type == SERVICE_NOTIFY && s->state == SERVICE_START)
+ service_enter_start_post(s);
- notify_dbus = true;
- }
+ /* 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);
- /* Interpret READY= */
- if (strv_find(tags, "READY=1")) {
+ notify_dbus = true;
+ break;
- s->notify_state = NOTIFY_READY;
+ } else if (streq(*i, "RELOADING=1")) {
+ s->notify_state = NOTIFY_RELOADING;
- /* 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 (s->state == SERVICE_RUNNING)
+ service_enter_reload_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;
+ break;
- notify_dbus = true;
- }
+ } else if (streq(*i, "STOPPING=1")) {
+ s->notify_state = NOTIFY_STOPPING;
- /* Interpret STOPPING= */
- if (strv_find(tags, "STOPPING=1")) {
+ if (s->state == SERVICE_RUNNING)
+ service_enter_stop_by_notify(s);
- s->notify_state = NOTIFY_STOPPING;
-
- if (s->state == SERVICE_RUNNING)
- service_enter_stop_by_notify(s);
-
- notify_dbus = true;
+ notify_dbus = true;
+ break;
+ }
}
/* Interpret STATUS= */
@@ -3425,13 +3458,13 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds)
if (e) {
int status_errno;
- if (safe_atoi(e, &status_errno) < 0 || status_errno < 0)
- log_unit_warning(u, "Failed to parse ERRNO= field in notification message: %s", e);
- else {
- if (s->status_errno != status_errno) {
- s->status_errno = status_errno;
- notify_dbus = true;
- }
+ status_errno = parse_errno(e);
+ if (status_errno < 0)
+ log_unit_warning_errno(u, status_errno,
+ "Failed to parse ERRNO= field in notification message: %s", e);
+ else if (s->status_errno != status_errno) {
+ s->status_errno = status_errno;
+ notify_dbus = true;
}
}
@@ -3439,18 +3472,6 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds)
if (strv_find(tags, "WATCHDOG=1"))
service_reset_watchdog(s);
- if (strv_find(tags, "FDSTORE=1")) {
- const char *name;
-
- name = strv_find_startswith(tags, "FDNAME=");
- if (name && !fdname_is_valid(name)) {
- log_unit_warning(u, "Passed FDNAME= name is invalid, ignoring.");
- name = NULL;
- }
-
- service_add_fd_store_set(s, fds, name);
- }
-
e = strv_find_startswith(tags, "WATCHDOG_USEC=");
if (e) {
usec_t watchdog_override_usec;
@@ -3460,6 +3481,30 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds)
service_reset_watchdog_timeout(s, watchdog_override_usec);
}
+ /* Process FD store messages. Either FDSTOREREMOVE=1 for removal, or FDSTORE=1 for addition. In both cases,
+ * process FDNAME= for picking the file descriptor name to use. Note that FDNAME= is required when removing
+ * fds, but optional when pushing in new fds, for compatibility reasons. */
+ if (strv_find(tags, "FDSTOREREMOVE=1")) {
+ const char *name;
+
+ name = strv_find_startswith(tags, "FDNAME=");
+ if (!name || !fdname_is_valid(name))
+ log_unit_warning(u, "FDSTOREREMOVE=1 requested, but no valid file descriptor name passed, ignoring.");
+ else
+ service_remove_fd_store(s, name);
+
+ } else if (strv_find(tags, "FDSTORE=1")) {
+ const char *name;
+
+ name = strv_find_startswith(tags, "FDNAME=");
+ if (name && !fdname_is_valid(name)) {
+ log_unit_warning(u, "Passed FDNAME= name is invalid, ignoring.");
+ name = NULL;
+ }
+
+ (void) service_add_fd_store_set(s, fds, name);
+ }
+
/* Notify clients about changed status or main pid */
if (notify_dbus)
unit_add_to_dbus_queue(u);
diff --git a/src/login/logind-session-device.c b/src/login/logind-session-device.c
index b55ce60be1..067e67a93d 100644
--- a/src/login/logind-session-device.c
+++ b/src/login/logind-session-device.c
@@ -410,6 +410,17 @@ error:
void session_device_free(SessionDevice *sd) {
assert(sd);
+ if (sd->pushed_fd) {
+ const char *m;
+
+ /* Remove the pushed fd again, just in case. */
+
+ m = strjoina("FDSTOREREMOVE=1\n"
+ "FDNAME=session-", sd->session->id);
+
+ (void) sd_notify(false, m);
+ }
+
session_device_stop(sd);
session_device_notify(sd, SESSION_DEVICE_RELEASE);
close_nointr(sd->fd);
@@ -489,26 +500,30 @@ unsigned int session_device_try_pause_all(Session *s) {
}
int session_device_save(SessionDevice *sd) {
- _cleanup_free_ char *state = NULL;
+ const char *m;
int r;
assert(sd);
- /* Store device fd in PID1. It will send it back to us on
- * restart so revocation will continue to work. To make things
- * simple, send fds for all type of devices even if they don't
- * support the revocation mechanism so we don't have to handle
- * them differently later.
+ /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make
+ * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we
+ * don't have to handle them differently later.
*
- * Note: for device supporting revocation, PID1 will drop a
- * stored fd automatically if the corresponding device is
- * revoked. */
- r = asprintf(&state, "FDSTORE=1\n"
- "FDNAME=session-%s", sd->session->id);
- if (r < 0)
- return -ENOMEM;
+ * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device
+ * is revoked. */
- return sd_pid_notify_with_fds(0, false, state, &sd->fd, 1);
+ if (sd->pushed_fd)
+ return 0;
+
+ m = strjoina("FDSTORE=1\n"
+ "FDNAME=session", sd->session->id);
+
+ r = sd_pid_notify_with_fds(0, false, m, &sd->fd, 1);
+ if (r < 0)
+ return r;
+
+ sd->pushed_fd = true;
+ return 1;
}
void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
diff --git a/src/login/logind-session-device.h b/src/login/logind-session-device.h
index c263e16750..a1cf17af92 100644
--- a/src/login/logind-session-device.h
+++ b/src/login/logind-session-device.h
@@ -41,6 +41,7 @@ struct SessionDevice {
int fd;
bool active;
DeviceType type;
+ bool pushed_fd;
LIST_FIELDS(struct SessionDevice, sd_by_device);
};
diff --git a/src/systemd/sd-daemon.h b/src/systemd/sd-daemon.h
index 61cfdd8e0a..9772a05dad 100644
--- a/src/systemd/sd-daemon.h
+++ b/src/systemd/sd-daemon.h
@@ -175,12 +175,22 @@ int sd_is_mq(int fd, const char *path);
newline separated environment-style variable assignments in a
string. The following variables are known:
- READY=1 Tells systemd that daemon startup is finished (only
- relevant for services of Type=notify). The passed
- argument is a boolean "1" or "0". Since there is
- little value in signaling non-readiness the only
+ MAINPID=... The main PID of a daemon, in case systemd did not
+ fork off the process itself. Example: "MAINPID=4711"
+
+ READY=1 Tells systemd that daemon startup or daemon reload
+ is finished (only relevant for services of Type=notify).
+ The passed argument is a boolean "1" or "0". Since there
+ is little value in signaling non-readiness the only
value daemons should send is "READY=1".
+ RELOADING=1 Tell systemd that the daemon began reloading its
+ configuration. When the configuration has been
+ reloaded completely, READY=1 should be sent to inform
+ systemd about this.
+
+ STOPPING=1 Tells systemd that the daemon is about to go down.
+
STATUS=... Passes a single-line status string back to systemd
that describes the daemon state. This is free-form
and can be used for various purposes: general state
@@ -195,25 +205,31 @@ int sd_is_mq(int fd, const char *path);
BUSERROR=... If a daemon fails, the D-Bus error-style error
code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
- MAINPID=... The main pid of a daemon, in case systemd did not
- fork off the process itself. Example: "MAINPID=4711"
-
WATCHDOG=1 Tells systemd to update the watchdog timestamp.
Services using this feature should do this in
regular intervals. A watchdog framework can use the
timestamps to detect failed services. Also see
sd_watchdog_enabled() below.
+ WATCHDOG_USEC=...
+ Reset watchdog_usec value during runtime.
+ To reset watchdog_usec value, start the service again.
+ Example: "WATCHDOG_USEC=20000000"
+
FDSTORE=1 Store the file descriptors passed along with the
message in the per-service file descriptor store,
and pass them to the main process again on next
invocation. This variable is only supported with
sd_pid_notify_with_fds().
- WATCHDOG_USEC=...
- Reset watchdog_usec value during runtime.
- To reset watchdog_usec value, start the service again.
- Example: "WATCHDOG_USEC=20000000"
+ FDSTOREREMOVE=1
+ Remove one or more file descriptors from the file
+ descriptor store, identified by the name specified
+ in FDNAME=, see below.
+
+ FDNAME= A name to assign to new file descriptors stored in the
+ file descriptor store, or the name of the file descriptors
+ to remove in case of FDSTOREREMOVE=1.
Daemons can choose to send additional variables. However, it is
recommended to prefix variable names not listed above with X_.