git_sprout/interrupt.rs
1// ABOUTME: Defers an interrupt until the worktree is complete, so Ctrl-C never leaves a
2// ABOUTME: destination that git registered but nobody filled.
3
4use std::sync::atomic::{AtomicI32, Ordering};
5
6/// The signal that arrived, or zero. Written only from a signal handler, which may do
7/// nothing but store to an atomic.
8static ARRIVED: AtomicI32 = AtomicI32::new(0);
9
10#[cfg(unix)]
11extern "C" fn record(signal: libc::c_int) {
12 ARRIVED.store(signal, Ordering::Relaxed);
13}
14
15/// Asks the process to note interrupts rather than die on them.
16///
17/// Between the moment git creates the worktree and the moment git finishes checking it out
18/// there is a window where the destination exists, is registered in `git worktree list`,
19/// and holds nothing. Dying inside it leaves the user a worktree that looks real and
20/// reports its whole tree as deleted. Instead the request is recorded, the clone phase
21/// stops at its next path, git still finishes the checkout, and the process then dies of
22/// the original signal.
23#[cfg(unix)]
24pub fn defer() {
25 let handler = record as *const () as libc::sighandler_t;
26 for signal in [libc::SIGINT, libc::SIGTERM, libc::SIGHUP] {
27 // A signal the caller already asked to be ignored stays ignored: a shell that
28 // starts a background job hands it SIGINT set to ignore, and taking it back would
29 // make the tool die where git would not.
30 // SAFETY: `record` only stores to an atomic, which is async-signal-safe.
31 unsafe {
32 if libc::signal(signal, handler) == libc::SIG_IGN {
33 libc::signal(signal, libc::SIG_IGN);
34 }
35 }
36 }
37}
38
39#[cfg(not(unix))]
40pub fn defer() {}
41
42/// Whether an interrupt is waiting to be honoured.
43pub fn requested() -> bool {
44 ARRIVED.load(Ordering::Relaxed) != 0
45}
46
47/// Dies of the deferred signal, if one arrived. Returns if none did.
48#[cfg(unix)]
49pub fn honour() {
50 let signal = ARRIVED.load(Ordering::Relaxed);
51 if signal == 0 {
52 return;
53 }
54 // SAFETY: restoring the default disposition and re-raising is the documented way to
55 // exit with the status the signal would have produced.
56 unsafe {
57 libc::signal(signal, libc::SIG_DFL);
58 libc::raise(signal);
59 }
60}
61
62#[cfg(not(unix))]
63pub fn honour() {}