Skip to main content

ssh_cli/
signals.rs

1//! Tratamento de sinais do sistema operacional.
2//!
3//! Registra um handler para Ctrl+C (SIGINT) que sinaliza cancelamento
4//! via um [`AtomicBool`] compartilhado. Todos os módulos que executam
5//! operações longas devem verificar [`cancelado`] periodicamente.
6
7use anyhow::Result;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::sync::{Arc, OnceLock};
10
11/// Flag global de cancelamento. Definida uma única vez na inicialização.
12static FLAG_CANCELAMENTO: OnceLock<Arc<AtomicBool>> = OnceLock::new();
13
14/// Registra o handler de Ctrl+C que marca a flag de cancelamento.
15///
16/// Deve ser chamada uma única vez, antes de qualquer operação longa.
17/// Chamadas adicionais são seguras e ignoradas silenciosamente.
18pub fn registrar_handler() -> Result<()> {
19    let flag = obter_flag();
20    let flag_clone = Arc::clone(&flag);
21
22    ctrlc::set_handler(move || {
23        flag_clone.store(true, Ordering::SeqCst);
24        tracing::debug!("sinal de cancelamento recebido via Ctrl+C");
25    })?;
26
27    tracing::debug!("handler de Ctrl+C registrado com sucesso");
28
29    #[cfg(unix)]
30    {
31        let flag_term = obter_flag_sigterm();
32        signal_hook::flag::register(signal_hook::consts::SIGTERM, flag_term)?;
33        tracing::debug!("handler SIGTERM registrado");
34    }
35
36    #[cfg(not(unix))]
37    {
38        // No Windows, SIGTERM não é suportado nativamente.
39        // ctrlc já cobre Ctrl+C (equivalente a SIGINT).
40        let _ = obter_flag_sigterm(); // Inicializa OnceLock mesmo sem handler
41    }
42
43    Ok(())
44}
45
46/// Retorna `true` se o usuário pressionou Ctrl+C.
47///
48/// Deve ser verificado em loops de operações longas para permitir
49/// encerramento gracioso.
50///
51/// # Examples
52///
53/// ```
54/// use ssh_cli::signals::cancelado;
55///
56/// // Antes de registrar handler, retorna false
57/// assert!(!cancelado());
58/// ```
59#[must_use]
60pub fn cancelado() -> bool {
61    FLAG_CANCELAMENTO
62        .get()
63        .map(|f| f.load(Ordering::SeqCst))
64        .unwrap_or(false)
65}
66
67/// Retorna o ponteiro compartilhado da flag de cancelamento.
68///
69/// Útil para passar a flag para tarefas assíncronas que precisam
70/// verificar cancelamento sem chamar [`cancelado`] diretamente.
71#[must_use]
72pub fn obter_flag() -> Arc<AtomicBool> {
73    Arc::clone(FLAG_CANCELAMENTO.get_or_init(|| Arc::new(AtomicBool::new(false))))
74}
75
76/// Flag global para SIGTERM.
77static FLAG_SIGTERM: OnceLock<Arc<AtomicBool>> = OnceLock::new();
78
79/// Retorna `true` se o processo recebeu SIGTERM.
80#[must_use]
81pub fn terminado() -> bool {
82    FLAG_SIGTERM
83        .get()
84        .map(|f| f.load(Ordering::SeqCst))
85        .unwrap_or(false)
86}
87
88/// Retorna o Arc do flag de SIGTERM para uso em tarefas async.
89#[must_use]
90pub fn obter_flag_sigterm() -> Arc<AtomicBool> {
91    Arc::clone(FLAG_SIGTERM.get_or_init(|| Arc::new(AtomicBool::new(false))))
92}
93
94#[cfg(test)]
95mod testes {
96    use super::*;
97
98    #[test]
99    fn cancelado_retorna_false_antes_de_sinal() {
100        // A flag não deve estar marcada em estado inicial
101        // (a menos que outro teste tenha ativado, mas cada test usa a mesma OnceLock)
102        // Verificamos apenas que a chamada não panics.
103        let _ = cancelado();
104    }
105
106    #[test]
107    fn obter_flag_retorna_mesmo_arc() {
108        let flag_a = obter_flag();
109        let flag_b = obter_flag();
110        // Ambos devem apontar para o mesmo AtomicBool subjacente
111        assert!(Arc::ptr_eq(&flag_a, &flag_b));
112    }
113
114    #[test]
115    fn flag_pode_ser_marcada_e_lida() {
116        let flag = obter_flag();
117        // Apenas verificamos que o AtomicBool funciona corretamente
118        let valor_anterior = flag.load(Ordering::SeqCst);
119        flag.store(valor_anterior, Ordering::SeqCst);
120        assert_eq!(flag.load(Ordering::SeqCst), valor_anterior);
121    }
122
123    #[test]
124    fn terminado_false_por_padrao() {
125        // OnceLock pode já estar setado por outros testes
126        // Se não setado, retorna false. Se setado, o valor padrão é false.
127        let flag = obter_flag_sigterm();
128        flag.store(false, Ordering::SeqCst);
129        assert!(!terminado());
130    }
131
132    #[test]
133    fn obter_flag_sigterm_retorna_mesmo_arc() {
134        let a = obter_flag_sigterm();
135        let b = obter_flag_sigterm();
136        assert!(Arc::ptr_eq(&a, &b));
137    }
138
139    #[test]
140    fn terminado_verdadeiro_apos_set() {
141        let flag = obter_flag_sigterm();
142        flag.store(true, Ordering::SeqCst);
143        assert!(terminado());
144        flag.store(false, Ordering::SeqCst); // Reset para não afetar outros testes
145    }
146
147    #[test]
148    fn cancelado_false_apos_reset() {
149        let flag = obter_flag();
150        flag.store(true, Ordering::SeqCst);
151        assert!(cancelado());
152        flag.store(false, Ordering::SeqCst);
153        assert!(!cancelado());
154    }
155}