Skip to main content

mtp_mount/
shutdown.rs

1//! One-way "take this mount down" signal.
2//!
3//! A FUSE callback can't unmount itself: it holds the inode lock and still owes
4//! the kernel a reply, and `fuser` hands the unmount handle to whoever mounted
5//! the filesystem, not to the filesystem. So when the device is gone for good,
6//! the callback records why here and returns an error; the thread that owns the
7//! mount picks the reason up and unmounts.
8
9use std::sync::{Condvar, Mutex};
10use std::time::Duration;
11
12/// Shared shutdown request, one reason, first one wins.
13#[derive(Debug, Default)]
14pub struct Shutdown {
15    reason: Mutex<Option<String>>,
16    changed: Condvar,
17}
18
19impl Shutdown {
20    /// Asks for the mount to go away. Later calls are ignored: the first reason
21    /// is the one that explains what happened.
22    pub fn request(&self, reason: impl Into<String>) {
23        let mut slot = self.reason.lock().unwrap();
24        if slot.is_none() {
25            *slot = Some(reason.into());
26            self.changed.notify_all();
27        }
28    }
29
30    /// Whether a shutdown was requested.
31    #[allow(dead_code)] // used by integration tests via lib.rs, not by the bin
32    pub fn is_requested(&self) -> bool {
33        self.reason.lock().unwrap().is_some()
34    }
35
36    /// The reason, if one was recorded.
37    #[allow(dead_code)] // used by integration tests via lib.rs, not by the bin
38    pub fn reason(&self) -> Option<String> {
39        self.reason.lock().unwrap().clone()
40    }
41
42    /// Waits up to `timeout` for a shutdown request and returns its reason.
43    pub fn wait_timeout(&self, timeout: Duration) -> Option<String> {
44        let slot = self.reason.lock().unwrap();
45        let (slot, _) = self
46            .changed
47            .wait_timeout_while(slot, timeout, |reason| reason.is_none())
48            .unwrap();
49        slot.clone()
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use std::sync::Arc;
57
58    #[test]
59    fn starts_quiet() {
60        let shutdown = Shutdown::default();
61        assert!(!shutdown.is_requested());
62        assert_eq!(shutdown.reason(), None);
63        assert_eq!(shutdown.wait_timeout(Duration::from_millis(1)), None);
64    }
65
66    #[test]
67    fn keeps_the_first_reason() {
68        let shutdown = Shutdown::default();
69        shutdown.request("device gone");
70        shutdown.request("something else");
71        assert!(shutdown.is_requested());
72        assert_eq!(shutdown.reason().as_deref(), Some("device gone"));
73    }
74
75    #[test]
76    fn wakes_a_waiter() {
77        let shutdown = Arc::new(Shutdown::default());
78        let signaller = Arc::clone(&shutdown);
79        std::thread::spawn(move || {
80            std::thread::sleep(Duration::from_millis(20));
81            signaller.request("device gone");
82        });
83        assert_eq!(
84            shutdown.wait_timeout(Duration::from_secs(5)).as_deref(),
85            Some("device gone")
86        );
87    }
88}