Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add swaylockd – a dumb launcher to spawn swaylock and ensure it’s running no matter what #71

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ executable('swaylock',
install: true
)

executable('swaylockd',
'swaylockd.c',
install: true
)

install_data(
'pam/swaylock',
install_dir: sysconfdir + '/pam.d/'
Expand All @@ -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]
Expand Down
60 changes: 60 additions & 0 deletions swaylockd.1.scd
Original file line number Diff line number Diff line change
@@ -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)*
101 changes: 101 additions & 0 deletions swaylockd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// vim: set ts=4:
// SPDX-FileCopyrightText: 2021 Jakub Jirutka <[email protected]>
// SPDX-License-Identifier: MIT

#define _POSIX_C_SOURCE 200809L

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <spawn.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/wait.h>

#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;
}