aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsodiboo <git@sodi.boo>2025-07-16 02:43:17 +0200
committerIvan Molodetskikh <yalterz@gmail.com>2025-07-18 11:41:17 -0700
commit8f442dee060db7899abbdeb94d9d699920e3a6d5 (patch)
tree5cf8e692b2607343cc0916b07bc5c68765bb97ad /src
parent9c09bc730f37d1d449cc756044ad275010907e4c (diff)
downloadniri-8f442dee060db7899abbdeb94d9d699920e3a6d5.tar.gz
niri-8f442dee060db7899abbdeb94d9d699920e3a6d5.tar.bz2
niri-8f442dee060db7899abbdeb94d9d699920e3a6d5.zip
refactor signal handling, and clear sigmask before spawning
Diffstat (limited to 'src')
-rw-r--r--src/main.rs12
-rw-r--r--src/utils/mod.rs1
-rw-r--r--src/utils/signals.rs57
-rw-r--r--src/utils/spawning.rs2
-rw-r--r--src/utils/xwayland/satellite.rs2
5 files changed, 63 insertions, 11 deletions
diff --git a/src/main.rs b/src/main.rs
index cd750ea2..f73f25fa 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,7 +9,6 @@ use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, mem};
-use calloop::signals::{Signal, Signals};
use calloop::EventLoop;
use clap::{CommandFactory, Parser};
use clap_complete::Shell;
@@ -197,16 +196,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut event_loop = EventLoop::<State>::try_new().unwrap();
// Handle Ctrl+C and other signals.
- event_loop
- .handle()
- .insert_source(
- Signals::new(&[Signal::SIGINT, Signal::SIGTERM, Signal::SIGHUP]).unwrap(),
- |event, _, state| {
- info!("quitting due to receiving signal {:?}", event.signal());
- state.niri.stop_signal.stop();
- },
- )
- .unwrap();
+ niri::utils::signals::listen(&event_loop.handle());
// Create the compositor.
let display = Display::new().unwrap();
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 6aab3b86..26bfe582 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -34,6 +34,7 @@ use crate::niri::ClientState;
pub mod id;
pub mod scale;
+pub mod signals;
pub mod spawning;
pub mod transaction;
pub mod watcher;
diff --git a/src/utils/signals.rs b/src/utils/signals.rs
new file mode 100644
index 00000000..9aae44b1
--- /dev/null
+++ b/src/utils/signals.rs
@@ -0,0 +1,57 @@
+//! We set a signal handler with `calloop::signals::Signals::new`.
+//! This does two things:
+//! 1. It blocks the thread from receiving these signals normally (pthread_sigmask)
+//! 2. It creates a signalfd to read them in the event loop.
+//!
+//! When spawning children, calloop already deals with the signalfd.
+//! `Signals::new` creates it with CLOEXEC, so it will not be inherited by children.
+//!
+//! But, the sigmask is always inherited, so we want to clear it before spawning children.
+//! That way, we don't affect their normal signal handling.
+//!
+//! In particular, if a child doesn't care about signals, we must not block it from receiving them.
+//!
+//! This module provides functions to clear the sigmask. Call them before spawning children.
+//!
+//! Technically, a "more correct" solution would be to remember the original sigmask and restore it
+//! after the child exits, but that's painful *and* likely to cause issues, because the user almost
+//! never intended to spawn niri with a nonempty sigmask. It indicates a bug in whoever spawned us,
+//! so we may as well clean up after them (which is easier than not doing so).
+
+use std::{io, mem};
+
+use calloop::signals::{Signal, Signals};
+
+pub fn listen(handle: &calloop::LoopHandle<crate::niri::State>) {
+ handle
+ .insert_source(
+ Signals::new(&[Signal::SIGINT, Signal::SIGTERM, Signal::SIGHUP]).unwrap(),
+ |event, _, state| {
+ info!("quitting due to receiving signal {:?}", event.signal());
+ state.niri.stop_signal.stop();
+ },
+ )
+ .unwrap();
+}
+
+pub fn unblock_all() -> io::Result<()> {
+ set_sigmask(&empty_sigset()?)
+}
+
+pub fn empty_sigset() -> io::Result<libc::sigset_t> {
+ let mut sigset = mem::MaybeUninit::uninit();
+ if unsafe { libc::sigemptyset(sigset.as_mut_ptr()) } == 0 {
+ Ok(unsafe { sigset.assume_init() })
+ } else {
+ Err(io::Error::last_os_error())
+ }
+}
+
+pub fn set_sigmask(set: &libc::sigset_t) -> io::Result<()> {
+ let oldset = std::ptr::null_mut(); // ignore old mask
+ if unsafe { libc::pthread_sigmask(libc::SIG_SETMASK, set, oldset) } == 0 {
+ Ok(())
+ } else {
+ Err(io::Error::last_os_error())
+ }
+}
diff --git a/src/utils/spawning.rs b/src/utils/spawning.rs
index 0e118785..a6f516db 100644
--- a/src/utils/spawning.rs
+++ b/src/utils/spawning.rs
@@ -141,6 +141,8 @@ fn spawn_sync(
process.env("DESKTOP_STARTUP_ID", token.as_str());
}
+ unsafe { process.pre_exec(crate::utils::signals::unblock_all) };
+
let Some(mut child) = do_spawn(command, process) else {
return;
};
diff --git a/src/utils/xwayland/satellite.rs b/src/utils/xwayland/satellite.rs
index 0c5a2d67..05a1b4a2 100644
--- a/src/utils/xwayland/satellite.rs
+++ b/src/utils/xwayland/satellite.rs
@@ -238,6 +238,8 @@ fn spawn(path: String, xwl: &Satellite) {
.stdout(Stdio::null())
.stderr(Stdio::null());
+ unsafe { process.pre_exec(crate::utils::signals::unblock_all) };
+
// Spawning and waiting takes some milliseconds, so do it in a thread.
let res = thread::Builder::new()
.name("Xwl-s Spawner".to_owned())