1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/// Library for interacting with the following syscalls in a safe way:
///  https://linux.die.net/man/7/sem_overview
///  https://linux.die.net/man/3/sem_close
///  https://linux.die.net/man/3/sem_open
///  https://linux.die.net/man/3/sem_push
///  https://linux.die.net/man/3/sem_trywait
///  https://linux.die.net/man/3/sem_wait
///
use crate::*;

// TODO: Fix deps.
//  - Either get nix updated with libc calls below.
//  - Or find a way to nix nix.
use nix::{fcntl::OFlag, sys::stat::Mode};

/// A safe publicly usable object surrounding the
/// wrapper we built since 'nix' doesn't have it yet.
pub struct SemaphoreLock {
    /// The POSIX Named Semaphore.
    sem: safe_wrapper::NamedSem,

    /// Local consistency check, will be true if we have successfully
    /// completed the lock operation, will be false otherwise or on
    /// successful completion of the unlock operation.
    have_lock: bool,
}

const INITIAL_VALUE: i32 = 1;
const FLAGS: OFlag = OFlag::O_CREAT;

/// The naming scheme for semaphore names, per POSIX sem_overview:
///
///   A named semaphore is identified by a name of the form /somename;
///   that is, a null-terminated string of up to NAME_MAX-4 (i.e., 251)
///   characters consisting of an initial slash, followed by one or more
///   characters, none of which are slashes.
///
/// We expect either 'somename' or '/somename' and we'll append the slash.
fn verify_name(name: impl Into<String>) -> SimpleLockResult<String> {
    let mut test = name.into();
    if !test.starts_with("/") {
        test.insert_str(0, "/");
    }

    if test.len() < 2 || test.len() > 250 || test.split('/').count() > 2 {
        Err(SimpleLockError::InvalidName)
    } else {
        Ok(test)
    }
}

impl SemaphoreLock {
    /// Create a new lock with the given semaphore name. Must meet POSIX requirements.
    pub fn new(name: impl Into<String>) -> SimpleLockResult<SemaphoreLock> {
        let name = verify_name(name)?;
        let mode = Mode::S_IRWXU | Mode::S_IRWXO;
        let sem = safe_wrapper::sem_open(&name, &FLAGS, mode, INITIAL_VALUE)?;
        Ok(SemaphoreLock {
            sem,
            have_lock: false,
        })
    }

    /// Get a copy of the name used in creating this Semaphore.
    pub fn name(&self) -> String {
        self.sem.name()
    }
}

impl ConcreteLock for SemaphoreLock {
    fn status(&self) -> SimpleLockResult<LockStatus> {
        if self.have_lock {
            Ok(LockStatus::Mine)
        } else if safe_wrapper::sem_getvalue(&self.sem)? != INITIAL_VALUE {
            Ok(LockStatus::Taken)
        } else {
            Ok(LockStatus::Open)
        }
    }

    fn try_lock(&mut self) -> SimpleLockResult<()> {
        if self.have_lock {
            return Ok(());
        }
        safe_wrapper::sem_trywait(&self.sem)
            .map_err(|e| e.into())
            .and_then(|_| {
                self.have_lock = true;
                Ok(())
            })
    }

    fn hang_lock(&mut self) -> SimpleLockResult<()> {
        if self.have_lock {
            return Ok(());
        }
        safe_wrapper::sem_wait(&self.sem)
            .map_err(|e| e.into())
            .and_then(|_| {
                self.have_lock = true;
                Ok(())
            })
    }

    fn try_unlock(&mut self) -> SimpleLockResult<()> {
        if !self.have_lock {
            return Ok(());
        }
        safe_wrapper::sem_post(&self.sem)
            .map_err(|e| e.into())
            .and_then(|_| {
                self.have_lock = false;
                Ok(())
            })
    }
}

// FIXME: Once all the semaphore stuff is in `nix' then this can be removed.
mod safe_wrapper {
    use crate::SimpleLockError;
    use nix::{errno::Errno, fcntl::OFlag, sys::stat::Mode, Error, NixPath};

    impl From<Error> for SimpleLockError {
        fn from(e: Error) -> Self {
            match e {
                // We can make the assumption that its an invalid name issue since the
                // only time we use strings/paths/utf8 here is with the name creation.
                Error::InvalidPath => SimpleLockError::InvalidName,
                Error::InvalidUtf8 => SimpleLockError::InvalidName,

                // This is brute-force, and drops a lot of potentially helpful context.
                Error::Sys(errno) => errno.into(),

                // This should really not ever happen since we implement everything here
                // and we don't do the proper error handling for "other Operating Systems".
                Error::UnsupportedOperation => {
                    SimpleLockError::UnknownError("nix::UnsupportedOperation".into())
                }
            }
        }
    }

    impl From<Errno> for SimpleLockError {
        fn from(e: Errno) -> Self {
            match e {
                // sem_open - The semaphore exists, but the caller does not have permission to open it.
                Errno::EACCES => SimpleLockError::PermissionDenied,

                // sem_open - The per-process limit on the number of open file descriptors has been reached.
                Errno::EMFILE => SimpleLockError::PermissionDenied,
                // sem_open - The system-wide limit on the total number of open files has been reached.
                Errno::ENFILE => SimpleLockError::PermissionDenied,
                // sem_open - Insufficient memory.
                Errno::ENOMEM => SimpleLockError::PermissionDenied,

                // sem_open - name consists of just "/", followed by no other characters.
                // sem_post - sem is not a valid semaphore.
                // sem_getvalue - sem is not a valid semaphore.
                Errno::EINVAL => SimpleLockError::InvalidName,

                // sem_open - name was too long.
                Errno::ENAMETOOLONG => SimpleLockError::InvalidName,

                // sem_wait - The call was interrupted by a signal handler; see signal(7).
                Errno::EINTR => SimpleLockError::UnknownError("Signal interrupt.".into()),

                // Bad fail-through...
                _ => SimpleLockError::UnknownError(format!("{:?}: {}", e, e.desc())),
            }
        }
    }

    /// Fake object to wrap what libc has. Would hope that in the future nix has this.
    pub struct NamedSem {
        name: String,
        sem: *mut libc::sem_t,
    }
    impl Drop for NamedSem {
        fn drop(&mut self) {
            // Close this process's use of the semaphore.
            let _ = sem_post(self);
            let _ = sem_close(self);
        }
    }
    impl NamedSem {
        pub fn name(&self) -> String {
            self.name.clone()
        }
    }

    pub fn sem_getvalue(sem: &NamedSem) -> nix::Result<i32> {
        let mut res: i32 = 0;
        if unsafe { libc::sem_getvalue(sem.sem, &mut res) == libc::EXIT_SUCCESS } {
            Ok(res)
        } else {
            Err(nix::Error::Sys(Errno::last()).into())
        }
    }

    pub fn sem_open(name: &String, oflag: &OFlag, mode: Mode, value: i32) -> nix::Result<NamedSem> {
        name.as_bytes().with_nix_path(|cstr| unsafe {
            let sem: *mut libc::sem_t = libc::sem_open(
                cstr.as_ptr(),
                oflag.bits(),
                mode.bits() as libc::mode_t,
                value,
            );
            if sem == libc::SEM_FAILED {
                Err(nix::Error::Sys(Errno::last()))
            } else {
                Ok(NamedSem {
                    name: name.clone(),
                    sem,
                })
            }
        })?
    }

    pub fn sem_wait(sem: &NamedSem) -> nix::Result<()> {
        if unsafe { libc::sem_wait(sem.sem) == libc::EXIT_SUCCESS } {
            Ok(())
        } else {
            Err(nix::Error::Sys(Errno::last()).into())
        }
    }

    pub fn sem_trywait(sem: &NamedSem) -> nix::Result<()> {
        if unsafe { libc::sem_trywait(sem.sem) == libc::EXIT_SUCCESS } {
            Ok(())
        } else {
            Err(nix::Error::Sys(Errno::last()).into())
        }
    }

    pub fn sem_post(sem: &NamedSem) -> nix::Result<()> {
        if unsafe { libc::sem_post(sem.sem) == libc::EXIT_SUCCESS } {
            Ok(())
        } else {
            Err(nix::Error::Sys(Errno::last()).into())
        }
    }

    pub fn sem_close(sem: &mut NamedSem) -> nix::Result<()> {
        if unsafe { libc::sem_close(sem.sem) == libc::EXIT_SUCCESS } {
            Ok(())
        } else {
            Err(nix::Error::Sys(Errno::last()).into())
        }
    }
}

#[cfg(test)]
mod _test {
    use super::*;

    #[test]
    fn good_names() {
        // Pretty much any character except slashes:
        assert_eq!(true, verify_name("abc").is_ok(), "lowercase");
        assert_eq!(true, verify_name("123").is_ok(), "numbers");
        assert_eq!(true, verify_name("ABC").is_ok(), "capitals");
        assert_eq!(true, verify_name("a-c").is_ok(), "dash");
        assert_eq!(true, verify_name("a_c").is_ok(), "underscore");

        // Can begin with a slash though.
        assert_eq!(true, verify_name("/ab").is_ok(), "prefix-slash");
    }

    #[test]
    fn bad_names() {
        assert_eq!(false, verify_name("").is_ok(), "too short");
        assert_eq!(false, verify_name("a".repeat(251)).is_ok(), "too long");
        assert_eq!(false, verify_name("a/").is_ok(), "slash within name");
    }
}