Skip to content
Snippets Groups Projects
Commit 3148f42f authored by Apertis package maintainers's avatar Apertis package maintainers
Browse files

Import Upstream version 0.9.1

parents
Branches upstream/trixie
Tags upstream/0.9.1
No related merge requests found
image: alpine/edge
packages:
- meson
- linux-headers
- clang
- clang-extra-tools
- clang-analyzer
- scdoc
sources:
- https://git.sr.ht/~kennylevinsen/seatd
tasks:
- prepare: |
meson -Dlibseat-seatd=enabled -Dlibseat-builtin=enabled -Dlibseat-logind=disabled -Dexamples=enabled build seatd
- build: |
ninja -C build
sudo ninja -C build install
- unittest: |
ninja -C build test
- scan-build: |
ninja -C build scan-build
[ -z "$(ls -A build/meson-logs/scanbuild/ 2>/dev/null)" ]
- smoketest: |
timeout -s KILL 30s sudo ./build/seatd-launch -l debug -- ./build/simpletest /dev/dri/card0
- smoketest-builtin: |
timeout -s KILL 30s sudo LIBSEAT_BACKEND=builtin ./build/simpletest /dev/dri/card0
- check-format: |
ninja -C build clang-format
git -C seatd diff --exit-code
image: archlinux
packages:
- meson
- linux-headers
- clang
- clang-analyzer
- scdoc
sources:
- https://git.sr.ht/~kennylevinsen/seatd
tasks:
- prepare: |
meson -Db_sanitize=address -Dlibseat-logind=auto -Dlibseat-seatd=enabled -Dlibseat-builtin=enabled -Dexamples=enabled build seatd
- build: |
ninja -C build
sudo ninja -C build install
- unittest: |
ninja -C build test
- scan-build: |
ninja -C build scan-build
[ -z "$(ls -A build/meson-logs/scanbuild/ 2>/dev/null)" ]
- smoketest: |
timeout -s KILL 30s sudo ./build/seatd-launch -l debug -- ./build/simpletest /dev/input/event0
- smoketest-builtin: |
timeout -s KILL 30s sudo LIBSEAT_BACKEND=builtin ./build/simpletest /dev/input/event0
image: freebsd/latest
packages:
- meson
sources:
- https://git.sr.ht/~kennylevinsen/seatd
tasks:
- prepare: |
meson -Dlibseat-seatd=enabled -Dlibseat-builtin=enabled -Dlibseat-logind=disabled build seatd
- build: |
ninja -C build
- unittest: |
ninja -C build test
- smoketest: |
rm -rf build
meson -Db_lundef=false -Db_sanitize=address -Dlibseat-seatd=enabled -Dlibseat-builtin=enabled -Dexamples=enabled -Dlibseat-logind=disabled build seatd
ninja -C build
sudo ninja -C build install
timeout -s KILL 30s sudo ./build/seatd-launch -l debug -- ./build/simpletest /dev/input/event0
- smoketest-builtin: |
timeout -s KILL 30s sudo LIBSEAT_BACKEND=builtin ./build/simpletest /dev/input/event0
image: netbsd/latest
packages:
- meson
sources:
- https://git.sr.ht/~kennylevinsen/seatd
tasks:
- wscons: |
echo 'wscons=YES' | sudo tee -a /etc/rc.conf
sudo /etc/rc.d/wscons start
sudo /etc/rc.d/ttys restart
- prepare: |
meson -Dlibseat-seatd=enabled -Dlibseat-builtin=enabled -Dlibseat-logind=disabled build seatd
- build: |
ninja -C build
- unittest: |
ninja -C build test
- smoketest: |
rm -rf build
meson -Db_lundef=false -Db_sanitize=address -Dlibseat-seatd=enabled -Dlibseat-builtin=enabled -Dexamples=enabled -Dlibseat-logind=disabled build seatd
ninja -C build
sudo ninja -C build install
timeout -s SIGKILL 30s sudo SEATD_LOGLEVEL=debug ./build/seatd-launch ./build/simpletest /dev/wskbd
- smoketest-builtin: |
timeout -s SIGKILL 30s sudo LIBSEAT_BACKEND=builtin ./build/simpletest /dev/wskbd
---
IndentWidth: 8
TabWidth: 8
ContinuationIndentWidth: 8
UseTab: ForContinuationAndIndentation
ColumnLimit: 100
AlignConsecutiveMacros: true
AlignEscapedNewlines: Left
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortFunctionsOnASingleLine: Empty
BreakBeforeBinaryOperators: None
BreakStringLiterals: false
PenaltyExcessCharacter: 10
PenaltyBreakBeforeFirstCallParameter: 999999
PenaltyBreakAssignment: 10
MaxEmptyLinesToKeep: 1
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
---
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab
indent_size = 8
Copyright 2020 Kenny Levinsen
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 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.
# seatd and libseat
A minimal seat management daemon, and a universal seat management library.
Currently supports Linux and FreeBSD, and has experimental NetBSD support.
## What is seat management?
Seat management takes care of mediating access to shared devices (graphics, input), without requiring the applications needing access to be root.
## What's in the box?
### seatd
A seat management daemon, that does everything it needs to do. Nothing more, nothing less. Depends only on libc.
### libseat
A seat management library allowing applications to use whatever seat management is available.
Supports:
- seatd
- (e)logind
- embedded seatd for standalone operation
Each backend can be compile-time included and is runtime auto-detected or manually selected with the `LIBSEAT_BACKEND` environment variable.
Which backend is in use is transparent to the application, providing a simple common interface.
## Why not (e)logind?
systemd-logind is not portable, and being part of the systemd project, it cannot be used in an environment not based on systemd. Furthermore, "simple" is definitely not within the set of adjectives that can be used to describe logind. For those in the dark, [take a glance at its API](https://www.freedesktop.org/wiki/Software/systemd/logind/). Plus, competition is healthy.
elogind tries to isolate systemd-logind form systemd through brute-force. This requires actively fighting against upstream design decisions for deep integration, and the efforts must be repeated every time one syncs with upstream. And even after all this work, one is left with nothing but a hackjob.
Why spend time isolating logind and keeping up with upstream when we could instead create something better with less work?
## Why does libseat support (e)logind?
[In order to not be part of the problem](https://xkcd.com/927/). We will not displace systemd-logind anytime soon, so for user shells like [sway](https://github.com/swaywm/sway), seatd joins the ranks of logind and direct session management for things they need to support.
Instead of giving user shell developers more work, libseat aims to make supporting seatd less work than what they're currently implementing. This is done by taking care of all the seat management needs with multiple backends, providing not only seatd support, but replacing the existing logind and direct seat management implementations.
## How to discuss
Go to [#kennylevinsen @ irc.libera.chat](ircs://irc.libera.chat/#kennylevinsen) to discuss, or use [~kennylevinsen/seatd-devel@lists.sr.ht](https://lists.sr.ht/~kennylevinsen/seatd-devel).
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "connection.h"
static inline uint32_t connection_buffer_mask(const uint32_t idx) {
return idx & (CONNECTION_BUFFER_SIZE - 1);
}
static inline uint32_t connection_buffer_size(const struct connection_buffer *b) {
return b->head - b->tail;
}
static inline void connection_buffer_consume(struct connection_buffer *b, const size_t size) {
b->tail += size;
}
static inline void connection_buffer_restore(struct connection_buffer *b, const size_t size) {
b->tail -= size;
}
/*
* connection_buffer_get_iov prepares I/O vectors pointing to our ring buffer.
* Two may be used if the buffer has wrapped around.
*/
static void connection_buffer_get_iov(struct connection_buffer *b, struct iovec *iov, int *count) {
uint32_t head = connection_buffer_mask(b->head);
uint32_t tail = connection_buffer_mask(b->tail);
if (tail < head) {
iov[0].iov_base = b->data + tail;
iov[0].iov_len = head - tail;
*count = 1;
} else if (head == 0) {
iov[0].iov_base = b->data + tail;
iov[0].iov_len = sizeof b->data - tail;
*count = 1;
} else {
iov[0].iov_base = b->data + tail;
iov[0].iov_len = sizeof b->data - tail;
iov[1].iov_base = b->data;
iov[1].iov_len = head;
*count = 2;
}
}
/*
* connection_buffer_put_iov prepares I/O vectors pointing to our ring buffer.
* Two may be used if the buffer has wrapped around.
*/
static void connection_buffer_put_iov(struct connection_buffer *b, struct iovec *iov, int *count) {
uint32_t head = connection_buffer_mask(b->head);
uint32_t tail = connection_buffer_mask(b->tail);
if (head < tail) {
iov[0].iov_base = b->data + head;
iov[0].iov_len = tail - head;
*count = 1;
} else if (tail == 0) {
iov[0].iov_base = b->data + head;
iov[0].iov_len = sizeof b->data - head;
*count = 1;
} else {
iov[0].iov_base = b->data + head;
iov[0].iov_len = sizeof b->data - head;
iov[1].iov_base = b->data;
iov[1].iov_len = tail;
*count = 2;
}
}
/*
* connection_buffer_copy copies from our ring buffer into a linear buffer.
*/
static void connection_buffer_copy(const struct connection_buffer *b, void *data, const size_t count) {
uint32_t tail = connection_buffer_mask(b->tail);
if (tail + count <= sizeof b->data) {
memcpy(data, b->data + tail, count);
return;
}
uint32_t size = sizeof b->data - tail;
memcpy(data, b->data + tail, size);
memcpy((char *)data + size, b->data, count - size);
}
/*
* connection_buffer_copy copies from a linear buffer into our ring buffer.
*/
static int connection_buffer_put(struct connection_buffer *b, const void *data, const size_t count) {
if (count > sizeof(b->data)) {
errno = EOVERFLOW;
return -1;
}
uint32_t head = connection_buffer_mask(b->head);
if (head + count <= sizeof b->data) {
memcpy(b->data + head, data, count);
} else {
uint32_t size = sizeof b->data - head;
memcpy(b->data + head, data, size);
memcpy(b->data, (const char *)data + size, count - size);
}
b->head += count;
return 0;
}
/*
* close_fds closes all fds within a connection_buffer
*/
static void connection_buffer_close_fds(struct connection_buffer *buffer) {
size_t size = connection_buffer_size(buffer);
if (size == 0) {
return;
}
int fds[sizeof(buffer->data) / sizeof(int)];
connection_buffer_copy(buffer, fds, size);
int count = size / sizeof fds[0];
size = count * sizeof fds[0];
for (int idx = 0; idx < count; idx++) {
close(fds[idx]);
}
connection_buffer_consume(buffer, size);
}
/*
* build_cmsg prepares a cmsg from a buffer full of fds
*/
static void build_cmsg(struct connection_buffer *buffer, char *data, int *clen) {
size_t size = connection_buffer_size(buffer);
if (size > MAX_FDS * sizeof(int)) {
size = MAX_FDS * sizeof(int);
}
if (size <= 0) {
*clen = 0;
return;
}
struct cmsghdr *cmsg = (struct cmsghdr *)data;
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(size);
connection_buffer_copy(buffer, CMSG_DATA(cmsg), size);
*clen = cmsg->cmsg_len;
}
static int decode_cmsg(struct connection_buffer *buffer, struct msghdr *msg) {
bool overflow = false;
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
continue;
}
size_t size = cmsg->cmsg_len - CMSG_LEN(0);
size_t max = sizeof(buffer->data) - connection_buffer_size(buffer);
if (size > max || overflow) {
overflow = true;
size /= sizeof(int);
for (size_t idx = 0; idx < size; idx++) {
close(((int *)CMSG_DATA(cmsg))[idx]);
}
} else if (connection_buffer_put(buffer, CMSG_DATA(cmsg), size) < 0) {
return -1;
}
}
if (overflow) {
errno = EOVERFLOW;
return -1;
}
return 0;
}
int connection_read(struct connection *connection) {
if (connection_buffer_size(&connection->in) >= sizeof(connection->in.data)) {
errno = EOVERFLOW;
return -1;
}
int count;
struct iovec iov[2];
connection_buffer_put_iov(&connection->in, iov, &count);
char cmsg[CMSG_LEN(CONNECTION_BUFFER_SIZE)];
struct msghdr msg = {
.msg_name = NULL,
.msg_namelen = 0,
.msg_iov = iov,
.msg_iovlen = count,
.msg_control = cmsg,
.msg_controllen = sizeof cmsg,
.msg_flags = 0,
};
int len;
do {
len = recvmsg(connection->fd, &msg, MSG_DONTWAIT | MSG_CMSG_CLOEXEC);
if (len == -1 && errno != EINTR)
return -1;
} while (len == -1);
if (decode_cmsg(&connection->fds_in, &msg) != 0) {
return -1;
}
connection->in.head += len;
return connection_buffer_size(&connection->in);
}
int connection_flush(struct connection *connection) {
if (!connection->want_flush) {
return 0;
}
uint32_t tail = connection->out.tail;
while (connection->out.head - connection->out.tail > 0) {
int count;
struct iovec iov[2];
connection_buffer_get_iov(&connection->out, iov, &count);
int clen;
char cmsg[CMSG_LEN(CONNECTION_BUFFER_SIZE)];
build_cmsg(&connection->fds_out, cmsg, &clen);
struct msghdr msg = {
.msg_name = NULL,
.msg_namelen = 0,
.msg_iov = iov,
.msg_iovlen = count,
.msg_control = (clen > 0) ? cmsg : NULL,
.msg_controllen = clen,
.msg_flags = 0,
};
int len;
do {
len = sendmsg(connection->fd, &msg, MSG_NOSIGNAL | MSG_DONTWAIT);
if (len == -1 && errno != EINTR)
return -1;
} while (len == -1);
connection_buffer_close_fds(&connection->fds_out);
connection->out.tail += len;
}
connection->want_flush = 0;
return connection->out.head - tail;
}
int connection_put(struct connection *connection, const void *data, size_t count) {
if (connection_buffer_size(&connection->out) + count > CONNECTION_BUFFER_SIZE) {
connection->want_flush = 1;
if (connection_flush(connection) == -1) {
return -1;
}
}
if (connection_buffer_put(&connection->out, data, count) == -1) {
return -1;
}
connection->want_flush = 1;
return 0;
}
int connection_put_fd(struct connection *connection, int fd) {
if (connection_buffer_size(&connection->fds_out) >= MAX_FDS * sizeof fd) {
errno = EOVERFLOW;
return -1;
}
return connection_buffer_put(&connection->fds_out, &fd, sizeof fd);
}
int connection_get(struct connection *connection, void *dst, size_t count) {
if (count > connection_buffer_size(&connection->in)) {
errno = EAGAIN;
return -1;
}
connection_buffer_copy(&connection->in, dst, count);
connection_buffer_consume(&connection->in, count);
return count;
}
int connection_get_fd(struct connection *connection, int *fd) {
if (sizeof(int) > connection_buffer_size(&connection->fds_in)) {
errno = EAGAIN;
return -1;
}
connection_buffer_copy(&connection->fds_in, fd, sizeof(int));
connection_buffer_consume(&connection->fds_in, sizeof(int));
return 0;
}
void connection_close_fds(struct connection *connection) {
connection_buffer_close_fds(&connection->fds_in);
connection_buffer_close_fds(&connection->fds_out);
}
size_t connection_pending(struct connection *connection) {
return connection_buffer_size(&connection->in);
}
void connection_restore(struct connection *connection, size_t count) {
connection_buffer_restore(&connection->in, count);
}
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include "drm.h"
// From libdrm
#define DRM_IOCTL_BASE 'd'
#define DRM_IO(nr) _IO(DRM_IOCTL_BASE, nr)
#define DRM_IOCTL_SET_MASTER DRM_IO(0x1e)
#define DRM_IOCTL_DROP_MASTER DRM_IO(0x1f)
#define STRLEN(s) ((sizeof(s) / sizeof(s[0])) - 1)
#define STR_HAS_PREFIX(prefix, s) (strncmp(prefix, s, STRLEN(prefix)) == 0)
int drm_set_master(int fd) {
return ioctl(fd, DRM_IOCTL_SET_MASTER, 0);
}
int drm_drop_master(int fd) {
return ioctl(fd, DRM_IOCTL_DROP_MASTER, 0);
}
#if defined(__linux__) || defined(__NetBSD__)
int path_is_drm(const char *path) {
if (STR_HAS_PREFIX("/dev/dri/", path))
return 1;
return 0;
}
#elif defined(__FreeBSD__)
int path_is_drm(const char *path) {
if (STR_HAS_PREFIX("/dev/dri/", path))
return 1;
/* Some drivers have /dev/dri/X symlinked to /dev/drm/X */
if (STR_HAS_PREFIX("/dev/drm/", path))
return 1;
return 0;
}
#else
#error Unsupported platform
#endif
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#if defined(__linux__)
#include <linux/input.h>
#include <linux/major.h>
#include <sys/sysmacros.h>
#elif defined(__FreeBSD__)
#include <dev/evdev/input.h>
#endif
#include "evdev.h"
#define STRLEN(s) ((sizeof(s) / sizeof(s[0])) - 1)
#if defined(__linux__) || defined(__FreeBSD__)
int path_is_evdev(const char *path) {
static const char prefix[] = "/dev/input/event";
static const size_t prefixlen = STRLEN(prefix);
return strncmp(prefix, path, prefixlen) == 0;
}
int evdev_revoke(int fd) {
return ioctl(fd, EVIOCREVOKE, NULL);
}
#else
int path_is_evdev(const char *path) {
(void)path;
return 0;
}
int evdev_revoke(int fd) {
(void)fd;
return 0;
}
#endif
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#if defined(__linux__)
#include <linux/hidraw.h>
#endif
#include "hidraw.h"
#include "log.h"
#define STRLEN(s) ((sizeof(s) / sizeof(s[0])) - 1)
#if defined(__linux__) && defined(HIDIOCREVOKE)
int path_is_hidraw(const char *path) {
static const char prefix[] = "/dev/hidraw";
static const size_t prefixlen = STRLEN(prefix);
return strncmp(prefix, path, prefixlen) == 0;
}
int hidraw_revoke(int fd) {
return ioctl(fd, HIDIOCREVOKE, NULL);
}
#else
int path_is_hidraw(const char *path) {
(void)path;
return 0;
}
int hidraw_revoke(int fd) {
(void)fd;
return 0;
}
#endif
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include "linked_list.h"
void linked_list_init(struct linked_list *list) {
list->next = list;
list->prev = list;
}
void linked_list_insert(struct linked_list *list, struct linked_list *elem) {
assert(list->prev != NULL && list->next != NULL);
assert(elem->prev == elem->next);
elem->prev = list;
elem->next = list->next;
list->next = elem;
elem->next->prev = elem;
}
void linked_list_remove(struct linked_list *elem) {
assert(elem->prev != NULL && elem->next != NULL);
elem->prev->next = elem->next;
elem->next->prev = elem->prev;
elem->next = NULL;
elem->prev = NULL;
}
bool linked_list_empty(struct linked_list *list) {
assert(list->prev != NULL && list->next != NULL);
return list->next == list;
}
void linked_list_take(struct linked_list *target, struct linked_list *source) {
if (linked_list_empty(source)) {
return;
}
source->next->prev = target;
source->prev->next = target->next;
target->next->prev = source->prev;
target->next = source->next;
linked_list_init(source);
}
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "log.h"
const long NSEC_PER_SEC = 1000000000;
static void log_stderr(enum libseat_log_level level, const char *fmt, va_list args);
static enum libseat_log_level current_log_level = LIBSEAT_LOG_LEVEL_SILENT;
static libseat_log_func current_log_handler = log_stderr;
static struct timespec start_time = {-1, -1};
static bool colored = false;
static const char *verbosity_colors[] = {
[LIBSEAT_LOG_LEVEL_SILENT] = "",
[LIBSEAT_LOG_LEVEL_ERROR] = "\x1B[1;31m",
[LIBSEAT_LOG_LEVEL_INFO] = "\x1B[1;34m",
[LIBSEAT_LOG_LEVEL_DEBUG] = "\x1B[1;90m",
};
static const char *verbosity_headers[] = {
[LIBSEAT_LOG_LEVEL_SILENT] = "",
[LIBSEAT_LOG_LEVEL_ERROR] = "[ERROR]",
[LIBSEAT_LOG_LEVEL_INFO] = "[INFO]",
[LIBSEAT_LOG_LEVEL_DEBUG] = "[DEBUG]",
};
static void timespec_sub(struct timespec *r, const struct timespec *a, const struct timespec *b) {
r->tv_sec = a->tv_sec - b->tv_sec;
r->tv_nsec = a->tv_nsec - b->tv_nsec;
if (r->tv_nsec < 0) {
r->tv_sec--;
r->tv_nsec += NSEC_PER_SEC;
}
}
static void log_stderr(enum libseat_log_level level, const char *fmt, va_list args) {
struct timespec ts = {0};
clock_gettime(CLOCK_MONOTONIC, &ts);
timespec_sub(&ts, &ts, &start_time);
unsigned c = (level < LIBSEAT_LOG_LEVEL_LAST) ? level : LIBSEAT_LOG_LEVEL_LAST - 1;
const char *prefix, *postfix;
if (colored) {
prefix = verbosity_colors[c];
postfix = "\x1B[0m\n";
} else {
prefix = verbosity_headers[c];
postfix = "\n";
}
fprintf(stderr, "%02d:%02d:%02d.%03ld %s ", (int)(ts.tv_sec / 60 / 60),
(int)(ts.tv_sec / 60 % 60), (int)(ts.tv_sec % 60), ts.tv_nsec / 1000000, prefix);
vfprintf(stderr, fmt, args);
fprintf(stderr, "%s", postfix);
}
void log_init(void) {
if (start_time.tv_sec >= 0) {
return;
}
clock_gettime(CLOCK_MONOTONIC, &start_time);
colored = isatty(STDERR_FILENO);
}
void _logf(enum libseat_log_level level, const char *fmt, ...) {
int stored_errno = errno;
va_list args;
if (level > current_log_level) {
return;
}
va_start(args, fmt);
current_log_handler(level, fmt, args);
va_end(args);
errno = stored_errno;
}
void libseat_set_log_handler(libseat_log_func handler) {
if (handler == NULL) {
handler = log_stderr;
}
current_log_handler = handler;
}
void libseat_set_log_level(enum libseat_log_level level) {
current_log_level = level;
}
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#if defined(__linux__)
#include <linux/kd.h>
#include <linux/vt.h>
#define K_ENABLE K_UNICODE
#define K_DISABLE K_OFF
#define FRSIG 0
#elif defined(__FreeBSD__)
#include <sys/consio.h>
#include <sys/kbio.h>
#include <termios.h>
#define K_ENABLE K_XLATE
#define K_DISABLE K_RAW
#define FRSIG SIGIO
#elif defined(__NetBSD__)
#include <dev/wscons/wsdisplay_usl_io.h>
#define K_ENABLE K_XLATE
#define K_DISABLE K_RAW
#define FRSIG 0 // unimplemented
#else
#error Unsupported platform
#endif
#include "log.h"
#include "terminal.h"
#define TTYPATHLEN 16
#if defined(__FreeBSD__)
static int get_tty_path(int tty, char path[static TTYPATHLEN]) {
assert(tty >= 0);
const char prefix[] = "/dev/ttyv";
const size_t prefix_len = sizeof(prefix) - 1;
strcpy(path, prefix);
// The FreeBSD VT_GETACTIVE is implemented in the kernel as follows:
//
// static int
// vtterm_ioctl(struct terminal *tm, u_long cmd, caddr_t data,
// struct thread *td)
// {
// struct vt_window *vw = tm->tm_softc;
// struct vt_device *vd = vw->vw_device;
// ...
// switch (cmd) {
// ...
// case VT_GETACTIVE:
// *(int *)data = vd->vd_curwindow->vw_number + 1;
// return (0);
// ...
// }
// ...
// }
//
// The side-effect here being that the returned VT number is one
// greater than the internal VT number. The internal number is what is
// used to number the TTY device, while the external number is what we
// use in e.g. VT switching.
//
// We subtract one from the requested TTY number to compensate. If the
// user asked for TTY 0 (which is special on Linux), we just give them
// the first tty.
if (tty > 0) {
tty--;
}
// The FreeBSD tty name is constructed in the kernel as follows:
//
// static void
// vtterm_cnprobe(struct terminal *tm, struct consdev *cp)
// {
// ...
// struct vt_window *vw = tm->tm_softc;
// ...
// sprintf(cp->cn_name, "ttyv%r", VT_UNIT(vw));
// ...
// }
//
// With %r being a FreeBSD-internal radix formatter (seemingly set to
// base 32), and VT_UNIT expanding to the following to extract the
// internal VT number (which is one less than the external VT number):
//
// ((vw)->vw_device->vd_unit * VT_MAXWINDOWS + (vw)->vw_number)
//
// As the %r formatter is kernel-internal, we implement the base 32
// encoding ourselves below.
size_t offset = prefix_len;
if (tty == 0) {
path[offset++] = '0';
path[offset++] = '\0';
return 0;
}
const int base = 32;
for (int remaining = tty; remaining > 0; remaining /= base, offset++) {
// Return early if the buffer is too small.
if (offset + 1 >= TTYPATHLEN) {
errno = ENOMEM;
return -1;
}
const int value = remaining % base;
if (value >= 10) {
path[offset] = 'a' + value - 10;
} else {
path[offset] = '0' + value;
}
}
const size_t num_len = offset - prefix_len;
for (size_t i = 0; i < num_len / 2; i++) {
const size_t p1 = prefix_len + i;
const size_t p2 = offset - 1 - i;
const char tmp = path[p1];
path[p1] = path[p2];
path[p2] = tmp;
}
path[offset++] = '\0';
return 0;
}
#elif defined(__linux__)
static int get_tty_path(int tty, char path[static TTYPATHLEN]) {
assert(tty >= 0);
if (snprintf(path, TTYPATHLEN, "/dev/tty%d", tty) == -1) {
return -1;
}
return 0;
}
#elif defined(__NetBSD__)
static int get_tty_path(int tty, char path[static TTYPATHLEN]) {
assert(tty >= 0);
if (snprintf(path, TTYPATHLEN, "/dev/ttyE%d", tty) == -1) {
return -1;
}
return 0;
}
#else
#error Unsupported platform
#endif
int terminal_open(int vt) {
char path[TTYPATHLEN];
if (get_tty_path(vt, path) == -1) {
log_errorf("Could not generate tty path: %s", strerror(errno));
return -1;
}
int fd = open(path, O_RDWR | O_NOCTTY);
if (fd == -1) {
log_errorf("Could not open target tty: %s", strerror(errno));
return -1;
}
return fd;
}
int terminal_current_vt(int fd) {
#if defined(__linux__) || defined(__NetBSD__)
struct vt_stat st;
int res = ioctl(fd, VT_GETSTATE, &st);
if (res == -1) {
log_errorf("Could not retrieve VT state: %s", strerror(errno));
return -1;
}
return st.v_active;
#elif defined(__FreeBSD__)
int vt;
int res = ioctl(fd, VT_GETACTIVE, &vt);
if (res == -1) {
log_errorf("Could not retrieve VT state: %s", strerror(errno));
return -1;
}
if (vt == -1) {
log_errorf("Invalid VT: %d", vt);
return -1;
}
return vt;
#else
#error Unsupported platform
#endif
}
int terminal_set_process_switching(int fd, bool enable) {
log_debugf("Setting process switching to %d", enable);
struct vt_mode mode = {
.mode = enable ? VT_PROCESS : VT_AUTO,
.waitv = 0,
.relsig = enable ? SIGUSR1 : 0,
.acqsig = enable ? SIGUSR2 : 0,
.frsig = FRSIG,
};
if (ioctl(fd, VT_SETMODE, &mode) == -1) {
log_errorf("Could not set VT mode to %s process switching: %s",
enable ? "enable" : "disable", strerror(errno));
return -1;
}
return 0;
}
int terminal_switch_vt(int fd, int vt) {
log_debugf("Switching to VT %d", vt);
if (ioctl(fd, VT_ACTIVATE, vt) == -1) {
log_errorf("Could not activate VT %d: %s", vt, strerror(errno));
return -1;
}
return 0;
}
int terminal_ack_release(int fd) {
log_debug("Acking VT release");
if (ioctl(fd, VT_RELDISP, 1) == -1) {
log_errorf("Could not ack VT release: %s", strerror(errno));
return -1;
}
return 0;
}
int terminal_ack_acquire(int fd) {
log_debug("Acking VT acquire");
if (ioctl(fd, VT_RELDISP, VT_ACKACQ) == -1) {
log_errorf("Could not ack VT acquire: %s", strerror(errno));
return -1;
}
return 0;
}
int terminal_set_keyboard(int fd, bool enable) {
log_debugf("Setting KD keyboard state to %d", enable);
if (ioctl(fd, KDSKBMODE, enable ? K_ENABLE : K_DISABLE) == -1) {
log_errorf("Could not set KD keyboard mode to %s: %s",
enable ? "enabled" : "disabled", strerror(errno));
return -1;
}
#if defined(__FreeBSD__)
struct termios tios;
if (tcgetattr(fd, &tios) == -1) {
log_errorf("Could not set get terminal mode: %s", strerror(errno));
return -1;
}
if (enable) {
cfmakesane(&tios);
} else {
cfmakeraw(&tios);
}
if (tcsetattr(fd, TCSAFLUSH, &tios) == -1) {
log_errorf("Could not set terminal mode to %s: %s", enable ? "sane" : "raw",
strerror(errno));
return -1;
}
#endif
return 0;
}
int terminal_set_graphics(int fd, bool enable) {
log_debugf("Setting KD graphics state to %d", enable);
if (ioctl(fd, KDSETMODE, enable ? KD_GRAPHICS : KD_TEXT) == -1) {
log_errorf("Could not set KD graphics mode to %s: %s", enable ? "graphics" : "text",
strerror(errno));
return -1;
}
return 0;
}
#include <stdlib.h>
#include <string.h>
#if defined(__NetBSD__)
#include <stdlib.h>
#include <sys/stat.h>
#endif
#include "wscons.h"
#define STRLEN(s) ((sizeof(s) / sizeof(s[0])) - 1)
#if defined(__NetBSD__)
int path_is_wscons(const char *path) {
static const char wskbd[] = "/dev/wskbd";
static const char wsmouse[] = "/dev/wsmouse";
static const char wsmux[] = "/dev/wsmux";
return strncmp(path, wskbd, STRLEN(wskbd)) == 0 ||
strncmp(path, wsmouse, STRLEN(wsmouse)) == 0 ||
strncmp(path, wsmux, STRLEN(wsmouse)) == 0;
}
#else
int path_is_wscons(const char *path) {
(void)path;
return 0;
}
#endif
[Unit]
Description=Seat management daemon
Documentation=man:seatd(1)
[Service]
Type=simple
# Specify the group you'd like to grant access to seatd
ExecStart=seatd -g seat
Restart=always
RestartSec=1
# Filesystem lockdown
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectControlGroups=true
PrivateTmp=true
ProtectProc=invisible
ProcSubset=pid
UMask=0077
# Privilege escalation
NoNewPrivileges=true
RestrictSUIDSGID=true
# Network
PrivateNetwork=true
IPAddressDeny=any
# System call interfaces
SystemCallFilter=@system-service
SystemCallFilter=~@resources
SystemCallErrorNumber=EPERM
SystemCallArchitectures=native
# Kernel
ProtectKernelLogs=true
ProtectKernelModules=true
LockPersonality=true
# Namespaces
RestrictNamespaces=true
# Service capabilities
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_CHOWN CAP_SYS_TTY_CONFIG CAP_DAC_OVERRIDE
RestrictAddressFamilies=AF_UNIX
RestrictRealtime=true
MemoryDenyWriteExecute=true
ProtectClock=true
ProtectHostname=true
# Devices
DevicePolicy=strict
DeviceAllow=char-/dev/console rw
DeviceAllow=char-drm rw
DeviceAllow=char-input rw
DeviceAllow=char-tty rw
DeviceAllow=/dev/null rw
[Install]
WantedBy=multi-user.target
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "libseat.h"
static void handle_enable(struct libseat *backend, void *data) {
(void)backend;
int *active = (int *)data;
(*active)++;
}
static void handle_disable(struct libseat *backend, void *data) {
(void)backend;
int *active = (int *)data;
(*active)--;
libseat_disable_seat(backend);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Specify name of file to open as argument\n");
return -1;
}
char *file = argv[1];
int active = 0;
struct libseat_seat_listener listener = {
.enable_seat = handle_enable,
.disable_seat = handle_disable,
};
libseat_set_log_level(LIBSEAT_LOG_LEVEL_DEBUG);
struct libseat *backend = libseat_open_seat(&listener, &active);
fprintf(stderr, "libseat_open_seat(listener: %p, userdata: %p) = %p\n", (void *)&listener,
(void *)&active, (void *)backend);
if (backend == NULL) {
fprintf(stderr, "libseat_open_seat() failed: %s\n", strerror(errno));
return -1;
}
while (active == 0) {
fprintf(stderr, "waiting for activation...\n");
if (libseat_dispatch(backend, -1) == -1) {
libseat_close_seat(backend);
fprintf(stderr, "libseat_dispatch() failed: %s\n", strerror(errno));
return -1;
}
}
fprintf(stderr, "active!\n");
int fd, device;
device = libseat_open_device(backend, file, &fd);
fprintf(stderr, "libseat_open_device(backend: %p, path: %s, fd: %p) = %d\n",
(void *)backend, file, (void *)&fd, device);
if (device == -1) {
fprintf(stderr, "libseat_open_device() failed: %s\n", strerror(errno));
libseat_close_seat(backend);
return 1;
}
libseat_close_device(backend, device);
close(fd);
libseat_close_seat(backend);
return 0;
}
#ifndef _SEATD_BACKEND_H
#define _SEATD_BACKEND_H
#include "libseat.h"
struct seat_impl;
struct libseat_seat_listener;
struct libseat {
const struct seat_impl *impl;
};
struct named_backend {
const char *name;
const struct seat_impl *backend;
};
struct seat_impl {
struct libseat *(*open_seat)(const struct libseat_seat_listener *listener, void *data);
int (*disable_seat)(struct libseat *seat);
int (*close_seat)(struct libseat *seat);
const char *(*seat_name)(struct libseat *seat);
int (*open_device)(struct libseat *seat, const char *path, int *fd);
int (*close_device)(struct libseat *seat, int device_id);
int (*switch_session)(struct libseat *seat, int session);
int (*get_fd)(struct libseat *seat);
int (*dispatch)(struct libseat *seat, int timeout);
};
#endif
#ifndef _SEATD_CLIENT_H
#define _SEATD_CLIENT_H
#include <stdint.h>
#include <sys/types.h>
#include <unistd.h>
#include "connection.h"
#include "linked_list.h"
struct server;
enum client_state {
CLIENT_NEW,
CLIENT_ACTIVE,
CLIENT_PENDING_DISABLE,
CLIENT_DISABLED,
CLIENT_CLOSED
};
struct client {
struct linked_list link; // seat::clients
struct server *server;
struct event_source_fd *event_source;
struct connection connection;
pid_t pid;
uid_t uid;
gid_t gid;
struct seat *seat;
int session;
enum client_state state;
struct linked_list devices;
};
struct client *client_create(struct server *server, int client_fd);
void client_destroy(struct client *client);
int client_handle_connection(int fd, uint32_t mask, void *data);
int client_send_enable_seat(struct client *client);
int client_send_disable_seat(struct client *client);
#endif
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment