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