aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMake/FindLibcap.cmake56
-rw-r--r--CMakeLists.txt1
-rw-r--r--session/CMakeLists.txt5
-rw-r--r--session/direct.c165
4 files changed, 222 insertions, 5 deletions
diff --git a/CMake/FindLibcap.cmake b/CMake/FindLibcap.cmake
new file mode 100644
index 00000000..b34e5e37
--- /dev/null
+++ b/CMake/FindLibcap.cmake
@@ -0,0 +1,56 @@
+#.rst:
+# FindLibcap
+# -------
+#
+# Find Libcap library
+#
+# Try to find Libcap library. The following values are defined
+#
+# ::
+#
+# Libcap_FOUND - True if Libcap is available
+# Libcap_INCLUDE_DIRS - Include directories for Libcap
+# Libcap_LIBRARIES - List of libraries for Libcap
+# Libcap_DEFINITIONS - List of definitions for Libcap
+#
+# and also the following more fine grained variables
+#
+# ::
+#
+# Libcap_VERSION
+# Libcap_VERSION_MAJOR
+# Libcap_VERSION_MINOR
+#
+#=============================================================================
+# Copyright (c) 2017 Jerzi Kaminsky
+#
+# Distributed under the OSI-approved BSD License (the "License");
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+
+include(FeatureSummary)
+set_package_properties(Libcap PROPERTIES
+ URL "https://www.kernel.org/pub/linux/libs/security/linux-privs/libcap2"
+ DESCRIPTION "Library for getting and setting POSIX.1e capabilities")
+
+find_package(PkgConfig)
+pkg_check_modules(PC_CAP QUIET Libcap)
+find_library(Libcap_LIBRARIES NAMES cap HINTS ${PC_CAP_LIBRARY_DIRS})
+find_path(Libcap_INCLUDE_DIRS sys/capability.h HINTS ${PC_CAP_INCLUDE_DIRS})
+
+set(Libcap_VERSION ${PC_CAP_VERSION})
+string(REPLACE "." ";" VERSION_LIST "${PC_CAP_VERSION}")
+
+LIST(LENGTH VERSION_LIST n)
+if (n EQUAL 2)
+ list(GET VERSION_LIST 0 Libcap_VERSION_MAJOR)
+ list(GET VERSION_LIST 1 Libcap_VERSION_MINOR)
+endif ()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Libcap DEFAULT_MSG Libcap_INCLUDE_DIRS Libcap_LIBRARIES)
+mark_as_advanced(Libcap_INCLUDE_DIRS Libcap_LIBRARIES Libcap_DEFINITIONS
+ Libcap_VERSION Libcap_VERSION_MAJOR Libcap_VERSION_MICRO Libcap_VERSION_MINOR)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b15882ce..ba1cfba8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -50,6 +50,7 @@ find_package(GBM REQUIRED)
find_package(LibInput REQUIRED)
find_package(XKBCommon REQUIRED)
find_package(Udev REQUIRED)
+find_package(Libcap REQUIRED)
find_package(Systemd)
include(Wayland)
diff --git a/session/CMakeLists.txt b/session/CMakeLists.txt
index 23077c12..72ef9f56 100644
--- a/session/CMakeLists.txt
+++ b/session/CMakeLists.txt
@@ -1,5 +1,7 @@
include_directories(
- ${WAYLAND_INCLUDE_DIR}
+ ${WAYLAND_INCLUDE_DIR}
+ ${DRM_INCLUDE_DIRS}
+ ${Libcap_INCLUDE_DIRS}
)
set(sources
@@ -10,6 +12,7 @@ set(sources
set(libs
wlr-util
${WAYLAND_LIBRARIES}
+ ${Libcap_LIBRARIES}
)
if (SYSTEMD_FOUND)
diff --git a/session/direct.c b/session/direct.c
index 74f2faf1..e128658a 100644
--- a/session/direct.c
+++ b/session/direct.c
@@ -5,36 +5,174 @@
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/capability.h>
+#include <linux/kd.h>
+#include <linux/major.h>
+#include <linux/vt.h>
#include <wayland-server.h>
+#include <xf86drm.h>
#include <wlr/session/interface.h>
#include <wlr/util/log.h>
+#ifndef KDSKBMUTE
+#define KDSKBMUTE 0x4B51
+#endif
+
+enum { DRM_MAJOR = 226 };
+
const struct session_impl session_direct;
struct direct_session {
struct wlr_session base;
+ int tty_fd;
+ int drm_fd;
+ int kb_mode;
+
+ struct wl_event_source *vt_source;
};
static int direct_session_open(struct wlr_session *restrict base,
- const char *restrict path) {
- return open(path, O_RDWR | O_CLOEXEC);
+ const char *restrict path) {
+ struct direct_session *session = wl_container_of(base, session, base);
+ int fd = open(path, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK);
+ if (fd == -1) {
+ wlr_log_errno(L_ERROR, "%s", path);
+ return -errno;
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) == 0 && major(st.st_rdev) == DRM_MAJOR) {
+ session->drm_fd = fd;
+ drmSetMaster(fd);
+ }
+
+ return fd;
}
static void direct_session_close(struct wlr_session *base, int fd) {
+ struct direct_session *session = wl_container_of(base, session, base);
+
+ if (fd == session->drm_fd) {
+ drmDropMaster(fd);
+ session->drm_fd = -1;
+ }
+
close(fd);
}
static bool direct_change_vt(struct wlr_session *base, int vt) {
- // TODO
- return false;
+ struct direct_session *session = wl_container_of(base, session, base);
+ return ioctl(session->tty_fd, VT_ACTIVATE, vt) == 0;
}
static void direct_session_finish(struct wlr_session *base) {
struct direct_session *session = wl_container_of(base, session, base);
+ struct vt_mode mode = {
+ .mode = VT_AUTO,
+ };
+ if (ioctl(session->tty_fd, KDSKBMUTE, 0)) {
+ ioctl(session->tty_fd, KDSKBMODE, session->kb_mode);
+ }
+ ioctl(session->tty_fd, KDSETMODE, KD_TEXT);
+ ioctl(session->tty_fd, VT_SETMODE, &mode);
+
+ wl_event_source_remove(session->vt_source);
+ close(session->tty_fd);
free(session);
}
+static int vt_handler(int signo, void *data) {
+ struct direct_session *session = data;
+
+ if (session->base.active) {
+ session->base.active = false;
+ wl_signal_emit(&session->base.session_signal, session);
+ drmDropMaster(session->drm_fd);
+ ioctl(session->tty_fd, VT_RELDISP, 1);
+ } else {
+ ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ);
+ drmSetMaster(session->drm_fd);
+ session->base.active = true;
+ wl_signal_emit(&session->base.session_signal, session);
+ }
+
+ return 1;
+}
+
+static bool setup_tty(struct direct_session *session, struct wl_display *display) {
+ session->tty_fd = dup(STDIN_FILENO);
+
+ struct stat st;
+ if (fstat(session->tty_fd, &st) == -1 || major(st.st_rdev) != TTY_MAJOR ||
+ minor(st.st_rdev) == 0) {
+ wlr_log(L_ERROR, "Not running from a virtual terminal");
+ goto error;
+ }
+
+ int ret;
+
+ int kd_mode;
+ ret = ioctl(session->tty_fd, KDGETMODE, &kd_mode);
+ if (ret) {
+ wlr_log_errno(L_ERROR, "Failed to get tty mode");
+ goto error;
+ }
+
+ if (kd_mode != KD_TEXT) {
+ wlr_log(L_ERROR,
+ "tty already in graphics mode; is another display server running?");
+ goto error;
+ }
+
+ ioctl(session->tty_fd, VT_ACTIVATE, minor(st.st_rdev));
+ ioctl(session->tty_fd, VT_WAITACTIVE, minor(st.st_rdev));
+
+ if (ioctl(session->tty_fd, KDGKBMODE, &session->kb_mode)) {
+ wlr_log_errno(L_ERROR, "Failed to read keyboard mode");
+ goto error;
+ }
+
+ if (ioctl(session->tty_fd, KDSKBMUTE, 1) &&
+ ioctl(session->tty_fd, KDSKBMODE, K_OFF)) {
+ wlr_log_errno(L_ERROR, "Failed to set keyboard mode");
+ goto error;
+ }
+
+ if (ioctl(session->tty_fd, KDSETMODE, KD_GRAPHICS)) {
+ wlr_log_errno(L_ERROR, "Failed to get graphics mode on tty");
+ goto error;
+ }
+
+ struct vt_mode mode = {
+ .mode = VT_PROCESS,
+ .relsig = SIGUSR1,
+ .acqsig = SIGUSR1,
+ };
+
+ if (ioctl(session->tty_fd, VT_SETMODE, &mode) < 0) {
+ wlr_log(L_ERROR, "Failed to take control of tty");
+ goto error;
+ }
+
+ struct wl_event_loop *loop = wl_display_get_event_loop(display);
+ session->vt_source = wl_event_loop_add_signal(loop, SIGUSR1,
+ vt_handler, session);
+ if (!session->vt_source) {
+ goto error;
+ }
+
+ return true;
+
+error:
+ close(session->tty_fd);
+ return false;
+}
+
static struct wlr_session *direct_session_start(struct wl_display *disp) {
struct direct_session *session = calloc(1, sizeof(*session));
if (!session) {
@@ -42,12 +180,31 @@ static struct wlr_session *direct_session_start(struct wl_display *disp) {
return NULL;
}
+ cap_t cap = cap_get_proc();
+ cap_flag_value_t val;
+
+ if (!cap || cap_get_flag(cap, CAP_SYS_ADMIN, CAP_PERMITTED, &val) || val != CAP_SET) {
+ wlr_log(L_ERROR, "Do not have CAP_SYS_ADMIN; cannot become DRM master");
+ cap_free(cap);
+ goto error_session;
+ }
+
+ cap_free(cap);
+
+ if (!setup_tty(session, disp)) {
+ goto error_session;
+ }
+
wlr_log(L_INFO, "Successfully loaded direct session");
session->base.impl = &session_direct;
session->base.active = true;
wl_signal_init(&session->base.session_signal);
return &session->base;
+
+error_session:
+ free(session);
+ return NULL;
}
const struct session_impl session_direct = {