Skip to main content

simplelock/impls/
semaphore.rs

1/// Library for interacting with the following syscalls in a safe way:
2///  https://linux.die.net/man/7/sem_overview
3///  https://linux.die.net/man/3/sem_close
4///  https://linux.die.net/man/3/sem_open
5///  https://linux.die.net/man/3/sem_push
6///  https://linux.die.net/man/3/sem_trywait
7///  https://linux.die.net/man/3/sem_wait
8///
9use crate::*;
10
11// TODO: Fix deps.
12//  - Either get nix updated with libc calls below.
13//  - Or find a way to nix nix.
14use nix::{fcntl::OFlag, sys::stat::Mode};
15
16/// A safe publicly usable object surrounding the
17/// wrapper we built since 'nix' doesn't have it yet.
18pub struct SemaphoreLock {
19    /// The POSIX Named Semaphore.
20    sem: safe_wrapper::NamedSem,
21
22    /// Local consistency check, will be true if we have successfully
23    /// completed the lock operation, will be false otherwise or on
24    /// successful completion of the unlock operation.
25    have_lock: bool,
26}
27
28const INITIAL_VALUE: i32 = 1;
29const FLAGS: OFlag = OFlag::O_CREAT;
30
31/// The naming scheme for semaphore names, per POSIX sem_overview:
32///
33///   A named semaphore is identified by a name of the form /somename;
34///   that is, a null-terminated string of up to NAME_MAX-4 (i.e., 251)
35///   characters consisting of an initial slash, followed by one or more
36///   characters, none of which are slashes.
37///
38/// We expect either 'somename' or '/somename' and we'll append the slash.
39fn verify_name(name: impl Into<String>) -> SimpleLockResult<String> {
40    let mut test = name.into();
41    if !test.starts_with("/") {
42        test.insert_str(0, "/");
43    }
44
45    if test.len() < 2 || test.len() > 250 || test.split('/').count() > 2 {
46        Err(SimpleLockError::InvalidName)
47    } else {
48        Ok(test)
49    }
50}
51
52impl SemaphoreLock {
53    /// Create a new lock with the given semaphore name. Must meet POSIX requirements.
54    pub fn new(name: impl Into<String>) -> SimpleLockResult<SemaphoreLock> {
55        let name = verify_name(name)?;
56        let mode = Mode::S_IRWXU | Mode::S_IRWXO;
57        let sem = safe_wrapper::sem_open(&name, &FLAGS, mode, INITIAL_VALUE)?;
58        Ok(SemaphoreLock {
59            sem,
60            have_lock: false,
61        })
62    }
63
64    /// Get a copy of the name used in creating this Semaphore.
65    pub fn name(&self) -> String {
66        self.sem.name()
67    }
68}
69
70impl ConcreteLock for SemaphoreLock {
71    fn status(&self) -> SimpleLockResult<LockStatus> {
72        if self.have_lock {
73            Ok(LockStatus::Mine)
74        } else if safe_wrapper::sem_getvalue(&self.sem)? != INITIAL_VALUE {
75            Ok(LockStatus::Taken)
76        } else {
77            Ok(LockStatus::Open)
78        }
79    }
80
81    fn try_lock(&mut self) -> SimpleLockResult<()> {
82        if self.have_lock {
83            return Ok(());
84        }
85        safe_wrapper::sem_trywait(&self.sem)
86            .map_err(|e| e.into())
87            .and_then(|_| {
88                self.have_lock = true;
89                Ok(())
90            })
91    }
92
93    fn hang_lock(&mut self) -> SimpleLockResult<()> {
94        if self.have_lock {
95            return Ok(());
96        }
97        safe_wrapper::sem_wait(&self.sem)
98            .map_err(|e| e.into())
99            .and_then(|_| {
100                self.have_lock = true;
101                Ok(())
102            })
103    }
104
105    fn try_unlock(&mut self) -> SimpleLockResult<()> {
106        if !self.have_lock {
107            return Ok(());
108        }
109        safe_wrapper::sem_post(&self.sem)
110            .map_err(|e| e.into())
111            .and_then(|_| {
112                self.have_lock = false;
113                Ok(())
114            })
115    }
116}
117
118// FIXME: Once all the semaphore stuff is in `nix' then this can be removed.
119mod safe_wrapper {
120    use crate::SimpleLockError;
121    use nix::{errno::Errno, fcntl::OFlag, sys::stat::Mode, Error, NixPath};
122
123    impl From<Error> for SimpleLockError {
124        fn from(e: Error) -> Self {
125            match e {
126                // We can make the assumption that its an invalid name issue since the
127                // only time we use strings/paths/utf8 here is with the name creation.
128                Error::InvalidPath => SimpleLockError::InvalidName,
129                Error::InvalidUtf8 => SimpleLockError::InvalidName,
130
131                // This is brute-force, and drops a lot of potentially helpful context.
132                Error::Sys(errno) => errno.into(),
133
134                // This should really not ever happen since we implement everything here
135                // and we don't do the proper error handling for "other Operating Systems".
136                Error::UnsupportedOperation => {
137                    SimpleLockError::UnknownError("nix::UnsupportedOperation".into())
138                }
139            }
140        }
141    }
142
143    impl From<Errno> for SimpleLockError {
144        fn from(e: Errno) -> Self {
145            match e {
146                // sem_open - The semaphore exists, but the caller does not have permission to open it.
147                Errno::EACCES => SimpleLockError::PermissionDenied,
148
149                // sem_open - The per-process limit on the number of open file descriptors has been reached.
150                Errno::EMFILE => SimpleLockError::PermissionDenied,
151                // sem_open - The system-wide limit on the total number of open files has been reached.
152                Errno::ENFILE => SimpleLockError::PermissionDenied,
153                // sem_open - Insufficient memory.
154                Errno::ENOMEM => SimpleLockError::PermissionDenied,
155
156                // sem_open - name consists of just "/", followed by no other characters.
157                // sem_post - sem is not a valid semaphore.
158                // sem_getvalue - sem is not a valid semaphore.
159                Errno::EINVAL => SimpleLockError::InvalidName,
160
161                // sem_open - name was too long.
162                Errno::ENAMETOOLONG => SimpleLockError::InvalidName,
163
164                // sem_wait - The call was interrupted by a signal handler; see signal(7).
165                Errno::EINTR => SimpleLockError::UnknownError("Signal interrupt.".into()),
166
167                // Bad fail-through...
168                _ => SimpleLockError::UnknownError(format!("{:?}: {}", e, e.desc())),
169            }
170        }
171    }
172
173    /// Fake object to wrap what libc has. Would hope that in the future nix has this.
174    pub struct NamedSem {
175        name: String,
176        sem: *mut libc::sem_t,
177    }
178    impl Drop for NamedSem {
179        fn drop(&mut self) {
180            // Close this process's use of the semaphore.
181            let _ = sem_post(self);
182            let _ = sem_close(self);
183        }
184    }
185    impl NamedSem {
186        pub fn name(&self) -> String {
187            self.name.clone()
188        }
189    }
190
191    pub fn sem_getvalue(sem: &NamedSem) -> nix::Result<i32> {
192        let mut res: i32 = 0;
193        if unsafe { libc::sem_getvalue(sem.sem, &mut res) == libc::EXIT_SUCCESS } {
194            Ok(res)
195        } else {
196            Err(nix::Error::Sys(Errno::last()).into())
197        }
198    }
199
200    pub fn sem_open(name: &String, oflag: &OFlag, mode: Mode, value: i32) -> nix::Result<NamedSem> {
201        name.as_bytes().with_nix_path(|cstr| unsafe {
202            let sem: *mut libc::sem_t = libc::sem_open(
203                cstr.as_ptr(),
204                oflag.bits(),
205                mode.bits() as libc::mode_t,
206                value,
207            );
208            if sem == libc::SEM_FAILED {
209                Err(nix::Error::Sys(Errno::last()))
210            } else {
211                Ok(NamedSem {
212                    name: name.clone(),
213                    sem,
214                })
215            }
216        })?
217    }
218
219    pub fn sem_wait(sem: &NamedSem) -> nix::Result<()> {
220        if unsafe { libc::sem_wait(sem.sem) == libc::EXIT_SUCCESS } {
221            Ok(())
222        } else {
223            Err(nix::Error::Sys(Errno::last()).into())
224        }
225    }
226
227    pub fn sem_trywait(sem: &NamedSem) -> nix::Result<()> {
228        if unsafe { libc::sem_trywait(sem.sem) == libc::EXIT_SUCCESS } {
229            Ok(())
230        } else {
231            Err(nix::Error::Sys(Errno::last()).into())
232        }
233    }
234
235    pub fn sem_post(sem: &NamedSem) -> nix::Result<()> {
236        if unsafe { libc::sem_post(sem.sem) == libc::EXIT_SUCCESS } {
237            Ok(())
238        } else {
239            Err(nix::Error::Sys(Errno::last()).into())
240        }
241    }
242
243    pub fn sem_close(sem: &mut NamedSem) -> nix::Result<()> {
244        if unsafe { libc::sem_close(sem.sem) == libc::EXIT_SUCCESS } {
245            Ok(())
246        } else {
247            Err(nix::Error::Sys(Errno::last()).into())
248        }
249    }
250}
251
252#[cfg(test)]
253mod _test {
254    use super::*;
255
256    #[test]
257    fn good_names() {
258        // Pretty much any character except slashes:
259        assert_eq!(true, verify_name("abc").is_ok(), "lowercase");
260        assert_eq!(true, verify_name("123").is_ok(), "numbers");
261        assert_eq!(true, verify_name("ABC").is_ok(), "capitals");
262        assert_eq!(true, verify_name("a-c").is_ok(), "dash");
263        assert_eq!(true, verify_name("a_c").is_ok(), "underscore");
264
265        // Can begin with a slash though.
266        assert_eq!(true, verify_name("/ab").is_ok(), "prefix-slash");
267    }
268
269    #[test]
270    fn bad_names() {
271        assert_eq!(false, verify_name("").is_ok(), "too short");
272        assert_eq!(false, verify_name("a".repeat(251)).is_ok(), "too long");
273        assert_eq!(false, verify_name("a/").is_ok(), "slash within name");
274    }
275}