Systemd/src/login/logind-session.c

1182 lines
31 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 <errno.h>
#include <fcntl.h>
#include <linux/vt.h>
#include <linux/kd.h>
#include <signal.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
2013-11-05 01:10:21 +01:00
#include "sd-id128.h"
#include "sd-messages.h"
#include "strv.h"
#include "util.h"
#include "mkdir.h"
2012-05-07 21:36:12 +02:00
#include "path-util.h"
#include "fileio.h"
2013-11-05 01:10:21 +01:00
#include "audit.h"
#include "bus-util.h"
#include "bus-error.h"
#include "logind-session.h"
static unsigned devt_hash_func(const void *p) {
uint64_t u = *(const dev_t*)p;
return uint64_hash_func(&u);
}
static int devt_compare_func(const void *_a, const void *_b) {
dev_t a, b;
a = *(const dev_t*) _a;
b = *(const dev_t*) _b;
return a < b ? -1 : (a > b ? 1 : 0);
}
Session* session_new(Manager *m, const char *id) {
Session *s;
assert(m);
assert(id);
assert(session_id_valid(id));
2011-05-25 00:55:58 +02:00
s = new0(Session, 1);
if (!s)
return NULL;
2011-06-24 18:50:50 +02:00
s->state_file = strappend("/run/systemd/sessions/", id);
if (!s->state_file) {
free(s);
return NULL;
}
s->devices = hashmap_new(devt_hash_func, devt_compare_func);
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 (!s->devices) {
free(s->state_file);
free(s);
return NULL;
}
2012-05-07 21:36:12 +02:00
s->id = path_get_file_name(s->state_file);
if (hashmap_put(m->sessions, s->id, s) < 0) {
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
hashmap_free(s->devices);
free(s->state_file);
free(s);
return NULL;
}
s->manager = m;
s->fifo_fd = -1;
s->vtfd = -1;
return s;
}
void session_free(Session *s) {
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
SessionDevice *sd;
assert(s);
2011-05-25 00:55:58 +02:00
if (s->in_gc_queue)
LIST_REMOVE(gc_queue, s->manager->session_gc_queue, s);
2011-05-25 00:55:58 +02:00
2013-11-05 01:10:21 +01:00
session_remove_fifo(s);
logind: add session controllers A session usually has only a single compositor or other application that controls graphics and input devices on it. To avoid multiple applications from hijacking each other's devices or even using the devices in parallel, we add session controllers. A session controller is an application that manages a session. Specific API calls may be limited to controllers to avoid others from getting unprivileged access to restricted resources. A session becomes a controller by calling the RequestControl() dbus API call. It can drop it via ReleaseControl(). logind tracks bus-names to release the controller once an application closes the bus. We use the new bus-name tracking to do that. Note that during ReleaseControl() we need to check whether some other session also tracks the name before we remove it from the bus-name tracking list. Currently, we only allow one controller at a time. However, the public API does not enforce this restriction. So if it makes sense, we can allow multiple controllers in parallel later. Or we can add a "scope" parameter, which allows a different controller for graphics-devices, sound-devices and whatever you want. Note that currently you get -EBUSY if there is already a controller. You can force the RequestControl() call (root-only) to drop the current controller and recover the session during an emergency. To recover a seat, this is not needed, though. You can simply create a new session or force-activate it. To become a session controller, a dbus caller must either be root or the same user as the user of the session. This allows us to run a session compositor as user and we no longer need any CAP_SYS_ADMIN.
2013-09-17 17:39:56 +02:00
session_drop_controller(s);
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
while ((sd = hashmap_first(s->devices)))
session_device_free(sd);
hashmap_free(s->devices);
if (s->user) {
LIST_REMOVE(sessions_by_user, s->user->sessions, s);
if (s->user->display == s)
s->user->display = NULL;
}
if (s->seat) {
if (s->seat->active == s)
s->seat->active = NULL;
if (s->seat->pending_switch == s)
s->seat->pending_switch = NULL;
LIST_REMOVE(sessions_by_seat, s->seat->sessions, s);
}
if (s->scope) {
hashmap_remove(s->manager->session_units, s->scope);
free(s->scope);
}
free(s->scope_job);
2013-11-05 01:10:21 +01:00
sd_bus_message_unref(s->create_message);
free(s->tty);
free(s->display);
free(s->remote_host);
2011-05-26 02:21:16 +02:00
free(s->remote_user);
2011-06-24 18:50:50 +02:00
free(s->service);
hashmap_remove(s->manager->sessions, s->id);
2011-06-24 18:50:50 +02:00
free(s->state_file);
free(s);
}
void session_set_user(Session *s, User *u) {
assert(s);
assert(!s->user);
s->user = u;
LIST_PREPEND(sessions_by_user, u->sessions, s);
}
int session_save(Session *s) {
_cleanup_free_ char *temp_path = NULL;
2013-11-05 01:10:21 +01:00
_cleanup_fclose_ FILE *f = NULL;
int r = 0;
assert(s);
if (!s->user)
return -ESTALE;
if (!s->started)
return 0;
r = mkdir_safe_label("/run/systemd/sessions", 0755, 0, 0);
if (r < 0)
2011-05-25 00:55:58 +02:00
goto finish;
2011-05-25 00:55:58 +02:00
r = fopen_temporary(s->state_file, &f, &temp_path);
if (r < 0)
goto finish;
assert(s->user);
2011-05-25 00:55:58 +02:00
fchmod(fileno(f), 0644);
fprintf(f,
"# This is private data. Do not parse.\n"
"UID=%lu\n"
"USER=%s\n"
"ACTIVE=%i\n"
"STATE=%s\n"
"REMOTE=%i\n",
(unsigned long) s->user->uid,
s->user->name,
session_is_active(s),
session_state_to_string(session_get_state(s)),
s->remote);
2011-06-24 23:50:39 +02:00
if (s->type >= 0)
fprintf(f, "TYPE=%s\n", session_type_to_string(s->type));
2011-06-24 23:50:39 +02:00
if (s->class >= 0)
fprintf(f, "CLASS=%s\n", session_class_to_string(s->class));
if (s->scope)
fprintf(f, "SCOPE=%s\n", s->scope);
if (s->scope_job)
fprintf(f, "SCOPE_JOB=%s\n", s->scope_job);
if (s->fifo_path)
fprintf(f, "FIFO=%s\n", s->fifo_path);
if (s->seat)
fprintf(f, "SEAT=%s\n", s->seat->id);
if (s->tty)
fprintf(f, "TTY=%s\n", s->tty);
if (s->display)
fprintf(f, "DISPLAY=%s\n", s->display);
if (s->remote_host)
fprintf(f, "REMOTE_HOST=%s\n", s->remote_host);
2011-05-26 02:21:16 +02:00
if (s->remote_user)
fprintf(f, "REMOTE_USER=%s\n", s->remote_user);
2011-05-26 02:21:16 +02:00
2011-06-24 18:50:50 +02:00
if (s->service)
fprintf(f, "SERVICE=%s\n", s->service);
2011-06-24 18:50:50 +02:00
if (s->seat && seat_has_vts(s->seat))
fprintf(f, "VTNR=%i\n", s->vtnr);
if (s->leader > 0)
fprintf(f, "LEADER=%lu\n", (unsigned long) s->leader);
if (s->audit_id > 0)
fprintf(f, "AUDIT=%"PRIu32"\n", s->audit_id);
if (dual_timestamp_is_set(&s->timestamp))
fprintf(f,
"REALTIME=%llu\n"
"MONOTONIC=%llu\n",
(unsigned long long) s->timestamp.realtime,
(unsigned long long) s->timestamp.monotonic);
logind: restore session-controller after crash We now save the unique bus-name of a session-controller as CONTROLLER=%s in the session files. This allows us to restore the controller after a crash or restart. Note that we test whether the name is still valid (dbus guarantees that the name is unique as long as the machine is up and running). If it is, we know that the controller still exists and can safely restore it. Our dbus-name-tracking guarantees that we're notified once it exits. Also note that session-devices are *not* restored. We have no way to know which devices where used before the crash. We could store all these on disk, too, or mark them via udev. However, this seems to be rather cumbersome. Instead, we expect controllers to listen for NewSession signals for their own session. This is sent on session_load() and they can then re-request all devices. The only race I could find is if logind crashes, then the session controller tries calling ReleaseControl() (which will fail as logind is down) but keeps the bus-connection valid for other independent requests. If logind is restarted, it will restore the old controller and thus block the session. However, this seems unlikely for several reasons: - The ReleaseControl() call must occur exactly in the timespan where logind is dead. - A process which calls ReleaseControl() usually closes the bus-connection afterwards. Especially if ReleaseControl() fails, the process should notice that something is wrong and close the bus. - A process calling ReleaseControl() usually exits afterwards. There may be any cleanup pending, but other than that, usual compositors exit. - If a session-controller calls ReleaseControl(), a session is usually considered closing. There is no known use-case where we hand-over session-control in a single session. So we don't care whether the controller is locked afterwards. So this seems negligible.
2013-11-28 14:58:57 +01:00
if (s->controller)
fprintf(f, "CONTROLLER=%s\n", s->controller);
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 session data for %s: %s", s->id, strerror(-r));
return r;
}
int session_load(Session *s) {
_cleanup_free_ char *remote = NULL,
2011-06-17 15:59:18 +02:00
*seat = NULL,
*vtnr = NULL,
*leader = NULL,
*type = NULL,
*class = NULL,
*uid = NULL,
*realtime = NULL,
logind: restore session-controller after crash We now save the unique bus-name of a session-controller as CONTROLLER=%s in the session files. This allows us to restore the controller after a crash or restart. Note that we test whether the name is still valid (dbus guarantees that the name is unique as long as the machine is up and running). If it is, we know that the controller still exists and can safely restore it. Our dbus-name-tracking guarantees that we're notified once it exits. Also note that session-devices are *not* restored. We have no way to know which devices where used before the crash. We could store all these on disk, too, or mark them via udev. However, this seems to be rather cumbersome. Instead, we expect controllers to listen for NewSession signals for their own session. This is sent on session_load() and they can then re-request all devices. The only race I could find is if logind crashes, then the session controller tries calling ReleaseControl() (which will fail as logind is down) but keeps the bus-connection valid for other independent requests. If logind is restarted, it will restore the old controller and thus block the session. However, this seems unlikely for several reasons: - The ReleaseControl() call must occur exactly in the timespan where logind is dead. - A process which calls ReleaseControl() usually closes the bus-connection afterwards. Especially if ReleaseControl() fails, the process should notice that something is wrong and close the bus. - A process calling ReleaseControl() usually exits afterwards. There may be any cleanup pending, but other than that, usual compositors exit. - If a session-controller calls ReleaseControl(), a session is usually considered closing. There is no known use-case where we hand-over session-control in a single session. So we don't care whether the controller is locked afterwards. So this seems negligible.
2013-11-28 14:58:57 +01:00
*monotonic = NULL,
*controller = NULL;
2011-06-17 15:59:18 +02:00
int k, r;
assert(s);
2011-06-17 15:59:18 +02:00
r = parse_env_file(s->state_file, NEWLINE,
"REMOTE", &remote,
"SCOPE", &s->scope,
"SCOPE_JOB", &s->scope_job,
"FIFO", &s->fifo_path,
2011-06-17 15:59:18 +02:00
"SEAT", &seat,
"TTY", &s->tty,
"DISPLAY", &s->display,
"REMOTE_HOST", &s->remote_host,
"REMOTE_USER", &s->remote_user,
2011-06-24 18:50:50 +02:00
"SERVICE", &s->service,
2011-06-17 15:59:18 +02:00
"VTNR", &vtnr,
"LEADER", &leader,
2011-06-24 23:50:39 +02:00
"TYPE", &type,
"CLASS", &class,
"UID", &uid,
"REALTIME", &realtime,
"MONOTONIC", &monotonic,
logind: restore session-controller after crash We now save the unique bus-name of a session-controller as CONTROLLER=%s in the session files. This allows us to restore the controller after a crash or restart. Note that we test whether the name is still valid (dbus guarantees that the name is unique as long as the machine is up and running). If it is, we know that the controller still exists and can safely restore it. Our dbus-name-tracking guarantees that we're notified once it exits. Also note that session-devices are *not* restored. We have no way to know which devices where used before the crash. We could store all these on disk, too, or mark them via udev. However, this seems to be rather cumbersome. Instead, we expect controllers to listen for NewSession signals for their own session. This is sent on session_load() and they can then re-request all devices. The only race I could find is if logind crashes, then the session controller tries calling ReleaseControl() (which will fail as logind is down) but keeps the bus-connection valid for other independent requests. If logind is restarted, it will restore the old controller and thus block the session. However, this seems unlikely for several reasons: - The ReleaseControl() call must occur exactly in the timespan where logind is dead. - A process which calls ReleaseControl() usually closes the bus-connection afterwards. Especially if ReleaseControl() fails, the process should notice that something is wrong and close the bus. - A process calling ReleaseControl() usually exits afterwards. There may be any cleanup pending, but other than that, usual compositors exit. - If a session-controller calls ReleaseControl(), a session is usually considered closing. There is no known use-case where we hand-over session-control in a single session. So we don't care whether the controller is locked afterwards. So this seems negligible.
2013-11-28 14:58:57 +01:00
"CONTROLLER", &controller,
2011-06-17 15:59:18 +02:00
NULL);
if (r < 0) {
log_error("Failed to read %s: %s", s->state_file, strerror(-r));
return r;
}
if (!s->user) {
uid_t u;
User *user;
if (!uid) {
log_error("UID not specified for session %s", s->id);
return -ENOENT;
}
r = parse_uid(uid, &u);
if (r < 0) {
log_error("Failed to parse UID value %s for session %s.", uid, s->id);
return r;
}
user = hashmap_get(s->manager->users, ULONG_TO_PTR((unsigned long) u));
if (!user) {
log_error("User of session %s not known.", s->id);
return -ENOENT;
}
session_set_user(s, user);
}
2011-06-17 15:59:18 +02:00
if (remote) {
k = parse_boolean(remote);
if (k >= 0)
s->remote = k;
}
if (seat && !s->seat) {
2011-06-17 15:59:18 +02:00
Seat *o;
o = hashmap_get(s->manager->seats, seat);
if (o)
seat_attach_session(o, s);
}
if (vtnr && s->seat && seat_has_vts(s->seat)) {
2011-06-17 15:59:18 +02:00
int v;
k = safe_atoi(vtnr, &v);
if (k >= 0 && v >= 1)
s->vtnr = v;
}
if (leader) {
k = parse_pid(leader, &s->leader);
if (k >= 0)
audit_session_from_pid(s->leader, &s->audit_id);
2011-06-17 15:59:18 +02:00
}
2011-06-24 23:50:39 +02:00
if (type) {
SessionType t;
t = session_type_from_string(type);
if (t >= 0)
s->type = t;
}
if (class) {
SessionClass c;
c = session_class_from_string(class);
if (c >= 0)
s->class = c;
}
if (s->fifo_path) {
int fd;
/* If we open an unopened pipe for reading we will not
get an EOF. to trigger an EOF we hence open it for
reading, but close it right-away which then will
trigger the EOF. */
fd = session_create_fifo(s);
if (fd >= 0)
close_nointr_nofail(fd);
}
if (realtime) {
unsigned long long l;
if (sscanf(realtime, "%llu", &l) > 0)
s->timestamp.realtime = l;
}
if (monotonic) {
unsigned long long l;
if (sscanf(monotonic, "%llu", &l) > 0)
s->timestamp.monotonic = l;
}
2011-06-17 15:59:18 +02:00
logind: restore session-controller after crash We now save the unique bus-name of a session-controller as CONTROLLER=%s in the session files. This allows us to restore the controller after a crash or restart. Note that we test whether the name is still valid (dbus guarantees that the name is unique as long as the machine is up and running). If it is, we know that the controller still exists and can safely restore it. Our dbus-name-tracking guarantees that we're notified once it exits. Also note that session-devices are *not* restored. We have no way to know which devices where used before the crash. We could store all these on disk, too, or mark them via udev. However, this seems to be rather cumbersome. Instead, we expect controllers to listen for NewSession signals for their own session. This is sent on session_load() and they can then re-request all devices. The only race I could find is if logind crashes, then the session controller tries calling ReleaseControl() (which will fail as logind is down) but keeps the bus-connection valid for other independent requests. If logind is restarted, it will restore the old controller and thus block the session. However, this seems unlikely for several reasons: - The ReleaseControl() call must occur exactly in the timespan where logind is dead. - A process which calls ReleaseControl() usually closes the bus-connection afterwards. Especially if ReleaseControl() fails, the process should notice that something is wrong and close the bus. - A process calling ReleaseControl() usually exits afterwards. There may be any cleanup pending, but other than that, usual compositors exit. - If a session-controller calls ReleaseControl(), a session is usually considered closing. There is no known use-case where we hand-over session-control in a single session. So we don't care whether the controller is locked afterwards. So this seems negligible.
2013-11-28 14:58:57 +01:00
if (controller) {
if (bus_name_has_owner(s->manager->bus, controller, NULL) > 0)
session_set_controller(s, controller, false);
else
session_restore_vt(s);
logind: restore session-controller after crash We now save the unique bus-name of a session-controller as CONTROLLER=%s in the session files. This allows us to restore the controller after a crash or restart. Note that we test whether the name is still valid (dbus guarantees that the name is unique as long as the machine is up and running). If it is, we know that the controller still exists and can safely restore it. Our dbus-name-tracking guarantees that we're notified once it exits. Also note that session-devices are *not* restored. We have no way to know which devices where used before the crash. We could store all these on disk, too, or mark them via udev. However, this seems to be rather cumbersome. Instead, we expect controllers to listen for NewSession signals for their own session. This is sent on session_load() and they can then re-request all devices. The only race I could find is if logind crashes, then the session controller tries calling ReleaseControl() (which will fail as logind is down) but keeps the bus-connection valid for other independent requests. If logind is restarted, it will restore the old controller and thus block the session. However, this seems unlikely for several reasons: - The ReleaseControl() call must occur exactly in the timespan where logind is dead. - A process which calls ReleaseControl() usually closes the bus-connection afterwards. Especially if ReleaseControl() fails, the process should notice that something is wrong and close the bus. - A process calling ReleaseControl() usually exits afterwards. There may be any cleanup pending, but other than that, usual compositors exit. - If a session-controller calls ReleaseControl(), a session is usually considered closing. There is no known use-case where we hand-over session-control in a single session. So we don't care whether the controller is locked afterwards. So this seems negligible.
2013-11-28 14:58:57 +01:00
}
2011-06-17 15:59:18 +02:00
return r;
}
int session_activate(Session *s) {
unsigned int num_pending;
assert(s);
assert(s->user);
if (!s->seat)
return -ENOTSUP;
if (s->seat->active == s)
return 0;
/* on seats with VTs, we let VTs manage session-switching */
if (seat_has_vts(s->seat)) {
if (s->vtnr <= 0)
return -ENOTSUP;
return chvt(s->vtnr);
}
/* On seats without VTs, we implement session-switching in logind. We
* try to pause all session-devices and wait until the session
* controller acknowledged them. Once all devices are asleep, we simply
* switch the active session and be done.
* We save the session we want to switch to in seat->pending_switch and
* seat_complete_switch() will perform the final switch. */
s->seat->pending_switch = s;
/* if no devices are running, immediately perform the session switch */
num_pending = session_device_try_pause_all(s);
if (!num_pending)
seat_complete_switch(s->seat);
return 0;
}
static int session_link_x11_socket(Session *s) {
2013-08-25 05:48:34 +02:00
_cleanup_free_ char *t = NULL, *f = NULL;
char *c;
size_t k;
assert(s);
assert(s->user);
assert(s->user->runtime_path);
if (s->user->display)
return 0;
if (!s->display || !display_is_local(s->display))
return 0;
k = strspn(s->display+1, "0123456789");
f = new(char, sizeof("/tmp/.X11-unix/X") + k);
if (!f)
return log_oom();
c = stpcpy(f, "/tmp/.X11-unix/X");
memcpy(c, s->display+1, k);
c[k] = 0;
if (access(f, F_OK) < 0) {
2012-05-21 15:22:28 +02:00
log_warning("Session %s has display %s with non-existing socket %s.", s->id, s->display, f);
return -ENOENT;
}
/* Note that this cannot be in a subdir to avoid
* vulnerabilities since we are privileged but the runtime
* path is owned by the user */
2012-01-17 14:03:00 +01:00
t = strappend(s->user->runtime_path, "/X11-display");
2013-08-25 05:48:34 +02:00
if (!t)
return log_oom();
if (link(f, t) < 0) {
if (errno == EEXIST) {
unlink(t);
if (link(f, t) >= 0)
goto done;
}
if (symlink(f, t) < 0) {
if (errno == EEXIST) {
unlink(t);
if (symlink(f, t) >= 0)
goto done;
}
log_error("Failed to link %s to %s: %m", f, t);
return -errno;
}
}
done:
log_info("Linked %s to %s.", f, t);
s->user->display = s;
return 0;
}
static int session_start_scope(Session *s) {
2011-06-24 18:50:50 +02:00
int r;
assert(s);
assert(s->user);
assert(s->user->slice);
2011-06-24 18:50:50 +02:00
if (!s->scope) {
2013-11-05 01:10:21 +01:00
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *description = NULL;
const char *kill_mode;
char *scope, *job;
description = strjoin("Session ", s->id, " of user ", s->user->name, NULL);
if (!description)
return log_oom();
scope = strjoin("session-", s->id, ".scope", NULL);
if (!scope)
return log_oom();
kill_mode = manager_shall_kill(s->manager, s->user->name) ? "control-group" : "none";
2011-06-24 18:50:50 +02:00
r = manager_start_scope(s->manager, scope, s->leader, s->user->slice, description, "systemd-user-sessions.service", kill_mode, &error, &job);
if (r < 0) {
2013-08-24 16:52:23 +02:00
log_error("Failed to start session scope %s: %s %s",
2013-11-05 01:10:21 +01:00
scope, bus_error_message(&error, r), error.name);
free(scope);
return r;
} else {
s->scope = scope;
free(s->scope_job);
s->scope_job = job;
}
}
if (s->scope)
hashmap_put(s->manager->session_units, s->scope, s);
return 0;
}
int session_start(Session *s) {
int r;
assert(s);
if (!s->user)
return -ESTALE;
if (s->started)
return 0;
2011-06-24 19:42:45 +02:00
r = user_start(s->user);
if (r < 0)
return r;
/* Create cgroup */
r = session_start_scope(s);
if (r < 0)
return r;
log_struct(s->type == SESSION_TTY || s->type == SESSION_X11 ? LOG_INFO : LOG_DEBUG,
MESSAGE_ID(SD_MESSAGE_SESSION_START),
"SESSION_ID=%s", s->id,
"USER_ID=%s", s->user->name,
"LEADER=%lu", (unsigned long) s->leader,
"MESSAGE=New session %s of user %s.", s->id, s->user->name,
NULL);
2011-06-24 18:50:50 +02:00
/* Create X11 symlink */
session_link_x11_socket(s);
2011-05-25 00:55:58 +02:00
if (!dual_timestamp_is_set(&s->timestamp))
dual_timestamp_get(&s->timestamp);
2011-05-25 00:55:58 +02:00
if (s->seat)
seat_read_active_vt(s->seat);
s->started = true;
/* Save session data */
session_save(s);
user_save(s->user);
session_send_signal(s, true);
if (s->seat) {
seat_save(s->seat);
if (s->seat->active == s)
2013-11-05 01:10:21 +01:00
seat_send_changed(s->seat, "Sessions", "ActiveSession", NULL);
else
2013-11-05 01:10:21 +01:00
seat_send_changed(s->seat, "Sessions", NULL);
}
2013-11-05 01:10:21 +01:00
user_send_changed(s->user, "Sessions", NULL);
return 0;
}
static int session_stop_scope(Session *s) {
2013-11-05 01:10:21 +01:00
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
char *job;
int r;
assert(s);
if (!s->scope)
return 0;
r = manager_stop_unit(s->manager, s->scope, &error, &job);
if (r < 0) {
2013-11-05 01:10:21 +01:00
log_error("Failed to stop session scope: %s", bus_error_message(&error, r));
return r;
}
free(s->scope_job);
s->scope_job = job;
return 0;
}
static int session_unlink_x11_socket(Session *s) {
2013-08-25 05:48:34 +02:00
_cleanup_free_ char *t = NULL;
int r;
assert(s);
assert(s->user);
if (s->user->display != s)
return 0;
s->user->display = NULL;
2012-01-17 14:03:00 +01:00
t = strappend(s->user->runtime_path, "/X11-display");
if (!t)
return log_oom();
r = unlink(t);
return r < 0 ? -errno : 0;
}
int session_stop(Session *s) {
int r;
assert(s);
if (!s->user)
return -ESTALE;
/* Kill cgroup */
r = session_stop_scope(s);
session_save(s);
2013-11-05 01:10:21 +01:00
user_save(s->user);
return r;
}
int session_finalize(Session *s) {
int r = 0;
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
SessionDevice *sd;
assert(s);
if (!s->user)
return -ESTALE;
2011-06-24 19:42:45 +02:00
if (s->started)
log_struct(s->type == SESSION_TTY || s->type == SESSION_X11 ? LOG_INFO : LOG_DEBUG,
MESSAGE_ID(SD_MESSAGE_SESSION_STOP),
"SESSION_ID=%s", s->id,
"USER_ID=%s", s->user->name,
"LEADER=%lu", (unsigned long) s->leader,
"MESSAGE=Removed session %s.", s->id,
NULL);
2011-06-24 18:50:50 +02:00
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
/* Kill session devices */
while ((sd = hashmap_first(s->devices)))
session_device_free(sd);
/* Remove X11 symlink */
session_unlink_x11_socket(s);
unlink(s->state_file);
session_add_to_gc_queue(s);
2011-06-24 19:42:45 +02:00
user_add_to_gc_queue(s->user);
2011-05-25 00:55:58 +02:00
if (s->started) {
2011-06-24 19:42:45 +02:00
session_send_signal(s, false);
s->started = false;
}
if (s->seat) {
if (s->seat->active == s)
seat_set_active(s->seat, NULL);
2013-11-05 01:10:21 +01:00
seat_send_changed(s->seat, "Sessions", NULL);
seat_save(s->seat);
}
2013-11-05 01:10:21 +01:00
user_send_changed(s->user, "Sessions", NULL);
user_save(s->user);
return r;
}
bool session_is_active(Session *s) {
assert(s);
if (!s->seat)
return true;
return s->seat->active == s;
}
static int get_tty_atime(const char *tty, usec_t *atime) {
_cleanup_free_ char *p = NULL;
2011-06-17 15:59:18 +02:00
struct stat st;
assert(tty);
assert(atime);
if (!path_is_absolute(tty)) {
p = strappend("/dev/", tty);
if (!p)
return -ENOMEM;
tty = p;
} else if (!path_startswith(tty, "/dev/"))
return -ENOENT;
if (lstat(tty, &st) < 0)
return -errno;
*atime = timespec_load(&st.st_atim);
return 0;
}
static int get_process_ctty_atime(pid_t pid, usec_t *atime) {
_cleanup_free_ char *p = NULL;
int r;
assert(pid > 0);
assert(atime);
r = get_ctty(pid, NULL, &p);
if (r < 0)
return r;
return get_tty_atime(p, atime);
}
int session_get_idle_hint(Session *s, dual_timestamp *t) {
usec_t atime = 0, n;
int r;
2011-06-17 15:59:18 +02:00
assert(s);
/* Explicit idle hint is set */
2011-06-17 15:59:18 +02:00
if (s->idle_hint) {
if (t)
*t = s->idle_hint_timestamp;
return s->idle_hint;
}
2013-01-11 17:06:23 +01:00
/* Graphical sessions should really implement a real
* idle hint logic */
if (s->display)
2011-06-17 15:59:18 +02:00
goto dont_know;
/* For sessions with an explicitly configured tty, let's check
* its atime */
if (s->tty) {
r = get_tty_atime(s->tty, &atime);
if (r >= 0)
goto found_atime;
}
2011-06-17 15:59:18 +02:00
/* For sessions with a leader but no explicitly configured
* tty, let's check the controlling tty of the leader */
if (s->leader > 0) {
r = get_process_ctty_atime(s->leader, &atime);
if (r >= 0)
goto found_atime;
2011-06-17 15:59:18 +02:00
}
dont_know:
if (t)
*t = s->idle_hint_timestamp;
return 0;
found_atime:
if (t)
dual_timestamp_from_realtime(t, atime);
n = now(CLOCK_REALTIME);
if (s->manager->idle_action_usec <= 0)
return 0;
return atime + s->manager->idle_action_usec <= n;
2011-06-17 15:59:18 +02:00
}
void session_set_idle_hint(Session *s, bool b) {
assert(s);
if (s->idle_hint == b)
return;
s->idle_hint = b;
dual_timestamp_get(&s->idle_hint_timestamp);
2013-11-05 01:10:21 +01:00
session_send_changed(s, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic", NULL);
if (s->seat)
2013-11-05 01:10:21 +01:00
seat_send_changed(s->seat, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic", NULL);
user_send_changed(s->user, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic", NULL);
manager_send_changed(s->manager, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic", NULL);
}
static int session_dispatch_fifo(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
Session *s = userdata;
assert(s);
assert(s->fifo_fd == fd);
/* EOF on the FIFO means the session died abnormally. */
session_remove_fifo(s);
session_stop(s);
return 1;
}
int session_create_fifo(Session *s) {
int r;
assert(s);
/* Create FIFO */
if (!s->fifo_path) {
r = mkdir_safe_label("/run/systemd/sessions", 0755, 0, 0);
if (r < 0)
return r;
if (asprintf(&s->fifo_path, "/run/systemd/sessions/%s.ref", s->id) < 0)
return -ENOMEM;
if (mkfifo(s->fifo_path, 0600) < 0 && errno != EEXIST)
return -errno;
}
/* Open reading side */
if (s->fifo_fd < 0) {
s->fifo_fd = open(s->fifo_path, O_RDONLY|O_CLOEXEC|O_NDELAY);
if (s->fifo_fd < 0)
return -errno;
2013-11-05 01:10:21 +01:00
}
if (!s->fifo_event_source) {
r = sd_event_add_io(s->manager->event, s->fifo_fd, 0, session_dispatch_fifo, s, &s->fifo_event_source);
if (r < 0)
return r;
r = sd_event_source_set_priority(s->fifo_event_source, SD_EVENT_PRIORITY_IDLE);
2013-11-05 01:10:21 +01:00
if (r < 0)
return r;
}
/* Open writing side */
r = open(s->fifo_path, O_WRONLY|O_CLOEXEC|O_NDELAY);
if (r < 0)
return -errno;
return r;
}
void session_remove_fifo(Session *s) {
assert(s);
2013-11-05 01:10:21 +01:00
if (s->fifo_event_source)
s->fifo_event_source = sd_event_source_unref(s->fifo_event_source);
if (s->fifo_fd >= 0) {
close_nointr_nofail(s->fifo_fd);
s->fifo_fd = -1;
}
if (s->fifo_path) {
unlink(s->fifo_path);
free(s->fifo_path);
s->fifo_path = NULL;
}
}
2013-11-05 01:10:21 +01:00
bool session_check_gc(Session *s, bool drop_not_started) {
int r;
assert(s);
if (drop_not_started && !s->started)
2013-11-05 01:10:21 +01:00
return false;
if (!s->user)
2013-11-05 01:10:21 +01:00
return false;
if (s->fifo_fd >= 0) {
r = pipe_eof(s->fifo_fd);
if (r < 0)
2013-11-05 01:10:21 +01:00
return true;
2011-05-25 00:55:58 +02:00
if (r == 0)
2013-11-05 01:10:21 +01:00
return true;
}
2013-11-05 01:10:21 +01:00
if (s->scope_job && manager_job_is_active(s->manager, s->scope_job))
return true;
2013-11-05 01:10:21 +01:00
if (s->scope && manager_unit_is_active(s->manager, s->scope))
return true;
2013-11-05 01:10:21 +01:00
return false;
}
2011-05-25 00:55:58 +02:00
void session_add_to_gc_queue(Session *s) {
assert(s);
if (s->in_gc_queue)
return;
LIST_PREPEND(gc_queue, s->manager->session_gc_queue, s);
2011-05-25 00:55:58 +02:00
s->in_gc_queue = true;
}
SessionState session_get_state(Session *s) {
assert(s);
if (s->closing)
return SESSION_CLOSING;
if (s->scope_job)
return SESSION_OPENING;
if (s->fifo_fd < 0)
return SESSION_CLOSING;
if (session_is_active(s))
return SESSION_ACTIVE;
return SESSION_ONLINE;
}
2011-07-13 19:58:35 +02:00
int session_kill(Session *s, KillWho who, int signo) {
assert(s);
if (!s->scope)
2011-07-13 19:58:35 +02:00
return -ESRCH;
return manager_kill_unit(s->manager, s->scope, who, signo, NULL);
2011-07-13 19:58:35 +02:00
}
static int session_open_vt(Session *s) {
char path[128];
if (s->vtnr <= 0)
return -1;
if (s->vtfd >= 0)
return s->vtfd;
sprintf(path, "/dev/tty%d", s->vtnr);
s->vtfd = open(path, O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOCTTY);
if (s->vtfd < 0) {
log_error("cannot open VT %s of session %s: %m", path, s->id);
return -1;
}
return s->vtfd;
}
static int session_vt_fn(sd_event_source *source, const struct signalfd_siginfo *si, void *data) {
Session *s = data;
if (s->vtfd >= 0)
ioctl(s->vtfd, VT_RELDISP, 1);
return 0;
}
void session_mute_vt(Session *s) {
int vt, r;
struct vt_mode mode = { 0 };
sigset_t mask;
vt = session_open_vt(s);
if (vt < 0)
return;
r = ioctl(vt, KDSKBMODE, K_OFF);
if (r < 0)
goto error;
r = ioctl(vt, KDSETMODE, KD_GRAPHICS);
if (r < 0)
goto error;
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
sigprocmask(SIG_BLOCK, &mask, NULL);
r = sd_event_add_signal(s->manager->event, SIGUSR1, session_vt_fn, s, &s->vt_source);
if (r < 0)
goto error;
/* Oh, thanks to the VT layer, VT_AUTO does not work with KD_GRAPHICS.
* So we need a dummy handler here which just acknowledges *all* VT
* switch requests. */
mode.mode = VT_PROCESS;
mode.relsig = SIGUSR1;
mode.acqsig = SIGUSR1;
r = ioctl(vt, VT_SETMODE, &mode);
if (r < 0)
goto error;
return;
error:
log_error("cannot mute VT %d for session %s (%d/%d)", s->vtnr, s->id, r, errno);
session_restore_vt(s);
}
void session_restore_vt(Session *s) {
_cleanup_free_ char *utf8;
int vt, kb = K_XLATE;
struct vt_mode mode = { 0 };
vt = session_open_vt(s);
if (vt < 0)
return;
sd_event_source_unref(s->vt_source);
s->vt_source = NULL;
ioctl(vt, KDSETMODE, KD_TEXT);
if (read_one_line_file("/sys/module/vt/parameters/default_utf8", &utf8) >= 0 && *utf8 == '1')
kb = K_UNICODE;
ioctl(vt, KDSKBMODE, kb);
mode.mode = VT_AUTO;
ioctl(vt, VT_SETMODE, &mode);
close_nointr_nofail(vt);
s->vtfd = -1;
}
2013-11-05 01:10:21 +01:00
bool session_is_controller(Session *s, const char *sender) {
logind: add session controllers A session usually has only a single compositor or other application that controls graphics and input devices on it. To avoid multiple applications from hijacking each other's devices or even using the devices in parallel, we add session controllers. A session controller is an application that manages a session. Specific API calls may be limited to controllers to avoid others from getting unprivileged access to restricted resources. A session becomes a controller by calling the RequestControl() dbus API call. It can drop it via ReleaseControl(). logind tracks bus-names to release the controller once an application closes the bus. We use the new bus-name tracking to do that. Note that during ReleaseControl() we need to check whether some other session also tracks the name before we remove it from the bus-name tracking list. Currently, we only allow one controller at a time. However, the public API does not enforce this restriction. So if it makes sense, we can allow multiple controllers in parallel later. Or we can add a "scope" parameter, which allows a different controller for graphics-devices, sound-devices and whatever you want. Note that currently you get -EBUSY if there is already a controller. You can force the RequestControl() call (root-only) to drop the current controller and recover the session during an emergency. To recover a seat, this is not needed, though. You can simply create a new session or force-activate it. To become a session controller, a dbus caller must either be root or the same user as the user of the session. This allows us to run a session compositor as user and we no longer need any CAP_SYS_ADMIN.
2013-09-17 17:39:56 +02:00
assert(s);
return streq_ptr(s->controller, sender);
}
logind: restore session-controller after crash We now save the unique bus-name of a session-controller as CONTROLLER=%s in the session files. This allows us to restore the controller after a crash or restart. Note that we test whether the name is still valid (dbus guarantees that the name is unique as long as the machine is up and running). If it is, we know that the controller still exists and can safely restore it. Our dbus-name-tracking guarantees that we're notified once it exits. Also note that session-devices are *not* restored. We have no way to know which devices where used before the crash. We could store all these on disk, too, or mark them via udev. However, this seems to be rather cumbersome. Instead, we expect controllers to listen for NewSession signals for their own session. This is sent on session_load() and they can then re-request all devices. The only race I could find is if logind crashes, then the session controller tries calling ReleaseControl() (which will fail as logind is down) but keeps the bus-connection valid for other independent requests. If logind is restarted, it will restore the old controller and thus block the session. However, this seems unlikely for several reasons: - The ReleaseControl() call must occur exactly in the timespan where logind is dead. - A process which calls ReleaseControl() usually closes the bus-connection afterwards. Especially if ReleaseControl() fails, the process should notice that something is wrong and close the bus. - A process calling ReleaseControl() usually exits afterwards. There may be any cleanup pending, but other than that, usual compositors exit. - If a session-controller calls ReleaseControl(), a session is usually considered closing. There is no known use-case where we hand-over session-control in a single session. So we don't care whether the controller is locked afterwards. So this seems negligible.
2013-11-28 14:58:57 +01:00
static void session_swap_controller(Session *s, char *name) {
SessionDevice *sd;
if (s->controller) {
manager_drop_busname(s->manager, s->controller);
free(s->controller);
s->controller = NULL;
/* Drop all devices as they're now unused. Do that after the
* controller is released to avoid sending out useles
* dbus signals. */
while ((sd = hashmap_first(s->devices)))
session_device_free(sd);
if (!name)
session_restore_vt(s);
logind: restore session-controller after crash We now save the unique bus-name of a session-controller as CONTROLLER=%s in the session files. This allows us to restore the controller after a crash or restart. Note that we test whether the name is still valid (dbus guarantees that the name is unique as long as the machine is up and running). If it is, we know that the controller still exists and can safely restore it. Our dbus-name-tracking guarantees that we're notified once it exits. Also note that session-devices are *not* restored. We have no way to know which devices where used before the crash. We could store all these on disk, too, or mark them via udev. However, this seems to be rather cumbersome. Instead, we expect controllers to listen for NewSession signals for their own session. This is sent on session_load() and they can then re-request all devices. The only race I could find is if logind crashes, then the session controller tries calling ReleaseControl() (which will fail as logind is down) but keeps the bus-connection valid for other independent requests. If logind is restarted, it will restore the old controller and thus block the session. However, this seems unlikely for several reasons: - The ReleaseControl() call must occur exactly in the timespan where logind is dead. - A process which calls ReleaseControl() usually closes the bus-connection afterwards. Especially if ReleaseControl() fails, the process should notice that something is wrong and close the bus. - A process calling ReleaseControl() usually exits afterwards. There may be any cleanup pending, but other than that, usual compositors exit. - If a session-controller calls ReleaseControl(), a session is usually considered closing. There is no known use-case where we hand-over session-control in a single session. So we don't care whether the controller is locked afterwards. So this seems negligible.
2013-11-28 14:58:57 +01:00
}
s->controller = name;
session_save(s);
}
logind: add session controllers A session usually has only a single compositor or other application that controls graphics and input devices on it. To avoid multiple applications from hijacking each other's devices or even using the devices in parallel, we add session controllers. A session controller is an application that manages a session. Specific API calls may be limited to controllers to avoid others from getting unprivileged access to restricted resources. A session becomes a controller by calling the RequestControl() dbus API call. It can drop it via ReleaseControl(). logind tracks bus-names to release the controller once an application closes the bus. We use the new bus-name tracking to do that. Note that during ReleaseControl() we need to check whether some other session also tracks the name before we remove it from the bus-name tracking list. Currently, we only allow one controller at a time. However, the public API does not enforce this restriction. So if it makes sense, we can allow multiple controllers in parallel later. Or we can add a "scope" parameter, which allows a different controller for graphics-devices, sound-devices and whatever you want. Note that currently you get -EBUSY if there is already a controller. You can force the RequestControl() call (root-only) to drop the current controller and recover the session during an emergency. To recover a seat, this is not needed, though. You can simply create a new session or force-activate it. To become a session controller, a dbus caller must either be root or the same user as the user of the session. This allows us to run a session compositor as user and we no longer need any CAP_SYS_ADMIN.
2013-09-17 17:39:56 +02:00
int session_set_controller(Session *s, const char *sender, bool force) {
char *t;
int r;
assert(s);
assert(sender);
if (session_is_controller(s, sender))
return 0;
if (s->controller && !force)
return -EBUSY;
t = strdup(sender);
if (!t)
return -ENOMEM;
r = manager_watch_busname(s->manager, sender);
if (r) {
free(t);
return r;
}
logind: restore session-controller after crash We now save the unique bus-name of a session-controller as CONTROLLER=%s in the session files. This allows us to restore the controller after a crash or restart. Note that we test whether the name is still valid (dbus guarantees that the name is unique as long as the machine is up and running). If it is, we know that the controller still exists and can safely restore it. Our dbus-name-tracking guarantees that we're notified once it exits. Also note that session-devices are *not* restored. We have no way to know which devices where used before the crash. We could store all these on disk, too, or mark them via udev. However, this seems to be rather cumbersome. Instead, we expect controllers to listen for NewSession signals for their own session. This is sent on session_load() and they can then re-request all devices. The only race I could find is if logind crashes, then the session controller tries calling ReleaseControl() (which will fail as logind is down) but keeps the bus-connection valid for other independent requests. If logind is restarted, it will restore the old controller and thus block the session. However, this seems unlikely for several reasons: - The ReleaseControl() call must occur exactly in the timespan where logind is dead. - A process which calls ReleaseControl() usually closes the bus-connection afterwards. Especially if ReleaseControl() fails, the process should notice that something is wrong and close the bus. - A process calling ReleaseControl() usually exits afterwards. There may be any cleanup pending, but other than that, usual compositors exit. - If a session-controller calls ReleaseControl(), a session is usually considered closing. There is no known use-case where we hand-over session-control in a single session. So we don't care whether the controller is locked afterwards. So this seems negligible.
2013-11-28 14:58:57 +01:00
session_swap_controller(s, t);
logind: add session controllers A session usually has only a single compositor or other application that controls graphics and input devices on it. To avoid multiple applications from hijacking each other's devices or even using the devices in parallel, we add session controllers. A session controller is an application that manages a session. Specific API calls may be limited to controllers to avoid others from getting unprivileged access to restricted resources. A session becomes a controller by calling the RequestControl() dbus API call. It can drop it via ReleaseControl(). logind tracks bus-names to release the controller once an application closes the bus. We use the new bus-name tracking to do that. Note that during ReleaseControl() we need to check whether some other session also tracks the name before we remove it from the bus-name tracking list. Currently, we only allow one controller at a time. However, the public API does not enforce this restriction. So if it makes sense, we can allow multiple controllers in parallel later. Or we can add a "scope" parameter, which allows a different controller for graphics-devices, sound-devices and whatever you want. Note that currently you get -EBUSY if there is already a controller. You can force the RequestControl() call (root-only) to drop the current controller and recover the session during an emergency. To recover a seat, this is not needed, though. You can simply create a new session or force-activate it. To become a session controller, a dbus caller must either be root or the same user as the user of the session. This allows us to run a session compositor as user and we no longer need any CAP_SYS_ADMIN.
2013-09-17 17:39:56 +02:00
/* When setting a session controller, we forcibly mute the VT and set
* it into graphics-mode. Applications can override that by changing
* VT state after calling TakeControl(). However, this serves as a good
* default and well-behaving controllers can now ignore VTs entirely.
* 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. */
session_mute_vt(s);
logind: add session controllers A session usually has only a single compositor or other application that controls graphics and input devices on it. To avoid multiple applications from hijacking each other's devices or even using the devices in parallel, we add session controllers. A session controller is an application that manages a session. Specific API calls may be limited to controllers to avoid others from getting unprivileged access to restricted resources. A session becomes a controller by calling the RequestControl() dbus API call. It can drop it via ReleaseControl(). logind tracks bus-names to release the controller once an application closes the bus. We use the new bus-name tracking to do that. Note that during ReleaseControl() we need to check whether some other session also tracks the name before we remove it from the bus-name tracking list. Currently, we only allow one controller at a time. However, the public API does not enforce this restriction. So if it makes sense, we can allow multiple controllers in parallel later. Or we can add a "scope" parameter, which allows a different controller for graphics-devices, sound-devices and whatever you want. Note that currently you get -EBUSY if there is already a controller. You can force the RequestControl() call (root-only) to drop the current controller and recover the session during an emergency. To recover a seat, this is not needed, though. You can simply create a new session or force-activate it. To become a session controller, a dbus caller must either be root or the same user as the user of the session. This allows us to run a session compositor as user and we no longer need any CAP_SYS_ADMIN.
2013-09-17 17:39:56 +02:00
return 0;
}
void session_drop_controller(Session *s) {
assert(s);
if (!s->controller)
return;
logind: restore session-controller after crash We now save the unique bus-name of a session-controller as CONTROLLER=%s in the session files. This allows us to restore the controller after a crash or restart. Note that we test whether the name is still valid (dbus guarantees that the name is unique as long as the machine is up and running). If it is, we know that the controller still exists and can safely restore it. Our dbus-name-tracking guarantees that we're notified once it exits. Also note that session-devices are *not* restored. We have no way to know which devices where used before the crash. We could store all these on disk, too, or mark them via udev. However, this seems to be rather cumbersome. Instead, we expect controllers to listen for NewSession signals for their own session. This is sent on session_load() and they can then re-request all devices. The only race I could find is if logind crashes, then the session controller tries calling ReleaseControl() (which will fail as logind is down) but keeps the bus-connection valid for other independent requests. If logind is restarted, it will restore the old controller and thus block the session. However, this seems unlikely for several reasons: - The ReleaseControl() call must occur exactly in the timespan where logind is dead. - A process which calls ReleaseControl() usually closes the bus-connection afterwards. Especially if ReleaseControl() fails, the process should notice that something is wrong and close the bus. - A process calling ReleaseControl() usually exits afterwards. There may be any cleanup pending, but other than that, usual compositors exit. - If a session-controller calls ReleaseControl(), a session is usually considered closing. There is no known use-case where we hand-over session-control in a single session. So we don't care whether the controller is locked afterwards. So this seems negligible.
2013-11-28 14:58:57 +01:00
session_swap_controller(s, NULL);
logind: add session controllers A session usually has only a single compositor or other application that controls graphics and input devices on it. To avoid multiple applications from hijacking each other's devices or even using the devices in parallel, we add session controllers. A session controller is an application that manages a session. Specific API calls may be limited to controllers to avoid others from getting unprivileged access to restricted resources. A session becomes a controller by calling the RequestControl() dbus API call. It can drop it via ReleaseControl(). logind tracks bus-names to release the controller once an application closes the bus. We use the new bus-name tracking to do that. Note that during ReleaseControl() we need to check whether some other session also tracks the name before we remove it from the bus-name tracking list. Currently, we only allow one controller at a time. However, the public API does not enforce this restriction. So if it makes sense, we can allow multiple controllers in parallel later. Or we can add a "scope" parameter, which allows a different controller for graphics-devices, sound-devices and whatever you want. Note that currently you get -EBUSY if there is already a controller. You can force the RequestControl() call (root-only) to drop the current controller and recover the session during an emergency. To recover a seat, this is not needed, though. You can simply create a new session or force-activate it. To become a session controller, a dbus caller must either be root or the same user as the user of the session. This allows us to run a session compositor as user and we no longer need any CAP_SYS_ADMIN.
2013-09-17 17:39:56 +02:00
}
static const char* const session_state_table[_SESSION_STATE_MAX] = {
[SESSION_OPENING] = "opening",
[SESSION_ONLINE] = "online",
[SESSION_ACTIVE] = "active",
[SESSION_CLOSING] = "closing"
};
DEFINE_STRING_TABLE_LOOKUP(session_state, SessionState);
static const char* const session_type_table[_SESSION_TYPE_MAX] = {
2011-05-26 02:21:16 +02:00
[SESSION_TTY] = "tty",
2011-06-24 18:50:50 +02:00
[SESSION_X11] = "x11",
2011-06-24 23:50:39 +02:00
[SESSION_UNSPECIFIED] = "unspecified"
};
DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType);
2011-07-13 19:58:35 +02:00
static const char* const session_class_table[_SESSION_CLASS_MAX] = {
[SESSION_USER] = "user",
[SESSION_GREETER] = "greeter",
[SESSION_LOCK_SCREEN] = "lock-screen",
[SESSION_BACKGROUND] = "background"
};
DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass);
2011-07-13 19:58:35 +02:00
static const char* const kill_who_table[_KILL_WHO_MAX] = {
[KILL_LEADER] = "leader",
[KILL_ALL] = "all"
};
DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);