diff --git a/man/systemd.service.xml b/man/systemd.service.xml
index 1372cc972a..95c16fded7 100644
--- a/man/systemd.service.xml
+++ b/man/systemd.service.xml
@@ -782,15 +782,14 @@
NonBlocking=
- Set the O_NONBLOCK flag
- for all file descriptors passed via socket-based activation.
- If true, all file descriptors >= 3 (i.e. all except stdin,
- stdout, and stderr) will have the
- O_NONBLOCK flag set and hence are in
- non-blocking mode. This option is only useful in conjunction
- with a socket unit, as described in
- systemd.socket5.
- Defaults to false.
+ Set the O_NONBLOCK flag for all file descriptors passed via socket-based
+ activation. If true, all file descriptors >= 3 (i.e. all except stdin, stdout, stderr), excluding those passed
+ in via the file descriptor storage logic (see FileDescriptorStoreMax= for details), will
+ have the O_NONBLOCK flag set and hence are in non-blocking mode. This option is only
+ useful in conjunction with a socket unit, as described in
+ systemd.socket5 and has no
+ effect on file descriptors which were previously saved in the file-descriptor store for example. Defaults to
+ false.
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
index d86700736d..4532f222c8 100644
--- a/src/basic/parse-util.c
+++ b/src/basic/parse-util.c
@@ -589,3 +589,18 @@ int parse_ip_port(const char *s, uint16_t *ret) {
return 0;
}
+
+int parse_dev(const char *s, dev_t *ret) {
+ unsigned x, y;
+ dev_t d;
+
+ if (sscanf(s, "%u:%u", &x, &y) != 2)
+ return -EINVAL;
+
+ d = makedev(x, y);
+ if ((unsigned) major(d) != x || (unsigned) minor(d) != y)
+ return -EINVAL;
+
+ *ret = d;
+ return 0;
+}
diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h
index 4d132f0de5..dc09782ca8 100644
--- a/src/basic/parse-util.h
+++ b/src/basic/parse-util.h
@@ -30,6 +30,7 @@
#define MODE_INVALID ((mode_t) -1)
int parse_boolean(const char *v) _pure_;
+int parse_dev(const char *s, dev_t *ret);
int parse_pid(const char *s, pid_t* ret_pid);
int parse_mode(const char *s, mode_t *ret);
int parse_ifindex(const char *s, int *ret);
diff --git a/src/core/execute.c b/src/core/execute.c
index 786096861a..340939d98a 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -157,22 +157,26 @@ static int shift_fds(int fds[], unsigned n_fds) {
return 0;
}
-static int flags_fds(const int fds[], unsigned n_fds, bool nonblock) {
- unsigned i;
+static int flags_fds(const int fds[], unsigned n_storage_fds, unsigned n_socket_fds, bool nonblock) {
+ unsigned i, n_fds;
int r;
+ n_fds = n_storage_fds + n_socket_fds;
if (n_fds <= 0)
return 0;
assert(fds);
- /* Drops/Sets O_NONBLOCK and FD_CLOEXEC from the file flags */
+ /* Drops/Sets O_NONBLOCK and FD_CLOEXEC from the file flags.
+ * O_NONBLOCK only applies to socket activation though. */
for (i = 0; i < n_fds; i++) {
- r = fd_nonblock(fds[i], nonblock);
- if (r < 0)
- return r;
+ if (i < n_socket_fds) {
+ r = fd_nonblock(fds[i], nonblock);
+ if (r < 0)
+ return r;
+ }
/* We unconditionally drop FD_CLOEXEC from the fds,
* since after all we want to pass these fds to our
@@ -2241,7 +2245,9 @@ static int exec_child(
char **argv,
int socket_fd,
int named_iofds[3],
- int *fds, unsigned n_fds,
+ int *fds,
+ unsigned n_storage_fds,
+ unsigned n_socket_fds,
char **files_env,
int user_lookup_fd,
int *exit_status,
@@ -2258,6 +2264,7 @@ static int exec_child(
uid_t uid = UID_INVALID;
gid_t gid = GID_INVALID;
int i, r, ngids = 0;
+ unsigned n_fds;
assert(unit);
assert(command);
@@ -2298,6 +2305,7 @@ static int exec_child(
log_forget_fds();
+ n_fds = n_storage_fds + n_socket_fds;
r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, fds, n_fds);
if (r < 0) {
*exit_status = EXIT_FDS;
@@ -2669,7 +2677,7 @@ static int exec_child(
if (r >= 0)
r = shift_fds(fds, n_fds);
if (r >= 0)
- r = flags_fds(fds, n_fds, context->non_blocking);
+ r = flags_fds(fds, n_storage_fds, n_socket_fds, context->non_blocking);
if (r < 0) {
*exit_status = EXIT_FDS;
return r;
@@ -2909,7 +2917,8 @@ int exec_spawn(Unit *unit,
pid_t *ret) {
_cleanup_strv_free_ char **files_env = NULL;
- int *fds = NULL; unsigned n_fds = 0;
+ int *fds = NULL;
+ unsigned n_storage_fds = 0, n_socket_fds = 0;
_cleanup_free_ char *line = NULL;
int socket_fd, r;
int named_iofds[3] = { -1, -1, -1 };
@@ -2921,18 +2930,18 @@ int exec_spawn(Unit *unit,
assert(context);
assert(ret);
assert(params);
- assert(params->fds || params->n_fds <= 0);
+ assert(params->fds || (params->n_storage_fds + params->n_socket_fds <= 0));
if (context->std_input == EXEC_INPUT_SOCKET ||
context->std_output == EXEC_OUTPUT_SOCKET ||
context->std_error == EXEC_OUTPUT_SOCKET) {
- if (params->n_fds > 1) {
+ if (params->n_socket_fds > 1) {
log_unit_error(unit, "Got more than one socket.");
return -EINVAL;
}
- if (params->n_fds == 0) {
+ if (params->n_socket_fds == 0) {
log_unit_error(unit, "Got no socket.");
return -EINVAL;
}
@@ -2941,7 +2950,8 @@ int exec_spawn(Unit *unit,
} else {
socket_fd = -1;
fds = params->fds;
- n_fds = params->n_fds;
+ n_storage_fds = params->n_storage_fds;
+ n_socket_fds = params->n_socket_fds;
}
r = exec_context_named_iofds(unit, context, params, named_iofds);
@@ -2979,7 +2989,9 @@ int exec_spawn(Unit *unit,
argv,
socket_fd,
named_iofds,
- fds, n_fds,
+ fds,
+ n_storage_fds,
+ n_socket_fds,
files_env,
unit->manager->user_lookup_fds[1],
&exit_status,
@@ -3188,6 +3200,7 @@ const char* exec_context_fdname(const ExecContext *c, int fd_index) {
int exec_context_named_iofds(Unit *unit, const ExecContext *c, const ExecParameters *p, int named_iofds[3]) {
unsigned i, targets;
const char* stdio_fdname[3];
+ unsigned n_fds;
assert(c);
assert(p);
@@ -3199,7 +3212,9 @@ int exec_context_named_iofds(Unit *unit, const ExecContext *c, const ExecParamet
for (i = 0; i < 3; i++)
stdio_fdname[i] = exec_context_fdname(c, i);
- for (i = 0; i < p->n_fds && targets > 0; i++)
+ n_fds = p->n_storage_fds + p->n_socket_fds;
+
+ for (i = 0; i < n_fds && targets > 0; i++)
if (named_iofds[STDIN_FILENO] < 0 &&
c->std_input == EXEC_INPUT_NAMED_FD &&
stdio_fdname[STDIN_FILENO] &&
diff --git a/src/core/execute.h b/src/core/execute.h
index 9f2b6fd39e..136319359c 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -246,7 +246,8 @@ struct ExecParameters {
int *fds;
char **fd_names;
- unsigned n_fds;
+ unsigned n_storage_fds;
+ unsigned n_socket_fds;
ExecFlags flags;
bool selinux_context_net:1;
diff --git a/src/core/service.c b/src/core/service.c
index b45929e535..df7f1f3053 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1068,14 +1068,21 @@ static int service_coldplug(Unit *u) {
return 0;
}
-static int service_collect_fds(Service *s, int **fds, char ***fd_names) {
+static int service_collect_fds(Service *s,
+ int **fds,
+ char ***fd_names,
+ unsigned *n_storage_fds,
+ unsigned *n_socket_fds) {
+
_cleanup_strv_free_ char **rfd_names = NULL;
_cleanup_free_ int *rfds = NULL;
- int rn_fds = 0, r;
+ unsigned rn_socket_fds = 0, rn_storage_fds = 0;
+ int r;
assert(s);
assert(fds);
assert(fd_names);
+ assert(n_socket_fds);
if (s->socket_fd >= 0) {
@@ -1090,7 +1097,7 @@ static int service_collect_fds(Service *s, int **fds, char ***fd_names) {
if (!rfd_names)
return -ENOMEM;
- rn_fds = 1;
+ rn_socket_fds = 1;
} else {
Iterator i;
Unit *u;
@@ -1116,20 +1123,20 @@ static int service_collect_fds(Service *s, int **fds, char ***fd_names) {
if (!rfds) {
rfds = cfds;
- rn_fds = cn_fds;
+ rn_socket_fds = cn_fds;
cfds = NULL;
} else {
int *t;
- t = realloc(rfds, (rn_fds + cn_fds) * sizeof(int));
+ t = realloc(rfds, (rn_socket_fds + cn_fds) * sizeof(int));
if (!t)
return -ENOMEM;
- memcpy(t + rn_fds, cfds, cn_fds * sizeof(int));
+ memcpy(t + rn_socket_fds, cfds, cn_fds * sizeof(int));
rfds = t;
- rn_fds += cn_fds;
+ rn_socket_fds += cn_fds;
}
r = strv_extend_n(&rfd_names, socket_fdname(sock), cn_fds);
@@ -1140,40 +1147,45 @@ static int service_collect_fds(Service *s, int **fds, char ***fd_names) {
if (s->n_fd_store > 0) {
ServiceFDStore *fs;
+ unsigned n_fds;
char **nl;
int *t;
- t = realloc(rfds, (rn_fds + s->n_fd_store) * sizeof(int));
+ t = realloc(rfds, (rn_socket_fds + s->n_fd_store) * sizeof(int));
if (!t)
return -ENOMEM;
rfds = t;
- nl = realloc(rfd_names, (rn_fds + s->n_fd_store + 1) * sizeof(char*));
+ nl = realloc(rfd_names, (rn_socket_fds + s->n_fd_store + 1) * sizeof(char*));
if (!nl)
return -ENOMEM;
rfd_names = nl;
+ n_fds = rn_socket_fds;
LIST_FOREACH(fd_store, fs, s->fd_store) {
- rfds[rn_fds] = fs->fd;
- rfd_names[rn_fds] = strdup(strempty(fs->fdname));
- if (!rfd_names[rn_fds])
+ rfds[n_fds] = fs->fd;
+ rfd_names[n_fds] = strdup(strempty(fs->fdname));
+ if (!rfd_names[n_fds])
return -ENOMEM;
- rn_fds++;
+ rn_storage_fds++;
+ n_fds++;
}
- rfd_names[rn_fds] = NULL;
+ rfd_names[n_fds] = NULL;
}
*fds = rfds;
*fd_names = rfd_names;
+ *n_socket_fds = rn_socket_fds;
+ *n_storage_fds = rn_storage_fds;
rfds = NULL;
rfd_names = NULL;
- return rn_fds;
+ return 0;
}
static bool service_exec_needs_notify_socket(Service *s, ExecFlags flags) {
@@ -1204,7 +1216,7 @@ static int service_spawn(
_cleanup_strv_free_ char **final_env = NULL, **our_env = NULL, **fd_names = NULL;
_cleanup_free_ int *fds = NULL;
- unsigned n_fds = 0, n_env = 0;
+ unsigned n_storage_fds = 0, n_socket_fds = 0, n_env = 0;
const char *path;
pid_t pid;
@@ -1248,12 +1260,11 @@ static int service_spawn(
s->exec_context.std_output == EXEC_OUTPUT_SOCKET ||
s->exec_context.std_error == EXEC_OUTPUT_SOCKET) {
- r = service_collect_fds(s, &fds, &fd_names);
+ r = service_collect_fds(s, &fds, &fd_names, &n_storage_fds, &n_socket_fds);
if (r < 0)
return r;
- n_fds = r;
- log_unit_debug(UNIT(s), "Passing %i fds to service", n_fds);
+ log_unit_debug(UNIT(s), "Passing %i fds to service", n_storage_fds + n_socket_fds);
}
r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout));
@@ -1347,7 +1358,8 @@ static int service_spawn(
exec_params.environment = final_env;
exec_params.fds = fds;
exec_params.fd_names = fd_names;
- exec_params.n_fds = n_fds;
+ exec_params.n_storage_fds = n_storage_fds;
+ exec_params.n_socket_fds = n_socket_fds;
exec_params.confirm_spawn = manager_get_confirm_spawn(UNIT(s)->manager);
exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported;
exec_params.cgroup_path = path;
diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c
index 22dea5db1f..22e5349a67 100644
--- a/src/login/logind-session-dbus.c
+++ b/src/login/logind-session-dbus.c
@@ -396,7 +396,7 @@ static int method_take_control(sd_bus_message *message, void *userdata, sd_bus_e
if (uid != 0 && (force || uid != s->user->uid))
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may take control");
- r = session_set_controller(s, sd_bus_message_get_sender(message), force);
+ r = session_set_controller(s, sd_bus_message_get_sender(message), force, true);
if (r < 0)
return r;
@@ -444,14 +444,23 @@ static int method_take_device(sd_bus_message *message, void *userdata, sd_bus_er
* equivalent). */
return sd_bus_error_setf(error, BUS_ERROR_DEVICE_IS_TAKEN, "Device already taken");
- r = session_device_new(s, dev, &sd);
+ r = session_device_new(s, dev, true, &sd);
if (r < 0)
return r;
+ r = session_device_save(sd);
+ if (r < 0)
+ goto error;
+
r = sd_bus_reply_method_return(message, "hb", sd->fd, !sd->active);
if (r < 0)
- session_device_free(sd);
+ goto error;
+ session_save(s);
+ return 0;
+
+error:
+ session_device_free(sd);
return r;
}
@@ -478,6 +487,8 @@ static int method_release_device(sd_bus_message *message, void *userdata, sd_bus
return sd_bus_error_setf(error, BUS_ERROR_DEVICE_NOT_TAKEN, "Device not taken");
session_device_free(sd);
+ session_save(s);
+
return sd_bus_reply_method_return(message, NULL);
}
diff --git a/src/login/logind-session-device.c b/src/login/logind-session-device.c
index 4055a23277..05af2045d2 100644
--- a/src/login/logind-session-device.c
+++ b/src/login/logind-session-device.c
@@ -30,6 +30,8 @@
#include "fd-util.h"
#include "logind-session-device.h"
#include "missing.h"
+#include "parse-util.h"
+#include "sd-daemon.h"
#include "util.h"
enum SessionDeviceNotifications {
@@ -206,7 +208,10 @@ static int session_device_start(SessionDevice *sd) {
r = session_device_open(sd, true);
if (r < 0)
return r;
- close_nointr(sd->fd);
+ /* For evdev devices, the file descriptor might be left
+ * uninitialized. This might happen while resuming into a
+ * session and logind has been restarted right before. */
+ safe_close(sd->fd);
sd->fd = r;
break;
case DEVICE_TYPE_UNKNOWN:
@@ -341,7 +346,7 @@ err_dev:
return r;
}
-int session_device_new(Session *s, dev_t dev, SessionDevice **out) {
+int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
SessionDevice *sd;
int r;
@@ -370,22 +375,24 @@ int session_device_new(Session *s, dev_t dev, SessionDevice **out) {
goto error;
}
- /* Open the device for the first time. We need a valid fd to pass back
- * to the caller. If the session is not active, this _might_ immediately
- * revoke access and thus invalidate the fd. But this is still needed
- * to pass a valid fd back. */
- sd->active = session_is_active(s);
- r = session_device_open(sd, sd->active);
- if (r < 0) {
- /* EINVAL _may_ mean a master is active; retry inactive */
- if (sd->active && r == -EINVAL) {
- sd->active = false;
- r = session_device_open(sd, false);
+ if (open_device) {
+ /* Open the device for the first time. We need a valid fd to pass back
+ * to the caller. If the session is not active, this _might_ immediately
+ * revoke access and thus invalidate the fd. But this is still needed
+ * to pass a valid fd back. */
+ sd->active = session_is_active(s);
+ r = session_device_open(sd, sd->active);
+ if (r < 0) {
+ /* EINVAL _may_ mean a master is active; retry inactive */
+ if (sd->active && r == -EINVAL) {
+ sd->active = false;
+ r = session_device_open(sd, false);
+ }
+ if (r < 0)
+ goto error;
}
- if (r < 0)
- goto error;
+ sd->fd = r;
}
- sd->fd = r;
LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
@@ -435,15 +442,16 @@ void session_device_complete_pause(SessionDevice *sd) {
void session_device_resume_all(Session *s) {
SessionDevice *sd;
Iterator i;
- int r;
assert(s);
HASHMAP_FOREACH(sd, s->devices, i) {
if (!sd->active) {
- r = session_device_start(sd);
- if (!r)
- session_device_notify(sd, SESSION_DEVICE_RESUME);
+ if (session_device_start(sd) < 0)
+ continue;
+ if (session_device_save(sd) < 0)
+ continue;
+ session_device_notify(sd, SESSION_DEVICE_RESUME);
}
}
}
@@ -478,3 +486,36 @@ unsigned int session_device_try_pause_all(Session *s) {
return num_pending;
}
+
+int session_device_save(SessionDevice *sd) {
+ _cleanup_free_ char *state = NULL;
+ 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.
+ *
+ * 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;
+
+ return sd_pid_notify_with_fds(0, false, state, &sd->fd, 1);
+}
+
+void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
+ assert(fd > 0);
+ assert(sd);
+ assert(sd->fd < 0);
+ assert(!sd->active);
+
+ sd->fd = fd;
+ sd->active = active;
+}
diff --git a/src/login/logind-session-device.h b/src/login/logind-session-device.h
index 7c8503583f..83aef1e18c 100644
--- a/src/login/logind-session-device.h
+++ b/src/login/logind-session-device.h
@@ -44,10 +44,13 @@ struct SessionDevice {
LIST_FIELDS(struct SessionDevice, sd_by_device);
};
-int session_device_new(Session *s, dev_t dev, SessionDevice **out);
+int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out);
void session_device_free(SessionDevice *sd);
void session_device_complete_pause(SessionDevice *sd);
void session_device_resume_all(Session *s);
void session_device_pause_all(Session *s);
unsigned int session_device_try_pause_all(Session *s);
+
+int session_device_save(SessionDevice *sd);
+void session_device_attach_fd(SessionDevice *sd, int fd, bool active);
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index 4a168906d6..42dfecaffb 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -152,6 +152,18 @@ void session_set_user(Session *s, User *u) {
LIST_PREPEND(sessions_by_user, u->sessions, s);
}
+static void session_save_devices(Session *s, FILE *f) {
+ SessionDevice *sd;
+ Iterator i;
+
+ if (!hashmap_isempty(s->devices)) {
+ fprintf(f, "DEVICES=");
+ HASHMAP_FOREACH(sd, s->devices, i)
+ fprintf(f, "%u:%u ", major(sd->dev), minor(sd->dev));
+ fprintf(f, "\n");
+ }
+}
+
int session_save(Session *s) {
_cleanup_free_ char *temp_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
@@ -281,8 +293,10 @@ int session_save(Session *s) {
s->timestamp.realtime,
s->timestamp.monotonic);
- if (s->controller)
+ if (s->controller) {
fprintf(f, "CONTROLLER=%s\n", s->controller);
+ session_save_devices(s, f);
+ }
r = fflush_and_check(f);
if (r < 0)
@@ -304,6 +318,43 @@ fail:
return log_error_errno(r, "Failed to save session data %s: %m", s->state_file);
}
+static int session_load_devices(Session *s, const char *devices) {
+ const char *p;
+ int r = 0;
+
+ assert(s);
+
+ for (p = devices;;) {
+ _cleanup_free_ char *word = NULL;
+ SessionDevice *sd;
+ dev_t dev;
+ int k;
+
+ k = extract_first_word(&p, &word, NULL, 0);
+ if (k == 0)
+ break;
+ if (k < 0) {
+ r = k;
+ break;
+ }
+
+ k = parse_dev(word, &dev);
+ if (k < 0) {
+ r = k;
+ continue;
+ }
+
+ /* The file descriptors for loaded devices will be reattached later. */
+ k = session_device_new(s, dev, false, &sd);
+ if (k < 0)
+ r = k;
+ }
+
+ if (r < 0)
+ log_error_errno(r, "Loading session devices for session %s failed: %m", s->id);
+
+ return r;
+}
int session_load(Session *s) {
_cleanup_free_ char *remote = NULL,
@@ -317,7 +368,9 @@ int session_load(Session *s) {
*uid = NULL,
*realtime = NULL,
*monotonic = NULL,
- *controller = NULL;
+ *controller = NULL,
+ *active = NULL,
+ *devices = NULL;
int k, r;
@@ -345,6 +398,8 @@ int session_load(Session *s) {
"REALTIME", &realtime,
"MONOTONIC", &monotonic,
"CONTROLLER", &controller,
+ "ACTIVE", &active,
+ "DEVICES", &devices,
NULL);
if (r < 0)
@@ -447,10 +502,17 @@ int session_load(Session *s) {
if (monotonic)
timestamp_deserialize(monotonic, &s->timestamp.monotonic);
+ if (active) {
+ k = parse_boolean(active);
+ if (k >= 0)
+ s->was_active = k;
+ }
+
if (controller) {
- if (bus_name_has_owner(s->manager->bus, controller, NULL) > 0)
- session_set_controller(s, controller, false);
- else
+ if (bus_name_has_owner(s->manager->bus, controller, NULL) > 0) {
+ session_set_controller(s, controller, false, false);
+ session_load_devices(s, devices);
+ } else
session_restore_vt(s);
}
@@ -1170,7 +1232,7 @@ static int on_bus_track(sd_bus_track *track, void *userdata) {
return 0;
}
-int session_set_controller(Session *s, const char *sender, bool force) {
+int session_set_controller(Session *s, const char *sender, bool force, bool prepare) {
_cleanup_free_ char *name = NULL;
int r;
@@ -1202,11 +1264,14 @@ int session_set_controller(Session *s, const char *sender, bool force) {
* Note that we reset the VT on ReleaseControl() and if the controller
* exits.
* If logind crashes/restarts, we restore the controller during restart
- * or reset the VT in case it crashed/exited, too. */
- r = session_prepare_vt(s);
- if (r < 0) {
- s->track = sd_bus_track_unref(s->track);
- return r;
+ * (without preparing the VT since the controller has probably overridden
+ * VT state by now) or reset the VT in case it crashed/exited, too. */
+ if (prepare) {
+ r = session_prepare_vt(s);
+ if (r < 0) {
+ s->track = sd_bus_track_unref(s->track);
+ return r;
+ }
}
session_release_controller(s, true);
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
index ffb7cd2d41..12b9d86f55 100644
--- a/src/login/logind-session.h
+++ b/src/login/logind-session.h
@@ -111,6 +111,8 @@ struct Session {
bool started:1;
bool stopping:1;
+ bool was_active:1;
+
sd_bus_message *create_message;
sd_event_source *timer_event_source;
@@ -176,7 +178,7 @@ void session_restore_vt(Session *s);
void session_leave_vt(Session *s);
bool session_is_controller(Session *s, const char *sender);
-int session_set_controller(Session *s, const char *sender, bool force);
+int session_set_controller(Session *s, const char *sender, bool force, bool prepare);
void session_drop_controller(Session *s);
int bus_session_method_activate(sd_bus_message *message, void *userdata, sd_bus_error *error);
diff --git a/src/login/logind.c b/src/login/logind.c
index 1e2acc838b..fb70972b80 100644
--- a/src/login/logind.c
+++ b/src/login/logind.c
@@ -409,10 +409,71 @@ static int manager_enumerate_users(Manager *m) {
return r;
}
+static int manager_attach_fds(Manager *m) {
+ _cleanup_strv_free_ char **fdnames = NULL;
+ int n, i, fd;
+
+ /* Upon restart, PID1 will send us back all fds of session devices
+ * that we previously opened. Each file descriptor is associated
+ * with a given session. The session ids are passed through FDNAMES. */
+
+ n = sd_listen_fds_with_names(true, &fdnames);
+ if (n <= 0)
+ return n;
+
+ for (i = 0; i < n; i++) {
+ struct stat st;
+ SessionDevice *sd;
+ Session *s;
+ char *id;
+
+ fd = SD_LISTEN_FDS_START + i;
+
+ id = startswith(fdnames[i], "session-");
+ if (!id)
+ continue;
+
+ s = hashmap_get(m->sessions, id);
+ if (!s) {
+ /* If the session doesn't exist anymore, the associated session
+ * device attached to this fd doesn't either. Let's simply close
+ * this fd. */
+ log_debug("Failed to attach fd for unknown session: %s", id);
+ close_nointr(fd);
+ continue;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ /* The device is allowed to go away at a random point, in which
+ * case fstat failing is expected. */
+ log_debug_errno(errno, "Failed to stat device fd for session %s: %m", id);
+ close_nointr(fd);
+ continue;
+ }
+
+ sd = hashmap_get(s->devices, &st.st_rdev);
+ if (!sd) {
+ /* Weird we got an fd for a session device which wasn't
+ * recorded in the session state file... */
+ log_warning("Got fd for missing session device [%u:%u] in session %s",
+ major(st.st_rdev), minor(st.st_rdev), s->id);
+ close_nointr(fd);
+ continue;
+ }
+
+ log_debug("Attaching fd to session device [%u:%u] for session %s",
+ major(st.st_rdev), minor(st.st_rdev), s->id);
+
+ session_device_attach_fd(sd, fd, s->was_active);
+ }
+
+ return 0;
+}
+
static int manager_enumerate_sessions(Manager *m) {
_cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
- int r = 0;
+ int r = 0, k;
assert(m);
@@ -427,7 +488,6 @@ static int manager_enumerate_sessions(Manager *m) {
FOREACH_DIRENT(de, d, return -errno) {
struct Session *s;
- int k;
if (!dirent_is_file(de))
continue;
@@ -441,7 +501,6 @@ static int manager_enumerate_sessions(Manager *m) {
k = manager_add_session(m, de->d_name, &s);
if (k < 0) {
log_error_errno(k, "Failed to add session by file name %s: %m", de->d_name);
-
r = k;
continue;
}
@@ -453,6 +512,12 @@ static int manager_enumerate_sessions(Manager *m) {
r = k;
}
+ /* We might be restarted and PID1 could have sent us back the
+ * session device fds we previously saved. */
+ k = manager_attach_fds(m);
+ if (k < 0)
+ log_warning_errno(k, "Failed to reattach session device fds: %m");
+
return r;
}
diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c
index 9194772fca..1b29b2ea87 100644
--- a/src/test/test-parse-util.c
+++ b/src/test/test-parse-util.c
@@ -593,6 +593,19 @@ static void test_parse_nice(void) {
assert_se(parse_nice("+20", &n) == -ERANGE);
}
+static void test_parse_dev(void) {
+ dev_t dev;
+
+ assert_se(parse_dev("0", &dev) == -EINVAL);
+ assert_se(parse_dev("5", &dev) == -EINVAL);
+ assert_se(parse_dev("5:", &dev) == -EINVAL);
+ assert_se(parse_dev(":5", &dev) == -EINVAL);
+#if SIZEOF_DEV_T < 8
+ assert_se(parse_dev("4294967295:4294967295", &dev) == -EINVAL);
+#endif
+ assert_se(parse_dev("8:11", &dev) >= 0 && major(dev) == 8 && minor(dev) == 11);
+}
+
int main(int argc, char *argv[]) {
log_parse_environment();
log_open();
@@ -611,6 +624,7 @@ int main(int argc, char *argv[]) {
test_parse_percent();
test_parse_percent_unbounded();
test_parse_nice();
+ test_parse_dev();
return 0;
}
diff --git a/units/systemd-logind.service.in b/units/systemd-logind.service.in
index e20a3ad057..22d74f51d9 100644
--- a/units/systemd-logind.service.in
+++ b/units/systemd-logind.service.in
@@ -31,6 +31,7 @@ RestrictNamespaces=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
SystemCallFilter=~@clock @cpu-emulation @debug @keyring @module @obsolete @raw-io @reboot @swap
SystemCallArchitectures=native
+FileDescriptorStoreMax=512
# Increase the default a bit in order to allow many simultaneous
# logins since we keep one fd open per session.