Systemd/src/login/logind-seat.c

662 lines
16 KiB
C
Raw Normal View History

/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2011 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/vt.h>
2011-05-24 04:20:35 +02:00
#include <string.h>
2013-11-05 01:10:21 +01:00
#include "sd-id128.h"
#include "sd-messages.h"
2011-05-24 00:19:22 +02:00
#include "logind-seat.h"
2011-05-24 04:20:35 +02:00
#include "logind-acl.h"
#include "util.h"
#include "mkdir.h"
2012-05-07 21:36:12 +02:00
#include "path-util.h"
Seat *seat_new(Manager *m, const char *id) {
Seat *s;
assert(m);
assert(id);
s = new0(Seat, 1);
if (!s)
return NULL;
2011-06-24 18:50:50 +02:00
s->state_file = strappend("/run/systemd/seats/", id);
if (!s->state_file) {
free(s);
return NULL;
}
s->id = basename(s->state_file);
2011-05-25 00:55:58 +02:00
s->manager = m;
if (hashmap_put(m->seats, s->id, s) < 0) {
2011-05-25 00:55:58 +02:00
free(s->state_file);
free(s);
return NULL;
}
return s;
}
void seat_free(Seat *s) {
assert(s);
2011-05-25 00:55:58 +02:00
if (s->in_gc_queue)
LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
2011-05-25 00:55:58 +02:00
while (s->sessions)
session_free(s->sessions);
assert(!s->active);
while (s->devices)
device_free(s->devices);
hashmap_remove(s->manager->seats, s->id);
logind: introduce session "positions" logind has no concept of session ordering. Sessions have a unique name, some attributes about the capabilities and that's already it. There is currently no stable+total order on sessions. If we use the logind API to switch between sessions, we are faced with an unordered list of sessions we have no clue of. This used to be no problem on seats with VTs or on seats with only a single active session. However, with the introduction of multi-session capability for seats without VTs, we need to find a way to order sessions in a stable way. This patch introduces session "positions". A position is a simple integer assigned to a session which is never changed implicitly (currently, we also don't change it explicitly, but that may be changed someday). For seats with VTs, we force the position to be the same as the VTnr. Without VTs, we simply find the lowest unassigned number and use it as position. If position-assignment fails or if, for any reason, we decide to not assign a position to a session, the position is set to 0 (which is treated as invalid position). During session_load() or if two sessions have the same VTnr, we may end up with two sessions with the same position (this shouldn't happen, but lets be fail-safe in case some other part of the stack fails). This case is dealt with gracefully by ignoring any session but the first session assigned to the position. Thus, session->pos is a hint, seat->positions[i] is the definite position-assignment. Always verify both match in case you need to modify them! Additionally, we introduce SwitchTo(unsigned int) on the seat-dbus-API. You can call it with any integer value != 0 and logind will try to switch to the request position. If you implement a compositor or any other session-controller, you simply watch for ctrl+alt+F1 to F12 and call SwitchTo(Fx). logind will figure a way out deal with this number. For convenience, we also introduce SwitchToNext/Previous(). It should be called on ctrl+alt+Left/Right (like the kernel-console used to support). Note that the public API (SwitchTo*()) is *not* bound to the underlying logic that is implemented now. We don't export "session-positions" on the dbus/C API! They are an implementation detail. Instead, the SwitchTo*() API is supposed to be a hint to let logind choose the session-switching logic. Any foreground session-controller is free to enumerate/order existing sessions according to their needs and call Session.Activate() manually. But the SwitchTo*() API provides a uniform behavior across session-controllers. Background: Session-switching keys depend on the active keymap. The XKB specification provides the XKB_KEY_XF86Switch_VT_1-12 key-symbols which have to be mapped by all keymaps to allow session-switching. It is usually bound to ctrl+alt+Fx but may be set differently. A compositor passes any keyboard input to XKB before passing it to clients. In case a key-press invokes the XKB_KEY_XF86Switch_VT_x action, the keypress is *not* forwarded to clients, but instead a session-switch is scheduled. This actually prevents us from handling these keys outside of the session. If an active compositor has a keymap with a different mapping of these keys, and logind itself tries to catch these combinations, we end up with the key-press sent to the compositor's clients *and* handled by logind. This is *bad* and we must avoid this. The only situation where a background process is allowed to handle key-presses is debugging and emergency-keys. In these cases, we don't care for keymap mismatches and accept the double-event. Another exception is unmapped keys like PowerOff/Suspend (even though this one is controversial).
2013-11-30 11:39:48 +01:00
free(s->positions);
free(s->state_file);
free(s);
}
int seat_save(Seat *s) {
2013-11-05 01:10:21 +01:00
_cleanup_free_ char *temp_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(s);
if (!s->started)
return 0;
r = mkdir_safe_label("/run/systemd/seats", 0755, 0, 0);
if (r < 0)
2011-05-25 00:55:58 +02:00
goto finish;
r = fopen_temporary(s->state_file, &f, &temp_path);
if (r < 0)
goto finish;
2011-05-25 00:55:58 +02:00
fchmod(fileno(f), 0644);
fprintf(f,
2011-05-25 00:55:58 +02:00
"# This is private data. Do not parse.\n"
"IS_SEAT0=%i\n"
"CAN_MULTI_SESSION=%i\n"
"CAN_TTY=%i\n"
"CAN_GRAPHICAL=%i\n",
seat_is_seat0(s),
seat_can_multi_session(s),
seat_can_tty(s),
seat_can_graphical(s));
if (s->active) {
assert(s->active->user);
fprintf(f,
"ACTIVE=%s\n"
"ACTIVE_UID=%lu\n",
s->active->id,
(unsigned long) s->active->user->uid);
}
if (s->sessions) {
Session *i;
fputs("SESSIONS=", f);
LIST_FOREACH(sessions_by_seat, i, s->sessions) {
2011-05-25 00:55:58 +02:00
fprintf(f,
"%s%c",
i->id,
i->sessions_by_seat_next ? ' ' : '\n');
}
fputs("UIDS=", f);
LIST_FOREACH(sessions_by_seat, i, s->sessions)
fprintf(f,
2011-05-25 00:55:58 +02:00
"%lu%c",
(unsigned long) i->user->uid,
i->sessions_by_seat_next ? ' ' : '\n');
}
fflush(f);
2011-05-25 00:55:58 +02:00
if (ferror(f) || rename(temp_path, s->state_file) < 0) {
r = -errno;
unlink(s->state_file);
2011-05-25 00:55:58 +02:00
unlink(temp_path);
}
2011-05-25 00:55:58 +02:00
finish:
if (r < 0)
log_error("Failed to save seat data for %s: %s", s->id, strerror(-r));
return r;
}
int seat_load(Seat *s) {
assert(s);
2011-06-17 15:59:18 +02:00
/* There isn't actually anything to read here ... */
return 0;
}
static int vt_allocate(unsigned int vtnr) {
2013-11-05 01:10:21 +01:00
_cleanup_free_ char *p = NULL;
_cleanup_close_ int fd = -1;
assert(vtnr >= 1);
if (asprintf(&p, "/dev/tty%u", vtnr) < 0)
return -ENOMEM;
fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
2013-11-05 01:10:21 +01:00
if (fd < 0)
return -errno;
2013-11-05 01:10:21 +01:00
return 0;
}
int seat_preallocate_vts(Seat *s) {
2011-05-26 02:21:16 +02:00
int r = 0;
unsigned i;
assert(s);
assert(s->manager);
log_debug("Preallocating VTs...");
if (s->manager->n_autovts <= 0)
return 0;
if (!seat_has_vts(s))
return 0;
for (i = 1; i <= s->manager->n_autovts; i++) {
int q;
q = vt_allocate(i);
2011-05-25 00:55:58 +02:00
if (q < 0) {
log_error("Failed to preallocate VT %i: %s", i, strerror(-q));
r = q;
2011-05-25 00:55:58 +02:00
}
}
return r;
}
2011-05-24 04:20:35 +02:00
int seat_apply_acls(Seat *s, Session *old_active) {
int r;
2011-05-24 04:20:35 +02:00
assert(s);
2011-05-24 04:20:35 +02:00
r = devnode_acl_all(s->manager->udev,
s->id,
false,
!!old_active, old_active ? old_active->user->uid : 0,
!!s->active, s->active ? s->active->user->uid : 0);
2011-05-24 04:20:35 +02:00
if (r < 0)
log_error("Failed to apply ACLs: %s", strerror(-r));
return r;
}
int seat_set_active(Seat *s, Session *session) {
Session *old_active;
assert(s);
2011-06-21 22:29:25 +02:00
assert(!session || session->seat == s);
if (session == s->active)
return 0;
old_active = s->active;
s->active = session;
if (old_active) {
logind: introduce session-devices A session-device is a device that is bound to a seat and used by a session-controller to run the session. This currently includes DRM, fbdev and evdev devices. A session-device can be created via RequestDevice() on the dbus API of the session. You can drop it via ReleaseDevice() again. Once the session is destroyed or you drop control of the session, all session-devices are automatically destroyed. Session devices follow the session "active" state. A device can be active/running or inactive/paused. Whenever a session is not the active session, no session-device of it can be active. That is, if a session is not in foreground, all session-devices are paused. Whenever a session becomes active, all devices are resumed/activated by logind. If it fails, a device may stay paused. With every session-device you request, you also get a file-descriptor back. logind keeps a copy of this fd and uses kernel specific calls to pause/resume the file-descriptors. For example, a DRM fd is muted by logind as long as a given session is not active. Hence, the fd of the application is also muted. Once the session gets active, logind unmutes the fd and the application will get DRM access again. This, however, requires kernel support. DRM devices provide DRM-Master for synchronization, evdev devices have EVIOCREVOKE (pending on linux-input-ML). fbdev devices do not provide such synchronization methods (and never will). Note that for evdev devices, we call EVIOCREVOKE once a session gets inactive. However, this cannot be undone (the fd is still valid but mostly unusable). So we reopen a new fd once the session is activated and send it together with the ResumeDevice() signal. With this infrastructure in place, compositors can now run without CAP_SYS_ADMIN (that is, without being root). They use RequestControl() to acquire a session and listen for devices via udev_monitor. For every device they want to open, they call RequestDevice() on logind. This returns a fd which they can use now. They no longer have to open the devices themselves or call any privileged ioctls. This is all done by logind. Session-switches are still bound to VTs. Hence, compositors will get notified via the usual VT mechanisms and can cleanup their state. Once the VT switch is acknowledged as usual, logind will get notified via sysfs and pause the old-session's devices and resume the devices of the new session. To allow using this infrastructure with systems without VTs, we provide notification signals. logind sends PauseDevice("force") dbus signals to the current session controller for every device that it pauses. And it sends ResumeDevice signals for every device that it resumes. For seats with VTs this is sent _after_ the VT switch is acknowledged. Because the compositor already acknowledged that it cleaned-up all devices. However, for seats without VTs, this is used to notify the active compositor that the session is about to be deactivated. That is, logind sends PauseDevice("force") for each active device and then performs the session-switch. The session-switch changes the "Active" property of the session which can be monitored by the compositor. The new session is activated and the ResumeDevice events are sent. For seats without VTs, this is a forced session-switch. As this is not backwards-compatible (xserver actually crashes, weston drops the related devices, ..) we also provide an acknowledged session-switch. Note that this is never used for sessions with VTs. You use the acknowledged VT-switch on these seats. An acknowledged session switch sends PauseDevice("pause") instead of PauseDevice("force") to the active session. It schedules a short timeout and waits for the session to acknowledge each of them with PauseDeviceComplete(). Once all are acknowledged, or the session ran out of time, a PauseDevice("force") is sent for all remaining active devices and the session switch is performed. Note that this is only partially implemented, yet, as we don't allow multi-session without VTs, yet. A follow up commit will hook it up and implemented the acknowledgements+timeout. The implementation is quite simple. We use major/minor exclusively to identify devices on the bus. On RequestDevice() we retrieve the udev_device from the major/minor and search for an existing "Device" object. If no exists, we create it. This guarantees us that we are notified whenever the device changes seats or is removed. We create a new SessionDevice object and link it to the related Session and Device. Session->devices is a hashtable to lookup SessionDevice objects via major/minor. Device->session_devices is a linked list so we can release all linked session-devices once a device vanishes. Now we only have to hook this up in seat_set_active() so we correctly change device states during session-switches. As mentioned earlier, these are forced state-changes as VTs are currently used exclusively for multi-session implementations. Everything else are hooks to release all session-devices once the controller changes or a session is closed or removed.
2013-09-17 23:39:04 +02:00
session_device_pause_all(old_active);
2013-11-05 01:10:21 +01:00
session_send_changed(old_active, "Active", NULL);
}
logind: introduce session-devices A session-device is a device that is bound to a seat and used by a session-controller to run the session. This currently includes DRM, fbdev and evdev devices. A session-device can be created via RequestDevice() on the dbus API of the session. You can drop it via ReleaseDevice() again. Once the session is destroyed or you drop control of the session, all session-devices are automatically destroyed. Session devices follow the session "active" state. A device can be active/running or inactive/paused. Whenever a session is not the active session, no session-device of it can be active. That is, if a session is not in foreground, all session-devices are paused. Whenever a session becomes active, all devices are resumed/activated by logind. If it fails, a device may stay paused. With every session-device you request, you also get a file-descriptor back. logind keeps a copy of this fd and uses kernel specific calls to pause/resume the file-descriptors. For example, a DRM fd is muted by logind as long as a given session is not active. Hence, the fd of the application is also muted. Once the session gets active, logind unmutes the fd and the application will get DRM access again. This, however, requires kernel support. DRM devices provide DRM-Master for synchronization, evdev devices have EVIOCREVOKE (pending on linux-input-ML). fbdev devices do not provide such synchronization methods (and never will). Note that for evdev devices, we call EVIOCREVOKE once a session gets inactive. However, this cannot be undone (the fd is still valid but mostly unusable). So we reopen a new fd once the session is activated and send it together with the ResumeDevice() signal. With this infrastructure in place, compositors can now run without CAP_SYS_ADMIN (that is, without being root). They use RequestControl() to acquire a session and listen for devices via udev_monitor. For every device they want to open, they call RequestDevice() on logind. This returns a fd which they can use now. They no longer have to open the devices themselves or call any privileged ioctls. This is all done by logind. Session-switches are still bound to VTs. Hence, compositors will get notified via the usual VT mechanisms and can cleanup their state. Once the VT switch is acknowledged as usual, logind will get notified via sysfs and pause the old-session's devices and resume the devices of the new session. To allow using this infrastructure with systems without VTs, we provide notification signals. logind sends PauseDevice("force") dbus signals to the current session controller for every device that it pauses. And it sends ResumeDevice signals for every device that it resumes. For seats with VTs this is sent _after_ the VT switch is acknowledged. Because the compositor already acknowledged that it cleaned-up all devices. However, for seats without VTs, this is used to notify the active compositor that the session is about to be deactivated. That is, logind sends PauseDevice("force") for each active device and then performs the session-switch. The session-switch changes the "Active" property of the session which can be monitored by the compositor. The new session is activated and the ResumeDevice events are sent. For seats without VTs, this is a forced session-switch. As this is not backwards-compatible (xserver actually crashes, weston drops the related devices, ..) we also provide an acknowledged session-switch. Note that this is never used for sessions with VTs. You use the acknowledged VT-switch on these seats. An acknowledged session switch sends PauseDevice("pause") instead of PauseDevice("force") to the active session. It schedules a short timeout and waits for the session to acknowledge each of them with PauseDeviceComplete(). Once all are acknowledged, or the session ran out of time, a PauseDevice("force") is sent for all remaining active devices and the session switch is performed. Note that this is only partially implemented, yet, as we don't allow multi-session without VTs, yet. A follow up commit will hook it up and implemented the acknowledgements+timeout. The implementation is quite simple. We use major/minor exclusively to identify devices on the bus. On RequestDevice() we retrieve the udev_device from the major/minor and search for an existing "Device" object. If no exists, we create it. This guarantees us that we are notified whenever the device changes seats or is removed. We create a new SessionDevice object and link it to the related Session and Device. Session->devices is a hashtable to lookup SessionDevice objects via major/minor. Device->session_devices is a linked list so we can release all linked session-devices once a device vanishes. Now we only have to hook this up in seat_set_active() so we correctly change device states during session-switches. As mentioned earlier, these are forced state-changes as VTs are currently used exclusively for multi-session implementations. Everything else are hooks to release all session-devices once the controller changes or a session is closed or removed.
2013-09-17 23:39:04 +02:00
seat_apply_acls(s, old_active);
logind: introduce session-devices A session-device is a device that is bound to a seat and used by a session-controller to run the session. This currently includes DRM, fbdev and evdev devices. A session-device can be created via RequestDevice() on the dbus API of the session. You can drop it via ReleaseDevice() again. Once the session is destroyed or you drop control of the session, all session-devices are automatically destroyed. Session devices follow the session "active" state. A device can be active/running or inactive/paused. Whenever a session is not the active session, no session-device of it can be active. That is, if a session is not in foreground, all session-devices are paused. Whenever a session becomes active, all devices are resumed/activated by logind. If it fails, a device may stay paused. With every session-device you request, you also get a file-descriptor back. logind keeps a copy of this fd and uses kernel specific calls to pause/resume the file-descriptors. For example, a DRM fd is muted by logind as long as a given session is not active. Hence, the fd of the application is also muted. Once the session gets active, logind unmutes the fd and the application will get DRM access again. This, however, requires kernel support. DRM devices provide DRM-Master for synchronization, evdev devices have EVIOCREVOKE (pending on linux-input-ML). fbdev devices do not provide such synchronization methods (and never will). Note that for evdev devices, we call EVIOCREVOKE once a session gets inactive. However, this cannot be undone (the fd is still valid but mostly unusable). So we reopen a new fd once the session is activated and send it together with the ResumeDevice() signal. With this infrastructure in place, compositors can now run without CAP_SYS_ADMIN (that is, without being root). They use RequestControl() to acquire a session and listen for devices via udev_monitor. For every device they want to open, they call RequestDevice() on logind. This returns a fd which they can use now. They no longer have to open the devices themselves or call any privileged ioctls. This is all done by logind. Session-switches are still bound to VTs. Hence, compositors will get notified via the usual VT mechanisms and can cleanup their state. Once the VT switch is acknowledged as usual, logind will get notified via sysfs and pause the old-session's devices and resume the devices of the new session. To allow using this infrastructure with systems without VTs, we provide notification signals. logind sends PauseDevice("force") dbus signals to the current session controller for every device that it pauses. And it sends ResumeDevice signals for every device that it resumes. For seats with VTs this is sent _after_ the VT switch is acknowledged. Because the compositor already acknowledged that it cleaned-up all devices. However, for seats without VTs, this is used to notify the active compositor that the session is about to be deactivated. That is, logind sends PauseDevice("force") for each active device and then performs the session-switch. The session-switch changes the "Active" property of the session which can be monitored by the compositor. The new session is activated and the ResumeDevice events are sent. For seats without VTs, this is a forced session-switch. As this is not backwards-compatible (xserver actually crashes, weston drops the related devices, ..) we also provide an acknowledged session-switch. Note that this is never used for sessions with VTs. You use the acknowledged VT-switch on these seats. An acknowledged session switch sends PauseDevice("pause") instead of PauseDevice("force") to the active session. It schedules a short timeout and waits for the session to acknowledge each of them with PauseDeviceComplete(). Once all are acknowledged, or the session ran out of time, a PauseDevice("force") is sent for all remaining active devices and the session switch is performed. Note that this is only partially implemented, yet, as we don't allow multi-session without VTs, yet. A follow up commit will hook it up and implemented the acknowledgements+timeout. The implementation is quite simple. We use major/minor exclusively to identify devices on the bus. On RequestDevice() we retrieve the udev_device from the major/minor and search for an existing "Device" object. If no exists, we create it. This guarantees us that we are notified whenever the device changes seats or is removed. We create a new SessionDevice object and link it to the related Session and Device. Session->devices is a hashtable to lookup SessionDevice objects via major/minor. Device->session_devices is a linked list so we can release all linked session-devices once a device vanishes. Now we only have to hook this up in seat_set_active() so we correctly change device states during session-switches. As mentioned earlier, these are forced state-changes as VTs are currently used exclusively for multi-session implementations. Everything else are hooks to release all session-devices once the controller changes or a session is closed or removed.
2013-09-17 23:39:04 +02:00
if (session && session->started) {
2013-11-05 01:10:21 +01:00
session_send_changed(session, "Active", NULL);
logind: introduce session-devices A session-device is a device that is bound to a seat and used by a session-controller to run the session. This currently includes DRM, fbdev and evdev devices. A session-device can be created via RequestDevice() on the dbus API of the session. You can drop it via ReleaseDevice() again. Once the session is destroyed or you drop control of the session, all session-devices are automatically destroyed. Session devices follow the session "active" state. A device can be active/running or inactive/paused. Whenever a session is not the active session, no session-device of it can be active. That is, if a session is not in foreground, all session-devices are paused. Whenever a session becomes active, all devices are resumed/activated by logind. If it fails, a device may stay paused. With every session-device you request, you also get a file-descriptor back. logind keeps a copy of this fd and uses kernel specific calls to pause/resume the file-descriptors. For example, a DRM fd is muted by logind as long as a given session is not active. Hence, the fd of the application is also muted. Once the session gets active, logind unmutes the fd and the application will get DRM access again. This, however, requires kernel support. DRM devices provide DRM-Master for synchronization, evdev devices have EVIOCREVOKE (pending on linux-input-ML). fbdev devices do not provide such synchronization methods (and never will). Note that for evdev devices, we call EVIOCREVOKE once a session gets inactive. However, this cannot be undone (the fd is still valid but mostly unusable). So we reopen a new fd once the session is activated and send it together with the ResumeDevice() signal. With this infrastructure in place, compositors can now run without CAP_SYS_ADMIN (that is, without being root). They use RequestControl() to acquire a session and listen for devices via udev_monitor. For every device they want to open, they call RequestDevice() on logind. This returns a fd which they can use now. They no longer have to open the devices themselves or call any privileged ioctls. This is all done by logind. Session-switches are still bound to VTs. Hence, compositors will get notified via the usual VT mechanisms and can cleanup their state. Once the VT switch is acknowledged as usual, logind will get notified via sysfs and pause the old-session's devices and resume the devices of the new session. To allow using this infrastructure with systems without VTs, we provide notification signals. logind sends PauseDevice("force") dbus signals to the current session controller for every device that it pauses. And it sends ResumeDevice signals for every device that it resumes. For seats with VTs this is sent _after_ the VT switch is acknowledged. Because the compositor already acknowledged that it cleaned-up all devices. However, for seats without VTs, this is used to notify the active compositor that the session is about to be deactivated. That is, logind sends PauseDevice("force") for each active device and then performs the session-switch. The session-switch changes the "Active" property of the session which can be monitored by the compositor. The new session is activated and the ResumeDevice events are sent. For seats without VTs, this is a forced session-switch. As this is not backwards-compatible (xserver actually crashes, weston drops the related devices, ..) we also provide an acknowledged session-switch. Note that this is never used for sessions with VTs. You use the acknowledged VT-switch on these seats. An acknowledged session switch sends PauseDevice("pause") instead of PauseDevice("force") to the active session. It schedules a short timeout and waits for the session to acknowledge each of them with PauseDeviceComplete(). Once all are acknowledged, or the session ran out of time, a PauseDevice("force") is sent for all remaining active devices and the session switch is performed. Note that this is only partially implemented, yet, as we don't allow multi-session without VTs, yet. A follow up commit will hook it up and implemented the acknowledgements+timeout. The implementation is quite simple. We use major/minor exclusively to identify devices on the bus. On RequestDevice() we retrieve the udev_device from the major/minor and search for an existing "Device" object. If no exists, we create it. This guarantees us that we are notified whenever the device changes seats or is removed. We create a new SessionDevice object and link it to the related Session and Device. Session->devices is a hashtable to lookup SessionDevice objects via major/minor. Device->session_devices is a linked list so we can release all linked session-devices once a device vanishes. Now we only have to hook this up in seat_set_active() so we correctly change device states during session-switches. As mentioned earlier, these are forced state-changes as VTs are currently used exclusively for multi-session implementations. Everything else are hooks to release all session-devices once the controller changes or a session is closed or removed.
2013-09-17 23:39:04 +02:00
session_device_resume_all(session);
}
if (!session || session->started)
2013-11-05 01:10:21 +01:00
seat_send_changed(s, "ActiveSession", NULL);
2011-06-24 18:50:50 +02:00
seat_save(s);
if (session) {
2011-06-24 18:50:50 +02:00
session_save(session);
user_save(session->user);
}
2011-06-24 18:50:50 +02:00
if (old_active) {
2011-06-24 18:50:50 +02:00
session_save(old_active);
if (!session || session->user != old_active->user)
user_save(old_active->user);
}
2011-06-24 18:50:50 +02:00
return 0;
}
logind: introduce session "positions" logind has no concept of session ordering. Sessions have a unique name, some attributes about the capabilities and that's already it. There is currently no stable+total order on sessions. If we use the logind API to switch between sessions, we are faced with an unordered list of sessions we have no clue of. This used to be no problem on seats with VTs or on seats with only a single active session. However, with the introduction of multi-session capability for seats without VTs, we need to find a way to order sessions in a stable way. This patch introduces session "positions". A position is a simple integer assigned to a session which is never changed implicitly (currently, we also don't change it explicitly, but that may be changed someday). For seats with VTs, we force the position to be the same as the VTnr. Without VTs, we simply find the lowest unassigned number and use it as position. If position-assignment fails or if, for any reason, we decide to not assign a position to a session, the position is set to 0 (which is treated as invalid position). During session_load() or if two sessions have the same VTnr, we may end up with two sessions with the same position (this shouldn't happen, but lets be fail-safe in case some other part of the stack fails). This case is dealt with gracefully by ignoring any session but the first session assigned to the position. Thus, session->pos is a hint, seat->positions[i] is the definite position-assignment. Always verify both match in case you need to modify them! Additionally, we introduce SwitchTo(unsigned int) on the seat-dbus-API. You can call it with any integer value != 0 and logind will try to switch to the request position. If you implement a compositor or any other session-controller, you simply watch for ctrl+alt+F1 to F12 and call SwitchTo(Fx). logind will figure a way out deal with this number. For convenience, we also introduce SwitchToNext/Previous(). It should be called on ctrl+alt+Left/Right (like the kernel-console used to support). Note that the public API (SwitchTo*()) is *not* bound to the underlying logic that is implemented now. We don't export "session-positions" on the dbus/C API! They are an implementation detail. Instead, the SwitchTo*() API is supposed to be a hint to let logind choose the session-switching logic. Any foreground session-controller is free to enumerate/order existing sessions according to their needs and call Session.Activate() manually. But the SwitchTo*() API provides a uniform behavior across session-controllers. Background: Session-switching keys depend on the active keymap. The XKB specification provides the XKB_KEY_XF86Switch_VT_1-12 key-symbols which have to be mapped by all keymaps to allow session-switching. It is usually bound to ctrl+alt+Fx but may be set differently. A compositor passes any keyboard input to XKB before passing it to clients. In case a key-press invokes the XKB_KEY_XF86Switch_VT_x action, the keypress is *not* forwarded to clients, but instead a session-switch is scheduled. This actually prevents us from handling these keys outside of the session. If an active compositor has a keymap with a different mapping of these keys, and logind itself tries to catch these combinations, we end up with the key-press sent to the compositor's clients *and* handled by logind. This is *bad* and we must avoid this. The only situation where a background process is allowed to handle key-presses is debugging and emergency-keys. In these cases, we don't care for keymap mismatches and accept the double-event. Another exception is unmapped keys like PowerOff/Suspend (even though this one is controversial).
2013-11-30 11:39:48 +01:00
int seat_switch_to(Seat *s, unsigned int num) {
/* Public session positions skip 0 (there is only F1-F12). Maybe it
* will get reassigned in the future, so return error for now. */
if (!num)
return -EINVAL;
if (num >= s->position_count || !s->positions[num])
return -EINVAL;
return session_activate(s->positions[num]);
}
int seat_switch_to_next(Seat *s) {
unsigned int start, i;
if (!s->position_count)
return -EINVAL;
start = 1;
if (s->active && s->active->pos > 0)
start = s->active->pos;
for (i = start + 1; i < s->position_count; ++i)
if (s->positions[i])
return session_activate(s->positions[i]);
for (i = 1; i < start; ++i)
if (s->positions[i])
return session_activate(s->positions[i]);
return -EINVAL;
}
int seat_switch_to_previous(Seat *s) {
unsigned int start, i;
if (!s->position_count)
return -EINVAL;
start = 1;
if (s->active && s->active->pos > 0)
start = s->active->pos;
for (i = start - 1; i > 0; --i)
if (s->positions[i])
return session_activate(s->positions[i]);
for (i = s->position_count - 1; i > start; --i)
if (s->positions[i])
return session_activate(s->positions[i]);
return -EINVAL;
}
int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
Session *i, *new_active = NULL;
int r;
assert(s);
assert(vtnr >= 1);
if (!seat_has_vts(s))
2011-05-25 00:55:58 +02:00
return -EINVAL;
log_debug("VT changed to %u", vtnr);
LIST_FOREACH(sessions_by_seat, i, s->sessions)
if (i->vtnr == vtnr) {
2011-05-25 00:55:58 +02:00
new_active = i;
break;
}
r = seat_set_active(s, new_active);
2011-05-24 04:20:35 +02:00
manager_spawn_autovt(s->manager, vtnr);
return r;
}
2011-05-25 00:55:58 +02:00
int seat_read_active_vt(Seat *s) {
char t[64];
ssize_t k;
unsigned int vtnr;
int r;
2011-05-25 00:55:58 +02:00
assert(s);
if (!seat_has_vts(s))
2011-05-25 00:55:58 +02:00
return 0;
lseek(s->manager->console_active_fd, SEEK_SET, 0);
k = read(s->manager->console_active_fd, t, sizeof(t)-1);
if (k <= 0) {
log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
return k < 0 ? -errno : -EIO;
}
t[k] = 0;
truncate_nl(t);
if (!startswith(t, "tty")) {
log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
return -EIO;
}
r = safe_atou(t+3, &vtnr);
2011-05-25 00:55:58 +02:00
if (r < 0) {
log_error("Failed to parse VT number %s", t+3);
return r;
}
if (!vtnr) {
2011-05-25 00:55:58 +02:00
log_error("VT number invalid: %s", t+3);
return -EIO;
}
return seat_active_vt_changed(s, vtnr);
}
int seat_start(Seat *s) {
assert(s);
2011-05-26 02:21:16 +02:00
if (s->started)
return 0;
log_struct(LOG_INFO,
MESSAGE_ID(SD_MESSAGE_SEAT_START),
"SEAT_ID=%s", s->id,
"MESSAGE=New seat %s.", s->id,
NULL);
2011-05-26 02:21:16 +02:00
2011-05-25 00:55:58 +02:00
/* Initialize VT magic stuff */
seat_preallocate_vts(s);
/* Read current VT */
seat_read_active_vt(s);
s->started = true;
2011-05-25 00:55:58 +02:00
/* Save seat data */
seat_save(s);
seat_send_signal(s, true);
2011-05-25 00:55:58 +02:00
return 0;
}
int seat_stop(Seat *s) {
2011-06-17 15:59:18 +02:00
int r = 0;
assert(s);
2011-06-24 19:42:45 +02:00
if (s->started)
log_struct(LOG_INFO,
MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
"SEAT_ID=%s", s->id,
"MESSAGE=Removed seat %s.", s->id,
NULL);
2011-06-17 15:59:18 +02:00
seat_stop_sessions(s);
unlink(s->state_file);
seat_add_to_gc_queue(s);
2011-06-24 19:42:45 +02:00
if (s->started)
seat_send_signal(s, false);
2011-06-17 15:59:18 +02:00
s->started = false;
return r;
}
int seat_stop_sessions(Seat *s) {
Session *session;
int r = 0, k;
assert(s);
LIST_FOREACH(sessions_by_seat, session, s->sessions) {
k = session_stop(session);
if (k < 0)
r = k;
}
2011-06-17 15:59:18 +02:00
return r;
}
2011-05-25 00:55:58 +02:00
logind: introduce session "positions" logind has no concept of session ordering. Sessions have a unique name, some attributes about the capabilities and that's already it. There is currently no stable+total order on sessions. If we use the logind API to switch between sessions, we are faced with an unordered list of sessions we have no clue of. This used to be no problem on seats with VTs or on seats with only a single active session. However, with the introduction of multi-session capability for seats without VTs, we need to find a way to order sessions in a stable way. This patch introduces session "positions". A position is a simple integer assigned to a session which is never changed implicitly (currently, we also don't change it explicitly, but that may be changed someday). For seats with VTs, we force the position to be the same as the VTnr. Without VTs, we simply find the lowest unassigned number and use it as position. If position-assignment fails or if, for any reason, we decide to not assign a position to a session, the position is set to 0 (which is treated as invalid position). During session_load() or if two sessions have the same VTnr, we may end up with two sessions with the same position (this shouldn't happen, but lets be fail-safe in case some other part of the stack fails). This case is dealt with gracefully by ignoring any session but the first session assigned to the position. Thus, session->pos is a hint, seat->positions[i] is the definite position-assignment. Always verify both match in case you need to modify them! Additionally, we introduce SwitchTo(unsigned int) on the seat-dbus-API. You can call it with any integer value != 0 and logind will try to switch to the request position. If you implement a compositor or any other session-controller, you simply watch for ctrl+alt+F1 to F12 and call SwitchTo(Fx). logind will figure a way out deal with this number. For convenience, we also introduce SwitchToNext/Previous(). It should be called on ctrl+alt+Left/Right (like the kernel-console used to support). Note that the public API (SwitchTo*()) is *not* bound to the underlying logic that is implemented now. We don't export "session-positions" on the dbus/C API! They are an implementation detail. Instead, the SwitchTo*() API is supposed to be a hint to let logind choose the session-switching logic. Any foreground session-controller is free to enumerate/order existing sessions according to their needs and call Session.Activate() manually. But the SwitchTo*() API provides a uniform behavior across session-controllers. Background: Session-switching keys depend on the active keymap. The XKB specification provides the XKB_KEY_XF86Switch_VT_1-12 key-symbols which have to be mapped by all keymaps to allow session-switching. It is usually bound to ctrl+alt+Fx but may be set differently. A compositor passes any keyboard input to XKB before passing it to clients. In case a key-press invokes the XKB_KEY_XF86Switch_VT_x action, the keypress is *not* forwarded to clients, but instead a session-switch is scheduled. This actually prevents us from handling these keys outside of the session. If an active compositor has a keymap with a different mapping of these keys, and logind itself tries to catch these combinations, we end up with the key-press sent to the compositor's clients *and* handled by logind. This is *bad* and we must avoid this. The only situation where a background process is allowed to handle key-presses is debugging and emergency-keys. In these cases, we don't care for keymap mismatches and accept the double-event. Another exception is unmapped keys like PowerOff/Suspend (even though this one is controversial).
2013-11-30 11:39:48 +01:00
void seat_evict_position(Seat *s, Session *session) {
unsigned int pos = session->pos;
session->pos = 0;
if (!pos)
return;
if (pos < s->position_count && s->positions[pos] == session)
s->positions[pos] = NULL;
}
void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
/* with VTs, the position is always the same as the VTnr */
if (seat_has_vts(s))
pos = session->vtnr;
if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
return;
seat_evict_position(s, session);
session->pos = pos;
if (pos > 0 && !s->positions[pos])
s->positions[pos] = session;
}
static void seat_assign_position(Seat *s, Session *session) {
unsigned int pos;
if (session->pos > 0)
return;
for (pos = 1; pos < s->position_count; ++pos)
if (!s->positions[pos])
break;
seat_claim_position(s, session, pos);
}
2011-06-17 15:59:18 +02:00
int seat_attach_session(Seat *s, Session *session) {
assert(s);
assert(session);
assert(!session->seat);
2011-05-26 02:21:16 +02:00
if (!seat_has_vts(s) != !session->vtnr)
return -EINVAL;
2011-06-17 15:59:18 +02:00
session->seat = s;
LIST_PREPEND(sessions_by_seat, s->sessions, session);
logind: introduce session "positions" logind has no concept of session ordering. Sessions have a unique name, some attributes about the capabilities and that's already it. There is currently no stable+total order on sessions. If we use the logind API to switch between sessions, we are faced with an unordered list of sessions we have no clue of. This used to be no problem on seats with VTs or on seats with only a single active session. However, with the introduction of multi-session capability for seats without VTs, we need to find a way to order sessions in a stable way. This patch introduces session "positions". A position is a simple integer assigned to a session which is never changed implicitly (currently, we also don't change it explicitly, but that may be changed someday). For seats with VTs, we force the position to be the same as the VTnr. Without VTs, we simply find the lowest unassigned number and use it as position. If position-assignment fails or if, for any reason, we decide to not assign a position to a session, the position is set to 0 (which is treated as invalid position). During session_load() or if two sessions have the same VTnr, we may end up with two sessions with the same position (this shouldn't happen, but lets be fail-safe in case some other part of the stack fails). This case is dealt with gracefully by ignoring any session but the first session assigned to the position. Thus, session->pos is a hint, seat->positions[i] is the definite position-assignment. Always verify both match in case you need to modify them! Additionally, we introduce SwitchTo(unsigned int) on the seat-dbus-API. You can call it with any integer value != 0 and logind will try to switch to the request position. If you implement a compositor or any other session-controller, you simply watch for ctrl+alt+F1 to F12 and call SwitchTo(Fx). logind will figure a way out deal with this number. For convenience, we also introduce SwitchToNext/Previous(). It should be called on ctrl+alt+Left/Right (like the kernel-console used to support). Note that the public API (SwitchTo*()) is *not* bound to the underlying logic that is implemented now. We don't export "session-positions" on the dbus/C API! They are an implementation detail. Instead, the SwitchTo*() API is supposed to be a hint to let logind choose the session-switching logic. Any foreground session-controller is free to enumerate/order existing sessions according to their needs and call Session.Activate() manually. But the SwitchTo*() API provides a uniform behavior across session-controllers. Background: Session-switching keys depend on the active keymap. The XKB specification provides the XKB_KEY_XF86Switch_VT_1-12 key-symbols which have to be mapped by all keymaps to allow session-switching. It is usually bound to ctrl+alt+Fx but may be set differently. A compositor passes any keyboard input to XKB before passing it to clients. In case a key-press invokes the XKB_KEY_XF86Switch_VT_x action, the keypress is *not* forwarded to clients, but instead a session-switch is scheduled. This actually prevents us from handling these keys outside of the session. If an active compositor has a keymap with a different mapping of these keys, and logind itself tries to catch these combinations, we end up with the key-press sent to the compositor's clients *and* handled by logind. This is *bad* and we must avoid this. The only situation where a background process is allowed to handle key-presses is debugging and emergency-keys. In these cases, we don't care for keymap mismatches and accept the double-event. Another exception is unmapped keys like PowerOff/Suspend (even though this one is controversial).
2013-11-30 11:39:48 +01:00
seat_assign_position(s, session);
2011-06-17 15:59:18 +02:00
2013-11-05 01:10:21 +01:00
seat_send_changed(s, "Sessions", NULL);
/* On seats with VTs, the VT logic defines which session is active. On
* seats without VTs, we automatically activate new sessions. */
if (!seat_has_vts(s))
seat_set_active(s, session);
2011-06-17 15:59:18 +02:00
return 0;
}
void seat_complete_switch(Seat *s) {
Session *session;
assert(s);
/* if no session-switch is pending or if it got canceled, do nothing */
if (!s->pending_switch)
return;
session = s->pending_switch;
s->pending_switch = NULL;
seat_set_active(s, session);
}
bool seat_has_vts(Seat *s) {
assert(s);
return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
}
bool seat_is_seat0(Seat *s) {
2011-06-17 15:59:18 +02:00
assert(s);
return s->manager->seat0 == s;
2011-06-17 15:59:18 +02:00
}
bool seat_can_multi_session(Seat *s) {
assert(s);
return seat_has_vts(s);
}
bool seat_can_tty(Seat *s) {
assert(s);
return seat_has_vts(s);
}
bool seat_has_master_device(Seat *s) {
assert(s);
/* device list is ordered by "master" flag */
return !!s->devices && s->devices->master;
}
bool seat_can_graphical(Seat *s) {
assert(s);
return seat_has_master_device(s);
}
2011-06-17 15:59:18 +02:00
int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
Session *session;
bool idle_hint = true;
dual_timestamp ts = { 0, 0 };
assert(s);
LIST_FOREACH(sessions_by_seat, session, s->sessions) {
dual_timestamp k;
int ih;
ih = session_get_idle_hint(session, &k);
if (ih < 0)
return ih;
if (!ih) {
if (!idle_hint) {
if (k.monotonic > ts.monotonic)
2011-06-17 15:59:18 +02:00
ts = k;
} else {
idle_hint = false;
ts = k;
}
} else if (idle_hint) {
if (k.monotonic > ts.monotonic)
ts = k;
}
}
if (t)
*t = ts;
return idle_hint;
}
2011-05-25 00:55:58 +02:00
2013-11-05 01:10:21 +01:00
bool seat_check_gc(Seat *s, bool drop_not_started) {
2011-05-25 00:55:58 +02:00
assert(s);
if (drop_not_started && !s->started)
2013-11-05 01:10:21 +01:00
return false;
if (seat_is_seat0(s))
2013-11-05 01:10:21 +01:00
return true;
2011-05-25 00:55:58 +02:00
return seat_has_master_device(s);
2011-05-25 00:55:58 +02:00
}
void seat_add_to_gc_queue(Seat *s) {
assert(s);
if (s->in_gc_queue)
return;
LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
2011-05-25 00:55:58 +02:00
s->in_gc_queue = true;
}
2011-05-26 02:21:16 +02:00
static bool seat_name_valid_char(char c) {
return
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '_';
}
bool seat_name_is_valid(const char *name) {
const char *p;
assert(name);
if (!startswith(name, "seat"))
return false;
if (!name[4])
return false;
for (p = name; *p; p++)
if (!seat_name_valid_char(*p))
return false;
if (strlen(name) > 255)
return false;
2011-05-26 02:21:16 +02:00
return true;
}