Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
signals: Add support for Windows
Merged lorenz opened 2 months ago
9 files changed +81 -16 865abd35 03bbe524
modified Cargo.lock
@@ -3151,6 +3151,7 @@ dependencies = [
 "crossbeam-channel",
 "libc",
 "signals_receipts",
+
 "windows 0.62.2",
]

[[package]]
modified Cargo.toml
@@ -70,6 +70,7 @@ sqlite = "0.32.0"
tempfile = "3.3.0"
thiserror = { version = "2", default-features = false }
uds_windows = "1.1.0"
+
windows = "0.62"
winpipe = "0.1.1"
winsplit = "0.1.0"
zeroize = "1.5.7"
modified crates/radicle-node/src/main.rs
@@ -13,7 +13,6 @@ use radicle::profile;
use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner};
use radicle_node::fingerprint::{Fingerprint, FingerprintVerification};
use radicle_node::{Runtime, VERSION};
-
#[cfg(unix)]
use radicle_signals as signals;

const HELP_MSG: &str = r#"
@@ -320,20 +319,12 @@ fn execute(options: Options) -> Result<(), ExecutionError> {
        log::warn!(target: "node", "Unable to set process open file limit: {e}");
    }

-
    #[cfg(unix)]
    let signals = {
        let (notify, signals) = chan::bounded(1);
        signals::install(notify)?;
        signals
    };

-
    #[cfg(windows)]
-
    let signals = {
-
        let (_, signals) = chan::bounded(1);
-
        log::info!(target: "node", "No signal handlers were installed, because this is not available on Windows.");
-
        signals
-
    };
-

    if options.force {
        log::debug!(target: "node", "Removing existing control socket..");
        std::fs::remove_file(home.socket()).ok();
modified crates/radicle-node/src/runtime.rs
@@ -287,7 +287,6 @@ impl Runtime {
            || control::listen(listener, handle)
        });

-
        #[cfg(unix)]
        let _signals = thread::spawn(&self.id, "signals", move || loop {
            use radicle::node::Handle as _;

modified crates/radicle-signals/Cargo.toml
@@ -8,7 +8,16 @@ edition.workspace = true
version = "0.11.0"
rust-version.workspace = true

-
[target.'cfg(unix)'.dependencies]
+
[dependencies]
crossbeam-channel = { workspace = true }
+

+
[target.'cfg(unix)'.dependencies]
libc = { workspace = true }
signals_receipts = { version = "0.2.0", features = ["channel_notify_facility"] }
+

+
[target.'cfg(windows)'.dependencies.windows]
+
workspace = true
+
features = [
+
    "Win32_Foundation",
+
    "Win32_System_Console",
+
]

\ No newline at end of file
modified crates/radicle-signals/src/lib.rs
@@ -1,9 +1,17 @@
+
use std::io;
+

#[cfg(unix)]
mod unix;

#[cfg(unix)]
pub use unix::*;

+
#[cfg(windows)]
+
mod windows;
+

+
#[cfg(windows)]
+
pub use windows::*;
+

/// Operating system signal.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Signal {
@@ -16,3 +24,12 @@ pub enum Signal {
    /// `SIGWINCH`.
    WindowChanged,
}
+

+
/// Return an error indicating that signal handling is already installed.
+
#[inline(always)]
+
fn already_installed() -> io::Error {
+
    io::Error::new(
+
        io::ErrorKind::AlreadyExists,
+
        "signal handling is already installed",
+
    )
+
}
modified crates/radicle-signals/src/unix.rs
@@ -57,10 +57,7 @@ pub fn install(notify: chan::Sender<Signal>) -> io::Result<()> {
    }

    SignalsChannel::install_with_outside_channel(ChanSender(notify)).map_err(|e| match e {
-
        InstallError::AlreadyInstalled { unused_notify: _ } => io::Error::new(
-
            io::ErrorKind::AlreadyExists,
-
            "signal handling is already installed",
-
        ),
+
        InstallError::AlreadyInstalled { unused_notify: _ } => already_installed(),
        _ => io::Error::other(e), // The error type is non-exhaustive.
    })
}
added crates/radicle-signals/src/windows.rs
@@ -0,0 +1,50 @@
+
use std::io;
+
use std::sync::OnceLock;
+

+
use crossbeam_channel as chan;
+

+
use ::windows::core::BOOL;
+
use ::windows::Win32::System::Console::{
+
    SetConsoleCtrlHandler, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_C_EVENT, CTRL_LOGOFF_EVENT,
+
    CTRL_SHUTDOWN_EVENT,
+
};
+

+
use crate::{already_installed, Signal};
+

+
static NOTIFY: OnceLock<chan::Sender<Signal>> = OnceLock::new();
+

+
/// Callback function, called by the system when a control signal is to be received.
+
/// See <https://learn.microsoft.com/en-us/windows/console/handlerroutine>.
+
unsafe extern "system" fn handler(ctrltype: u32) -> BOOL {
+
    match ctrltype {
+
        CTRL_C_EVENT | CTRL_BREAK_EVENT | CTRL_CLOSE_EVENT | CTRL_SHUTDOWN_EVENT => {
+
            if let Some(notify) = NOTIFY.get() {
+
                if notify.send(Signal::Terminate).is_ok() {
+
                    return true.into();
+
                }
+
            } else {
+
                // Do nothing, since we do not have a channel to send notifications to.
+
            }
+
        }
+
        CTRL_LOGOFF_EVENT => {
+
            // Do nothing, since we do not know which user is logging off.
+
        }
+
        _ => {
+
            // Do nothing, since we received an unknown control signal.
+
        }
+
    }
+

+
    false.into()
+
}
+

+
/// Install global signal handlers, with notifications sent to the given
+
/// `notify` channel.
+
pub fn install(notify: chan::Sender<Signal>) -> io::Result<()> {
+
    if let Err(_) = NOTIFY.set(notify) {
+
        return Err(already_installed());
+
    }
+

+
    // SAFETY: Our handler function is sane.
+
    let result = unsafe { SetConsoleCtrlHandler(Some(handler), true) };
+
    result.map_err(|_| io::Error::last_os_error())
+
}
modified crates/radicle-windows/Cargo.toml
@@ -12,7 +12,7 @@ rust-version.workspace = true
thiserror = { workspace = true }

[target.'cfg(windows)'.dependencies.windows]
-
version = "0.62"
+
workspace = true
features = [
    "Win32_Foundation",
    "Win32_Security",