/* * 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 #include #include #include #include #include #include #include #include #include #include #include "compositor.h" #include "dbus.h" #include "launcher-impl.h" #define DRM_MAJOR 226 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); 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 void launcher_logind_restore(struct weston_launcher *launcher) { } 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"); launcher_logind_restore(&wl->base); 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"); launcher_logind_restore(&wl->base); 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, tell the compositor to go asleep. */ if (!strcmp(type, "pause")) launcher_logind_pause_device_complete(wl, major, minor); 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; } free(t); 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); } struct launcher_interface launcher_logind_iface = { launcher_logind_connect, launcher_logind_destroy, launcher_logind_open, launcher_logind_close, launcher_logind_activate_vt, launcher_logind_restore, };