kwin/helpers/wayland_wrapper/kwin_wrapper.c
David Edmundson 86c6066551 Introduce helper to restart kwin on crash exit
Right now when kwin exits, the user is taken directly back to the login
screen. The login session exits, so all processes then are killed by the
session.

This patchset introduces a mechanism to safely restart kwin. The socket
(typically wayland-0) remains alive and persistent across restarts. This
means if any process reconnects through it's own mechanism or a crash
restart handler the socket appears to work, and blocks until the new
kwin restarts.

This makes it secure and race free.

If the screen was locked at the time kwin went down, this is also
secure. Kwin now checks the status from logind at the time of launch, so
will immediately restore a locked state before any other rendering.
2020-12-15 15:43:48 +00:00

118 lines
3.5 KiB
C

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
/**
* This tiny executable creates a socket, then starts kwin passing it the FD to the wayland socket.
* The WAYLAND_DISPLAY environment variable gets set here and passed to all spawned kwin instances.
* On any non-zero kwin exit kwin gets restarted.
*
* After restart kwin is relaunched but now with the KWIN_RESTART_COUNT env set to an incrementing counter
*
* It's somewhat akin to systemd socket activation, but we also need the lock file, finding the next free socket
* and so on, hence our own binary.
*
* Usage kwin_wayland_wrapper [argForKwin] [argForKwin] ...
*/
#include "wl-socket.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
char *old_wayland_env = NULL;
#define WAYLAND_ENV_NAME "WAYLAND_DISPLAY"
pid_t launch_kwin(struct wl_socket *socket, int argc, char **argv)
{
printf("Launching kwin\n");
pid_t pid = fork();
if (pid == 0) { /* child process */
char fdString[5]; // long enough string to contain what is probably a 1 digit number.
snprintf(fdString, sizeof(fdString) - 1, "%d", wl_socket_get_fd(socket));
char **args = calloc(argc + 6, sizeof(char *));
uint pos = 0;
args[pos++] = (char *)"kwin_wayland"; //process name is the first argument by convention
args[pos++] = (char *)"--wayland_fd";
args[pos++] = fdString;
// for running in nested wayland, pass the original socket name
if (old_wayland_env) {
args[pos++] = (char *)"--wayland-display";
args[pos++] = old_wayland_env;
}
//copy passed args
for (int i = 0; i < argc; i++) {
args[pos++] = argv[i];
}
args[pos++] = NULL;
execvpe("kwin_wayland", args, environ);
free(args);
exit(127); /* if exec fails */
} else {
return pid;
}
}
int main(int argc, char **argv)
{
struct wl_socket *socket = wl_socket_create();
if (!socket) {
return -1;
}
// copy the old WAYLAND_DISPLAY as we are about to overwrite it and kwin may need it
if (getenv(WAYLAND_ENV_NAME)) {
old_wayland_env = strdup(getenv(WAYLAND_ENV_NAME));
}
setenv(WAYLAND_ENV_NAME, wl_socket_get_display_name(socket), 1);
int crashCount = 0;
while (crashCount < 10) {
int status;
if (crashCount > 0) {
char restartCountEnv[3];
snprintf(restartCountEnv, sizeof(restartCountEnv) - 1, "%d", crashCount);
setenv("KWIN_RESTART_COUNT", restartCountEnv, 1);
}
// forward our own arguments, but drop the first, as that will be our own executable name
pid_t pid = launch_kwin(socket, argc - 1, &argv[1]);
if (pid < 0) {
// failed to launch kwin, we can just quit immediately
break;
}
waitpid(pid, &status, 0); /* wait for child */
if (WIFEXITED(status)) {
int exit_status = WEXITSTATUS(status);
fprintf(stderr, "Kwin exited with code %d\n", exit_status);
break;
} else if (WIFSIGNALED(status)) {
// we crashed! Let's go again!
pid = 0;
fprintf(stderr, "Compositor crashed, respawning\n");
}
crashCount++;
}
wl_socket_destroy(socket);
free(old_wayland_env);
return 0;
}