blob: ff6cd73e06070209452703ed4b2621585b0c7c19 [file] [log] [blame]
/*
* Copyright © 2013 David Herrmann
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "config.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <systemd/sd-login.h>
#include <unistd.h>
#include "compositor.h"
#include "dbus.h"
#include "launcher-impl.h"
#define DRM_MAJOR 226
/* major()/minor() */
#ifdef MAJOR_IN_MKDEV
#include <sys/mkdev.h>
#endif
#ifdef MAJOR_IN_SYSMACROS
#include <sys/sysmacros.h>
#endif
struct launcher_logind {
struct weston_launcher base;
struct weston_compositor *compositor;
bool sync_drm;
char *seat;
char *sid;
unsigned int vtnr;
int vt;
int kb_mode;
DBusConnection *dbus;
struct wl_event_source *dbus_ctx;
char *spath;
DBusPendingCall *pending_active;
};
static int
launcher_logind_take_device(struct launcher_logind *wl, uint32_t major,
uint32_t minor, bool *paused_out)
{
DBusMessage *m, *reply;
bool b;
int r, fd;
dbus_bool_t paused;
m = dbus_message_new_method_call("org.freedesktop.login1",
wl->spath,
"org.freedesktop.login1.Session",
"TakeDevice");
if (!m)
return -ENOMEM;
b = dbus_message_append_args(m,
DBUS_TYPE_UINT32, &major,
DBUS_TYPE_UINT32, &minor,
DBUS_TYPE_INVALID);
if (!b) {
r = -ENOMEM;
goto err_unref;
}
reply = dbus_connection_send_with_reply_and_block(wl->dbus, m,
-1, NULL);
if (!reply) {
r = -ENODEV;
goto err_unref;
}
b = dbus_message_get_args(reply, NULL,
DBUS_TYPE_UNIX_FD, &fd,
DBUS_TYPE_BOOLEAN, &paused,
DBUS_TYPE_INVALID);
if (!b) {
r = -ENODEV;
goto err_reply;
}
r = fd;
if (paused_out)
*paused_out = paused;
err_reply:
dbus_message_unref(reply);
err_unref:
dbus_message_unref(m);
return r;
}
static void
launcher_logind_release_device(struct launcher_logind *wl, uint32_t major,
uint32_t minor)
{
DBusMessage *m;
bool b;
m = dbus_message_new_method_call("org.freedesktop.login1",
wl->spath,
"org.freedesktop.login1.Session",
"ReleaseDevice");
if (m) {
b = dbus_message_append_args(m,
DBUS_TYPE_UINT32, &major,
DBUS_TYPE_UINT32, &minor,
DBUS_TYPE_INVALID);
if (b)
dbus_connection_send(wl->dbus, m, NULL);
dbus_message_unref(m);
}
}
static void
launcher_logind_pause_device_complete(struct launcher_logind *wl, uint32_t major,
uint32_t minor)
{
DBusMessage *m;
bool b;
m = dbus_message_new_method_call("org.freedesktop.login1",
wl->spath,
"org.freedesktop.login1.Session",
"PauseDeviceComplete");
if (m) {
b = dbus_message_append_args(m,
DBUS_TYPE_UINT32, &major,
DBUS_TYPE_UINT32, &minor,
DBUS_TYPE_INVALID);
if (b)
dbus_connection_send(wl->dbus, m, NULL);
dbus_message_unref(m);
}
}
static int
launcher_logind_open(struct weston_launcher *launcher, const char *path, int flags)
{
struct launcher_logind *wl = wl_container_of(launcher, wl, base);
struct stat st;
int fl, r, fd;
r = stat(path, &st);
if (r < 0)
return -1;
if (!S_ISCHR(st.st_mode)) {
errno = ENODEV;
return -1;
}
fd = launcher_logind_take_device(wl, major(st.st_rdev),
minor(st.st_rdev), NULL);
if (fd < 0)
return fd;
/* Compared to weston_launcher_open() we cannot specify the open-mode
* directly. Instead, logind passes us an fd with sane default modes.
* For DRM and evdev this means O_RDWR | O_CLOEXEC. If we want
* something else, we need to change it afterwards. We currently
* only support setting O_NONBLOCK. Changing access-modes is not
* possible so accept whatever logind passes us. */
fl = fcntl(fd, F_GETFL);
if (fl < 0) {
r = -errno;
goto err_close;
}
if (flags & O_NONBLOCK)
fl |= O_NONBLOCK;
r = fcntl(fd, F_SETFL, fl);
if (r < 0) {
r = -errno;
goto err_close;
}
return fd;
err_close:
close(fd);
launcher_logind_release_device(wl, major(st.st_rdev),
minor(st.st_rdev));
errno = -r;
return -1;
}
static void
launcher_logind_close(struct weston_launcher *launcher, int fd)
{
struct launcher_logind *wl = wl_container_of(launcher, wl, base);
struct stat st;
int r;
r = fstat(fd, &st);
close(fd);
if (r < 0) {
weston_log("logind: cannot fstat fd: %m\n");
return;
}
if (!S_ISCHR(st.st_mode)) {
weston_log("logind: invalid device passed\n");
return;
}
launcher_logind_release_device(wl, major(st.st_rdev),
minor(st.st_rdev));
}
static int
launcher_logind_activate_vt(struct weston_launcher *launcher, int vt)
{
struct launcher_logind *wl = wl_container_of(launcher, wl, base);
DBusMessage *m;
bool b;
int r;
m = dbus_message_new_method_call("org.freedesktop.login1",
"/org/freedesktop/login1/seat/self",
"org.freedesktop.login1.Seat",
"SwitchTo");
if (!m)
return -ENOMEM;
b = dbus_message_append_args(m,
DBUS_TYPE_UINT32, &vt,
DBUS_TYPE_INVALID);
if (!b) {
r = -ENOMEM;
goto err_unref;
}
dbus_connection_send(wl->dbus, m, NULL);
r = 0;
err_unref:
dbus_message_unref(m);
return r;
}
static void
launcher_logind_set_active(struct launcher_logind *wl, bool active)
{
if (!wl->compositor->session_active == !active)
return;
wl->compositor->session_active = active;
wl_signal_emit(&wl->compositor->session_signal,
wl->compositor);
}
static void
parse_active(struct launcher_logind *wl, DBusMessage *m, DBusMessageIter *iter)
{
DBusMessageIter sub;
dbus_bool_t b;
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT)
return;
dbus_message_iter_recurse(iter, &sub);
if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
return;
dbus_message_iter_get_basic(&sub, &b);
/* If the backend requested DRM master-device synchronization, we only
* wake-up the compositor once the master-device is up and running. For
* other backends, we immediately forward the Active-change event. */
if (!wl->sync_drm || !b)
launcher_logind_set_active(wl, b);
}
static void
get_active_cb(DBusPendingCall *pending, void *data)
{
struct launcher_logind *wl = data;
DBusMessageIter iter;
DBusMessage *m;
int type;
dbus_pending_call_unref(wl->pending_active);
wl->pending_active = NULL;
m = dbus_pending_call_steal_reply(pending);
if (!m)
return;
type = dbus_message_get_type(m);
if (type == DBUS_MESSAGE_TYPE_METHOD_RETURN &&
dbus_message_iter_init(m, &iter))
parse_active(wl, m, &iter);
dbus_message_unref(m);
}
static void
launcher_logind_get_active(struct launcher_logind *wl)
{
DBusPendingCall *pending;
DBusMessage *m;
bool b;
const char *iface, *name;
m = dbus_message_new_method_call("org.freedesktop.login1",
wl->spath,
"org.freedesktop.DBus.Properties",
"Get");
if (!m)
return;
iface = "org.freedesktop.login1.Session";
name = "Active";
b = dbus_message_append_args(m,
DBUS_TYPE_STRING, &iface,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID);
if (!b)
goto err_unref;
b = dbus_connection_send_with_reply(wl->dbus, m, &pending, -1);
if (!b)
goto err_unref;
b = dbus_pending_call_set_notify(pending, get_active_cb, wl, NULL);
if (!b) {
dbus_pending_call_cancel(pending);
dbus_pending_call_unref(pending);
goto err_unref;
}
if (wl->pending_active) {
dbus_pending_call_cancel(wl->pending_active);
dbus_pending_call_unref(wl->pending_active);
}
wl->pending_active = pending;
return;
err_unref:
dbus_message_unref(m);
}
static void
disconnected_dbus(struct launcher_logind *wl)
{
weston_log("logind: dbus connection lost, exiting..\n");
exit(-1);
}
static void
session_removed(struct launcher_logind *wl, DBusMessage *m)
{
const char *name, *obj;
bool r;
r = dbus_message_get_args(m, NULL,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_OBJECT_PATH, &obj,
DBUS_TYPE_INVALID);
if (!r) {
weston_log("logind: cannot parse SessionRemoved dbus signal\n");
return;
}
if (!strcmp(name, wl->sid)) {
weston_log("logind: our session got closed, exiting..\n");
exit(-1);
}
}
static void
property_changed(struct launcher_logind *wl, DBusMessage *m)
{
DBusMessageIter iter, sub, entry;
const char *interface, *name;
if (!dbus_message_iter_init(m, &iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
goto error;
dbus_message_iter_get_basic(&iter, &interface);
if (!dbus_message_iter_next(&iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
goto error;
dbus_message_iter_recurse(&iter, &sub);
while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY) {
dbus_message_iter_recurse(&sub, &entry);
if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
goto error;
dbus_message_iter_get_basic(&entry, &name);
if (!dbus_message_iter_next(&entry))
goto error;
if (!strcmp(name, "Active")) {
parse_active(wl, m, &entry);
return;
}
dbus_message_iter_next(&sub);
}
if (!dbus_message_iter_next(&iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
goto error;
dbus_message_iter_recurse(&iter, &sub);
while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
dbus_message_iter_get_basic(&sub, &name);
if (!strcmp(name, "Active")) {
launcher_logind_get_active(wl);
return;
}
dbus_message_iter_next(&sub);
}
return;
error:
weston_log("logind: cannot parse PropertiesChanged dbus signal\n");
}
static void
device_paused(struct launcher_logind *wl, DBusMessage *m)
{
bool r;
const char *type;
uint32_t major, minor;
r = dbus_message_get_args(m, NULL,
DBUS_TYPE_UINT32, &major,
DBUS_TYPE_UINT32, &minor,
DBUS_TYPE_STRING, &type,
DBUS_TYPE_INVALID);
if (!r) {
weston_log("logind: cannot parse PauseDevice dbus signal\n");
return;
}
/* "pause" means synchronous pausing. Acknowledge it unconditionally
* as we support asynchronous device shutdowns, anyway.
* "force" means asynchronous pausing.
* "gone" means the device is gone. We handle it the same as "force" as
* a following udev event will be caught, too.
*
* If it's our main DRM device and not "gone", tell the compositor to go asleep. */
if (!strcmp(type, "pause"))
launcher_logind_pause_device_complete(wl, major, minor);
else if (strcmp(type, "gone") == 0)
return;
if (wl->sync_drm && major == DRM_MAJOR)
launcher_logind_set_active(wl, false);
}
static void
device_resumed(struct launcher_logind *wl, DBusMessage *m)
{
bool r;
uint32_t major;
r = dbus_message_get_args(m, NULL,
DBUS_TYPE_UINT32, &major,
/*DBUS_TYPE_UINT32, &minor,
DBUS_TYPE_UNIX_FD, &fd,*/
DBUS_TYPE_INVALID);
if (!r) {
weston_log("logind: cannot parse ResumeDevice dbus signal\n");
return;
}
/* DeviceResumed messages provide us a new file-descriptor for
* resumed devices. For DRM devices it's the same as before, for evdev
* devices it's a new open-file. As we reopen evdev devices, anyway,
* there is no need for us to handle this event for evdev. For DRM, we
* notify the compositor to wake up. */
if (wl->sync_drm && major == DRM_MAJOR)
launcher_logind_set_active(wl, true);
}
static DBusHandlerResult
filter_dbus(DBusConnection *c, DBusMessage *m, void *data)
{
struct launcher_logind *wl = data;
if (dbus_message_is_signal(m, DBUS_INTERFACE_LOCAL, "Disconnected")) {
disconnected_dbus(wl);
} else if (dbus_message_is_signal(m, "org.freedesktop.login1.Manager",
"SessionRemoved")) {
session_removed(wl, m);
} else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties",
"PropertiesChanged")) {
property_changed(wl, m);
} else if (dbus_message_is_signal(m, "org.freedesktop.login1.Session",
"PauseDevice")) {
device_paused(wl, m);
} else if (dbus_message_is_signal(m, "org.freedesktop.login1.Session",
"ResumeDevice")) {
device_resumed(wl, m);
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static int
launcher_logind_setup_dbus(struct launcher_logind *wl)
{
bool b;
int r;
r = asprintf(&wl->spath, "/org/freedesktop/login1/session/%s",
wl->sid);
if (r < 0)
return -ENOMEM;
b = dbus_connection_add_filter(wl->dbus, filter_dbus, wl, NULL);
if (!b) {
weston_log("logind: cannot add dbus filter\n");
r = -ENOMEM;
goto err_spath;
}
r = weston_dbus_add_match_signal(wl->dbus,
"org.freedesktop.login1",
"org.freedesktop.login1.Manager",
"SessionRemoved",
"/org/freedesktop/login1");
if (r < 0) {
weston_log("logind: cannot add dbus match\n");
goto err_spath;
}
r = weston_dbus_add_match_signal(wl->dbus,
"org.freedesktop.login1",
"org.freedesktop.login1.Session",
"PauseDevice",
wl->spath);
if (r < 0) {
weston_log("logind: cannot add dbus match\n");
goto err_spath;
}
r = weston_dbus_add_match_signal(wl->dbus,
"org.freedesktop.login1",
"org.freedesktop.login1.Session",
"ResumeDevice",
wl->spath);
if (r < 0) {
weston_log("logind: cannot add dbus match\n");
goto err_spath;
}
r = weston_dbus_add_match_signal(wl->dbus,
"org.freedesktop.login1",
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
wl->spath);
if (r < 0) {
weston_log("logind: cannot add dbus match\n");
goto err_spath;
}
return 0;
err_spath:
/* don't remove any dbus-match as the connection is closed, anyway */
free(wl->spath);
return r;
}
static void
launcher_logind_destroy_dbus(struct launcher_logind *wl)
{
/* don't remove any dbus-match as the connection is closed, anyway */
free(wl->spath);
}
static int
launcher_logind_take_control(struct launcher_logind *wl)
{
DBusError err;
DBusMessage *m, *reply;
dbus_bool_t force;
bool b;
int r;
dbus_error_init(&err);
m = dbus_message_new_method_call("org.freedesktop.login1",
wl->spath,
"org.freedesktop.login1.Session",
"TakeControl");
if (!m)
return -ENOMEM;
force = false;
b = dbus_message_append_args(m,
DBUS_TYPE_BOOLEAN, &force,
DBUS_TYPE_INVALID);
if (!b) {
r = -ENOMEM;
goto err_unref;
}
reply = dbus_connection_send_with_reply_and_block(wl->dbus,
m, -1, &err);
if (!reply) {
if (dbus_error_has_name(&err, DBUS_ERROR_UNKNOWN_METHOD))
weston_log("logind: old systemd version detected\n");
else
weston_log("logind: cannot take control over session %s\n", wl->sid);
dbus_error_free(&err);
r = -EIO;
goto err_unref;
}
dbus_message_unref(reply);
dbus_message_unref(m);
return 0;
err_unref:
dbus_message_unref(m);
return r;
}
static void
launcher_logind_release_control(struct launcher_logind *wl)
{
DBusMessage *m;
m = dbus_message_new_method_call("org.freedesktop.login1",
wl->spath,
"org.freedesktop.login1.Session",
"ReleaseControl");
if (m) {
dbus_connection_send(wl->dbus, m, NULL);
dbus_message_unref(m);
}
}
static int
weston_sd_session_get_vt(const char *sid, unsigned int *out)
{
#ifdef HAVE_SYSTEMD_LOGIN_209
return sd_session_get_vt(sid, out);
#else
int r;
char *tty;
r = sd_session_get_tty(sid, &tty);
if (r < 0)
return r;
r = sscanf(tty, "tty%u", out);
free(tty);
if (r != 1)
return -EINVAL;
return 0;
#endif
}
static int
launcher_logind_activate(struct launcher_logind *wl)
{
DBusMessage *m;
m = dbus_message_new_method_call("org.freedesktop.login1",
wl->spath,
"org.freedesktop.login1.Session",
"Activate");
if (!m)
return -ENOMEM;
dbus_connection_send(wl->dbus, m, NULL);
return 0;
}
static int
launcher_logind_connect(struct weston_launcher **out, struct weston_compositor *compositor,
int tty, const char *seat_id, bool sync_drm)
{
struct launcher_logind *wl;
struct wl_event_loop *loop;
char *t;
int r;
wl = zalloc(sizeof(*wl));
if (wl == NULL) {
r = -ENOMEM;
goto err_out;
}
wl->base.iface = &launcher_logind_iface;
wl->compositor = compositor;
wl->sync_drm = sync_drm;
wl->seat = strdup(seat_id);
if (!wl->seat) {
r = -ENOMEM;
goto err_wl;
}
r = sd_pid_get_session(getpid(), &wl->sid);
if (r < 0) {
weston_log("logind: not running in a systemd session\n");
goto err_seat;
}
t = NULL;
r = sd_session_get_seat(wl->sid, &t);
if (r < 0) {
weston_log("logind: failed to get session seat\n");
free(t);
goto err_session;
} else if (strcmp(seat_id, t)) {
weston_log("logind: weston's seat '%s' differs from session-seat '%s'\n",
seat_id, t);
r = -EINVAL;
free(t);
goto err_session;
}
r = strcmp(t, "seat0");
free(t);
if (r == 0) {
r = weston_sd_session_get_vt(wl->sid, &wl->vtnr);
if (r < 0) {
weston_log("logind: session not running on a VT\n");
goto err_session;
} else if (tty > 0 && wl->vtnr != (unsigned int )tty) {
weston_log("logind: requested VT --tty=%d differs from real session VT %u\n",
tty, wl->vtnr);
r = -EINVAL;
goto err_session;
}
}
loop = wl_display_get_event_loop(compositor->wl_display);
r = weston_dbus_open(loop, DBUS_BUS_SYSTEM, &wl->dbus, &wl->dbus_ctx);
if (r < 0) {
weston_log("logind: cannot connect to system dbus\n");
goto err_session;
}
r = launcher_logind_setup_dbus(wl);
if (r < 0)
goto err_dbus;
r = launcher_logind_take_control(wl);
if (r < 0)
goto err_dbus_cleanup;
r = launcher_logind_activate(wl);
if (r < 0)
goto err_dbus_cleanup;
weston_log("logind: session control granted\n");
* (struct launcher_logind **) out = wl;
return 0;
err_dbus_cleanup:
launcher_logind_destroy_dbus(wl);
err_dbus:
weston_dbus_close(wl->dbus, wl->dbus_ctx);
err_session:
free(wl->sid);
err_seat:
free(wl->seat);
err_wl:
free(wl);
err_out:
weston_log("logind: cannot setup systemd-logind helper (%d), using legacy fallback\n", r);
errno = -r;
return -1;
}
static void
launcher_logind_destroy(struct weston_launcher *launcher)
{
struct launcher_logind *wl = wl_container_of(launcher, wl, base);
if (wl->pending_active) {
dbus_pending_call_cancel(wl->pending_active);
dbus_pending_call_unref(wl->pending_active);
}
launcher_logind_release_control(wl);
launcher_logind_destroy_dbus(wl);
weston_dbus_close(wl->dbus, wl->dbus_ctx);
free(wl->sid);
free(wl->seat);
free(wl);
}
static int
launcher_logind_get_vt(struct weston_launcher *launcher)
{
struct launcher_logind *wl = wl_container_of(launcher, wl, base);
return wl->vtnr;
}
const struct launcher_interface launcher_logind_iface = {
.connect = launcher_logind_connect,
.destroy = launcher_logind_destroy,
.open = launcher_logind_open,
.close = launcher_logind_close,
.activate_vt = launcher_logind_activate_vt,
.get_vt = launcher_logind_get_vt,
};