chj_unix_util/
unix.rs

1//! Some utilities for unix specific functionality
2
3use std::fs::File;
4use std::ops::Deref;
5use std::ops::DerefMut;
6use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
7
8use nix::fcntl::{flock, FlockArg};
9use nix::sys::wait::{waitpid, WaitStatus};
10use nix::{
11    errno::Errno,
12    sys::signal::Signal,
13    unistd::{fork, ForkResult, Pid},
14};
15use num_threads::is_single_threaded;
16
17// Don't make it overly complicated, please. The original API is
18// simple enough. If a Pid is given, it's the parent.
19//
20/// This function can only be run if there are no other threads
21/// running; it checks and panics if there are!
22pub fn easy_fork() -> Result<Option<Pid>, Errno> {
23    if let Some(single) = is_single_threaded() {
24        if !single {
25            panic!("easy_fork: other threads are running, refusing to fork")
26        }
27    } else {
28        panic!("easy_fork: can't determine if other threads are running")
29    }
30    match unsafe {
31        // Safe because there are no other threads (we checked above).
32        fork()
33    }? {
34        ForkResult::Parent { child, .. } => Ok(Some(child)),
35        ForkResult::Child => Ok(None),
36    }
37}
38
39pub enum Status {
40    Normalexit(i32),
41    Signalexit(Signal),
42}
43
44// Really wait until the given process has ended,
45// and return a simpler enum.
46pub fn waitpid_until_gone(pid: Pid) -> Result<Status, Errno> {
47    loop {
48        let st = waitpid(pid, None)?;
49        match st {
50            WaitStatus::Exited(_pid, exitcode) => return Ok(Status::Normalexit(exitcode)),
51            WaitStatus::Signaled(_pid, signal, _bool) => return Ok(Status::Signalexit(signal)),
52            _ => {} // retry
53        }
54    }
55}
56
57/// Represents an active lock via `flock`. Dropping it releases the
58/// lock.
59pub struct FlockGuard<'t> {
60    file: Option<&'t mut File>,
61}
62
63impl<'t> FlockGuard<'t> {
64    /// This "leaks" the lock, i.e. there will be no unlocking done on
65    /// Drop. This is necessary if you fork and either parent and
66    /// child should not release the lock for both processes. No
67    /// leaking of memory is happening.
68    pub fn leak(&mut self) -> Option<&'t mut File> {
69        self.file.take()
70    }
71}
72
73impl<'t> Deref for FlockGuard<'t> {
74    type Target = File;
75
76    fn deref(&self) -> &Self::Target {
77        self.file
78            .as_ref()
79            .expect("do not dereference the FlockGuard after calling leak() on it")
80    }
81}
82
83impl<'t> DerefMut for FlockGuard<'t> {
84    fn deref_mut(&mut self) -> &mut Self::Target {
85        self.file
86            .as_mut()
87            .expect("do not dereference the FlockGuard after calling leak() on it")
88    }
89}
90
91impl<'t> Drop for FlockGuard<'t> {
92    fn drop(&mut self) {
93        if let Some(file) = &self.file {
94            let bfd: BorrowedFd = file.as_fd();
95            let fd: i32 = bfd.as_raw_fd();
96            match flock(fd, FlockArg::Unlock) {
97                Ok(()) => (),
98                Err(_e) => {
99                    // XX Can't do this since it could panic. Perhaps can't lock stderr either?
100                    // eprintln!(
101                    //     "warning: FlockGuard::drop: unexpected error releasing file lock: {e:#}"
102                    // )
103                }
104            }
105        }
106    }
107}
108
109pub fn easy_flock(
110    file: &mut File,
111    exclusive: bool,
112    nonblock: bool,
113) -> Result<Option<FlockGuard<'_>>, Errno> {
114    let bfd: BorrowedFd = file.as_fd();
115    let fd: i32 = bfd.as_raw_fd();
116    let mode = if exclusive {
117        if nonblock {
118            FlockArg::LockExclusiveNonblock
119        } else {
120            FlockArg::LockExclusive
121        }
122    } else {
123        if nonblock {
124            FlockArg::LockSharedNonblock
125        } else {
126            FlockArg::LockShared
127        }
128    };
129    match flock(fd, mode) {
130        Ok(()) => Ok(Some(FlockGuard { file: Some(file) })),
131        Err(e) => match e {
132            // Same as Errno::EAGAIN
133            Errno::EWOULDBLOCK => Ok(None),
134            _ => Err(e),
135        },
136    }
137}
138
139pub fn easy_flock_nonblocking(
140    file: &mut File,
141    exclusive: bool,
142) -> Result<Option<FlockGuard<'_>>, Errno> {
143    easy_flock(file, exclusive, true)
144}
145
146pub fn easy_flock_blocking(file: &mut File, exclusive: bool) -> Result<FlockGuard<'_>, Errno> {
147    easy_flock(file, exclusive, false)
148        .map(|v| v.expect("said blocking, thus always getting the lock"))
149}