diff --git a/meson.build b/meson.build index 308dabd7..d0d60301 100644 --- a/meson.build +++ b/meson.build @@ -182,6 +182,11 @@ executable('swaylock', install: true ) +executable('swaylockd', + 'swaylockd.c', + install: true +) + install_data( 'pam/swaylock', install_dir: sysconfdir + '/pam.d/' @@ -192,6 +197,7 @@ if scdoc.found() mandir = get_option('mandir') man_files = [ 'swaylock.1.scd', + 'swaylockd.1.scd', ] foreach filename : man_files topic = filename.split('.')[-3].split('/')[-1] diff --git a/swaylockd.1.scd b/swaylockd.1.scd new file mode 100644 index 00000000..6b537e10 --- /dev/null +++ b/swaylockd.1.scd @@ -0,0 +1,60 @@ +swaylockd(1) + +# NAME + +swaylockd - launch swaylock(1) and re-launch if it dies + + +# SYNOPSIS + +*swaylockd* [options...] + +All options are directly passed to *swaylock(1)*. + + +# DESCRIPTION + +*swaylockd* is a dumb launcher to spawn *swaylock(1)* and ensure it's running no matter what - it immediately restarts *swaylock(1)* if terminated by a signal (e.g. the process crashed) and also blocks all signals (except `SIGKILL` and `SIGSTOP`). +It also ensures that only one instance per user is running (using *flock(2)*). + +*IMPORTANT*: *swaylockd* is not compatible with *swaylock(1)* option *--daemonize*! + + +# ENVIRONMENT + +*XDG_RUNTIME_DIR*: + This variable must be set and contain the path to the existing directory where the lock file will be created. + Refer to "XDG Base Directory Specification" for more information. + + +# FILES + +*${XDG_RUNTIME_DIR}/swaylockd.lock* + A lock file created when *swaylockd* is executed and removed right before it quits. + + +# LOGGING + +Error messages generated by *swaylockd* are printed to STDERR and logged to syslog with ident string "swaylockd" and facility code 1 (user). + + +# HISTORY + +*swaylockd* has been developed mainly as a workaround for the *swaylock(1)* stability issues (see e.g. https://github.com/swaywm/swaylock/issues/181). + +*swaylock* is a critical piece of security software - as a screen locker, any bug in the program that causes it to crash will cause the screen to unlock. +As soon as *swaylock* is no longer running, the screen is no longer locked. +Therefore, great care must be taken to ensure that it never crash. + +*swaylockd* started (and is still available) as a standalone project at https://github.com/jirutka/swaylockd. +The code has been merged into sway-effects. + + +# AUTHORS + +Jakub Jirutka + + +# SEE ALSO + +*swaylock(1)* diff --git a/swaylockd.c b/swaylockd.c new file mode 100644 index 00000000..f2cdefc8 --- /dev/null +++ b/swaylockd.c @@ -0,0 +1,101 @@ +// vim: set ts=4: +// SPDX-FileCopyrightText: 2021 Jakub Jirutka +// SPDX-License-Identifier: MIT + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PROGNAME "swaylockd" + +#ifndef SWAYLOCK_PATH + #define SWAYLOCK_PATH "/usr/bin/swaylock" +#endif + +#define logerr(format, ...) \ + do { \ + fprintf(stderr, PROGNAME ": " format "\n", __VA_ARGS__); \ + if (syslog_enabled) syslog(LOG_ERR, format, __VA_ARGS__); \ + } while (0) + + +extern char **environ; + +static bool syslog_enabled = true; + +int main (int argc, char **argv) { + char lock_path[PATH_MAX] = "\0"; + int lock_fd = -1; + int rc = 1; + + // Open connection to syslog. + openlog(PROGNAME, LOG_PID, LOG_USER); + + { + const char *xdg_rd = getenv("XDG_RUNTIME_DIR"); + if (xdg_rd == NULL) { + logerr("%s", "XDG_RUNTIME_DIR not set in the environment"); + return 100; + } + (void) snprintf(lock_path, sizeof(lock_path), "%s/%s.lock", xdg_rd, PROGNAME); + } + + // Obtain exclusive lock. + if ((lock_fd = open(lock_path, O_CREAT | O_RDWR | O_CLOEXEC, 0600)) < 0) { + logerr("failed to write %s: %s", lock_path, strerror(errno)); + return 101; + } + if (flock(lock_fd, LOCK_EX | LOCK_NB) < 0) { + logerr("another instance of %s is running", PROGNAME); + return 102; + } + + // Block (ignore) all signals we can (i.e. except SIGKILL and SIGSTOP). + { + sigset_t mask; + sigfillset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + } + + argv[0] = SWAYLOCK_PATH; + + for (int i = 1;; i++) { + pid_t pid; + if (posix_spawn(&pid, SWAYLOCK_PATH, NULL, 0, argv, environ) != 0) { + logerr("failed to spawn %s: %s", SWAYLOCK_PATH, strerror(errno)); + rc = 101; + goto done; + } + + int status = -1; + (void) waitpid(pid, &status, 0); + + // Exit only if the child terminated normally, not via signal. + if (WIFEXITED(status)) { + rc = WEXITSTATUS(status); + goto done; + } + logerr("%s terminated with signal %d, restarting (%d)", + SWAYLOCK_PATH, WIFSIGNALED(status) ? WTERMSIG(status) : -1, i); + + // Just to make sure we don't flood syslog in case of some bug... + if (i > 100) syslog_enabled = false; + } + +done: + if (unlink(lock_path) < 0) { + logerr("failed to remove lock file %s: %s", lock_path, strerror(errno)); + } + return rc; +}