1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-26 03:22:00 +03:00

Merge pull request #7316 from poettering/fd-store-remove

add new FDSTOREREMOVE=1 sd_notify() message
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2017-11-28 09:15:00 +01:00 committed by GitHub
commit edb5318cbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 199 additions and 126 deletions

2
TODO
View File

@ -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

View File

@ -124,12 +124,10 @@
<varlistentry>
<term>READY=1</term>
<listitem><para>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 <literal>READY=1</literal> (i.e.
<literal>READY=0</literal> is not defined).</para></listitem>
<listitem><para>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 <varname>Type=notify</varname>
set. Since there is little value in signaling non-readiness, the only value services should send is
<literal>READY=1</literal> (i.e. <literal>READY=0</literal> is not defined).</para></listitem>
</varlistentry>
<varlistentry>
@ -204,6 +202,14 @@
watchdog is enabled. </para></listitem>
</varlistentry>
<varlistentry>
<term>WATCHDOG_USEC=…</term>
<listitem><para>Reset <varname>watchdog_usec</varname> value during runtime.
Notice that this is not available when using <function>sd_event_set_watchdog()</function>
or <function>sd_watchdog_enabled()</function>.
Example : <literal>WATCHDOG_USEC=20000000</literal></para></listitem>
</varlistentry>
<varlistentry>
<term>FDSTORE=1</term>
@ -229,33 +235,25 @@
</varlistentry>
<varlistentry>
<term>FDNAME=…</term>
<term>FDSTOREREMOVE=1</term>
<listitem><para>When used in combination with
<varname>FDSTORE=1</varname>, specifies a name for the
submitted file descriptors. This name is passed to the service
during activation, and may be queried using
<citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>. File
descriptors submitted without this field set, will implicitly
get the name <literal>stored</literal> 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
<function>sd_pid_notify_with_fds()</function>. The name may
consist of any ASCII character, but must not contain control
characters or <literal>:</literal>. It may not be longer than
255 characters. If a submitted name does not follow these
restrictions, it is ignored.</para></listitem>
<listitem><para>Removes file descriptors from the file descriptor store. This field needs to be combined with
<varname>FDNAME=</varname> to specify the name of the file descriptors to remove.</para></listitem>
</varlistentry>
<varlistentry>
<term>WATCHDOG_USEC=…</term>
<term>FDNAME=…</term>
<listitem><para>Reset <varname>watchdog_usec</varname> value during runtime.
Notice that this is not available when using <function>sd_event_set_watchdog()</function>
or <function>sd_watchdog_enabled()</function>.
Example : <literal>WATCHDOG_USEC=20000000</literal></para></listitem>
<listitem><para>When used in combination with <varname>FDSTORE=1</varname>, specifies a name for the submitted
file descriptors. When used with <varname>FDSTOREREMOVE=1</varname>, specifies the name for the file
descriptors to remove. This name is passed to the service during activation, and may be queried using
<citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>. File
descriptors submitted without this field set, will implicitly get the name <literal>stored</literal>
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 <function>sd_pid_notify_with_fds()</function>. The name may consist of arbitrary ASCII
characters except control characters or <literal>:</literal>. It may not be longer than 255 characters. If a
submitted name does not follow these restrictions, it is ignored.</para></listitem>
</varlistentry>
</variablelist>

View File

@ -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);

View File

@ -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) {

View File

@ -41,6 +41,7 @@ struct SessionDevice {
int fd;
bool active;
DeviceType type;
bool pushed_fd;
LIST_FIELDS(struct SessionDevice, sd_by_device);
};

View File

@ -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_.