fslock_arti_fork/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3//! **WARNING**: v0.1.x is incompatible with v0.2.x onwards.
4//!
5//! > NOTE: This is a fork of the [`fslock`](https://docs.rs/crate/fslock/latest)
6//! > crate for use by Arti.  We are forking temporarily because we need
7//! > https://github.com/brunoczim/fslock/pull/15 in order to
8//! > implement file deletion safely.
9//!
10//! API to use files as a lock. Supports non-std crates by disabling feature
11//! `std`.
12//!
13//!
14//! # Types
15//! Currently, only one type is provided: [`LockFile`]. It does not destroy the
16//! file after closed. Locks are per-handle and not by per-process in any
17//! platform. On Unix, however, under `fork` file descriptors might be
18//! duplicated sharing the same lock, but `fork` is usually `unsafe` in Rust.
19//!
20//! # Example
21//! ```
22//! # use fslock_arti_fork as fslock;
23//! use fslock::LockFile;
24//! fn main() -> Result<(), fslock::Error> {
25//!
26//!     let mut file = LockFile::open("testfiles/mylock.lock")?;
27//!     file.lock()?;
28//!     do_stuff();
29//!     file.unlock()?;
30//!
31//!     Ok(())
32//! }
33//! # fn do_stuff() {
34//! #    // doing stuff here.
35//! # }
36//! ```
37
38#[cfg(test)]
39mod test;
40
41#[cfg(unix)]
42mod unix;
43#[cfg(unix)]
44use crate::unix as sys;
45
46mod string;
47mod fmt;
48
49#[cfg(windows)]
50mod windows;
51#[cfg(windows)]
52use crate::windows as sys;
53
54pub use crate::{
55    string::{EitherOsStr, IntoOsString, ToOsStr},
56    sys::{Error, OsStr, OsString},
57};
58
59#[derive(Debug)]
60/// A handle to a file that is lockable. Does not delete the file. On both
61/// Unix and Windows, the lock is held by an individual handle, and not by the
62/// whole process. On Unix, however, under `fork` file descriptors might be
63/// duplicated sharing the same lock, but `fork` is usually `unsafe` in Rust.
64///
65/// # Example
66/// ```
67/// # use fslock_arti_fork as fslock;
68/// # fn main() -> Result<(), fslock::Error> {
69/// use fslock::LockFile;
70///
71/// let mut file = LockFile::open("testfiles/mylock.lock")?;
72/// file.lock()?;
73/// do_stuff();
74/// file.unlock()?;
75///
76/// # Ok(())
77/// # }
78/// # fn do_stuff() {
79/// #    // doing stuff here.
80/// # }
81/// ```
82pub struct LockFile {
83    locked: bool,
84    desc: sys::FileDesc,
85}
86
87impl LockFile {
88    /// Opens a file for locking, with OS-dependent locking behavior. On Unix,
89    /// if the path is nul-terminated (ends with 0), no extra allocation will be
90    /// made.
91    ///
92    /// # Compatibility
93    ///
94    /// This crate used to behave differently in regards to Unix and Windows,
95    /// when locks on Unix were per-process and not per-handle. However, the
96    /// current version locks per-handle on any platform. On Unix, however,
97    /// under `fork` file descriptors might be duplicated sharing the same lock,
98    /// but `fork` is usually `unsafe` in Rust.
99    ///
100    /// # Panics
101    /// Panics if the path contains a nul-byte in a place other than the end.
102    ///
103    /// # Example
104    ///
105    /// ```
106    /// # use fslock_arti_fork as fslock;
107    /// # fn main() -> Result<(), fslock::Error> {
108    /// use fslock::LockFile;
109    ///
110    /// let mut file = LockFile::open("testfiles/regular.lock")?;
111    ///
112    /// # Ok(())
113    /// # }
114    /// ```
115    ///
116    /// # Panicking Example
117    ///
118    /// ```should_panic
119    /// # use fslock_arti_fork as fslock;
120    /// # fn main() -> Result<(), fslock::Error> {
121    /// use fslock::LockFile;
122    ///
123    /// let mut file = LockFile::open("my\0lock")?;
124    ///
125    /// # Ok(())
126    /// # }
127    /// ```
128    pub fn open<P>(path: &P) -> Result<Self, Error>
129    where
130        P: ToOsStr + ?Sized,
131    {
132        let path = path.to_os_str()?;
133        let desc = sys::open(path.as_ref())?;
134        Ok(Self { locked: false, desc })
135    }
136
137    /// Locks this file. Blocks while it is not possible to lock (i.e. someone
138    /// else already owns a lock). After locked, if no attempt to unlock is
139    /// made, it will be automatically unlocked on the file handle drop.
140    ///
141    /// # Panics
142    /// Panics if this handle already owns the file.
143    ///
144    /// # Example
145    ///
146    /// ```
147    /// # use fslock_arti_fork as fslock;
148    /// # fn main() -> Result<(), fslock::Error> {
149    /// use fslock::LockFile;
150    ///
151    /// let mut file = LockFile::open("testfiles/target.lock")?;
152    /// file.lock()?;
153    /// do_stuff();
154    /// file.unlock()?;
155    ///
156    /// # Ok(())
157    /// # }
158    /// # fn do_stuff() {
159    /// #    // doing stuff here.
160    /// # }
161    /// ```
162    ///
163    /// # Panicking Example
164    ///
165    /// ```should_panic
166    /// # use fslock_arti_fork as fslock;
167    /// # fn main() -> Result<(), fslock::Error> {
168    /// use fslock::LockFile;
169    ///
170    /// let mut file = LockFile::open("testfiles/panicking.lock")?;
171    /// file.lock()?;
172    /// file.lock()?;
173    ///
174    /// # Ok(())
175    /// # }
176    /// ```
177    pub fn lock(&mut self) -> Result<(), Error> {
178        if self.locked {
179            panic!("Cannot lock if already owning a lock");
180        }
181        sys::lock(self.desc)?;
182        self.locked = true;
183        Ok(())
184    }
185
186    /// Locks this file and writes this process's PID into the file, which will
187    /// be erased on unlock. Like [`LockFile::lock`], blocks while it is not
188    /// possible to lock. After locked, if no attempt to unlock is made, it will
189    /// be automatically unlocked on the file handle drop.
190    ///
191    /// # Panics
192    /// Panics if this handle already owns the file.
193    ///
194    /// # Example
195    ///
196    /// ```
197    /// # use fslock_arti_fork as fslock;
198    /// # fn main() -> Result<(), fslock::Error> {
199    /// use fslock::LockFile;
200    /// # #[cfg(feature = "std")]
201    /// use std::fs::read_to_string;
202    ///
203    /// let mut file = LockFile::open("testfiles/withpid.lock")?;
204    /// file.lock_with_pid()?;
205    /// # #[cfg(feature = "std")]
206    /// # {
207    /// do_stuff()?;
208    /// # }
209    /// file.unlock()?;
210    ///
211    /// # #[cfg(feature = "std")]
212    /// fn do_stuff() -> Result<(), fslock::Error> {
213    ///     let mut content = read_to_string("testfiles/withpid.lock")?;
214    ///     assert!(content.trim().len() > 0);
215    ///     assert!(content.trim().chars().all(|ch| ch.is_ascii_digit()));
216    ///     Ok(())
217    /// }
218    ///
219    /// # Ok(())
220    /// # }
221    /// ```
222    pub fn lock_with_pid(&mut self) -> Result<(), Error> {
223        if let Err(error) = self.lock() {
224            return Err(error);
225        }
226
227        let result = writeln!(fmt::Writer(self.desc), "{}", sys::pid());
228        if result.is_err() {
229            let _ = self.unlock();
230        }
231        result
232    }
233
234    /// Locks this file. Does NOT block if it is not possible to lock (i.e.
235    /// someone else already owns a lock). After locked, if no attempt to
236    /// unlock is made, it will be automatically unlocked on the file handle
237    /// drop.
238    ///
239    /// # Panics
240    /// Panics if this handle already owns the file.
241    ///
242    /// # Example
243    ///
244    /// ```
245    /// # use fslock_arti_fork as fslock;
246    /// # fn main() -> Result<(), fslock::Error> {
247    /// use fslock::LockFile;
248    ///
249    /// let mut file = LockFile::open("testfiles/attempt.lock")?;
250    /// if file.try_lock()? {
251    ///     do_stuff();
252    ///     file.unlock()?;
253    /// }
254    ///
255    /// # Ok(())
256    /// # }
257    /// # fn do_stuff() {
258    /// #    // doing stuff here.
259    /// # }
260    /// ```
261    ///
262    /// # Panicking Example
263    ///
264    /// ```should_panic
265    /// # use fslock_arti_fork as fslock;
266    /// # fn main() -> Result<(), fslock::Error> {
267    /// use fslock::LockFile;
268    ///
269    /// let mut file = LockFile::open("testfiles/attempt_panic.lock")?;
270    /// file.lock()?;
271    /// file.try_lock()?;
272    ///
273    /// # Ok(())
274    /// # }
275    /// ```
276    pub fn try_lock(&mut self) -> Result<bool, Error> {
277        if self.locked {
278            panic!("Cannot lock if already owning a lock");
279        }
280        let lock_result = sys::try_lock(self.desc);
281        if let Ok(true) = lock_result {
282            self.locked = true;
283        }
284        lock_result
285    }
286
287    /// Locks this file and writes this process's PID into the file, which will
288    /// be erased on unlock. Does NOT block if it is not possible to lock (i.e.
289    /// someone else already owns a lock). After locked, if no attempt to
290    /// unlock is made, it will be automatically unlocked on the file handle
291    /// drop.
292    ///
293    /// # Panics
294    /// Panics if this handle already owns the file.
295    ///
296    /// # Example
297    ///
298    /// ```
299    /// # use fslock_arti_fork as fslock;
300    /// # #[cfg(feature = "std")]
301    /// # use std::fs::read_to_string;
302    /// # fn main() -> Result<(), fslock::Error> {
303    /// use fslock::LockFile;
304    ///
305    /// let mut file = LockFile::open("testfiles/pid_attempt.lock")?;
306    /// if file.try_lock_with_pid()? {
307    ///     # #[cfg(feature = "std")]
308    ///     # {
309    ///     do_stuff()?;
310    ///     # }
311    ///     file.unlock()?;
312    /// }
313    ///
314    /// # Ok(())
315    /// # }
316    /// # #[cfg(feature = "std")]
317    /// fn do_stuff() -> Result<(), fslock::Error> {
318    ///     let mut content = read_to_string("testfiles/pid_attempt.lock")?;
319    ///     assert!(content.trim().len() > 0);
320    ///     assert!(content.trim().chars().all(|ch| ch.is_ascii_digit()));
321    ///     Ok(())
322    /// }
323    /// ```
324    ///
325    /// # Panicking Example
326    ///
327    /// ```should_panic
328    /// # use fslock_arti_fork as fslock;
329    /// # fn main() -> Result<(), fslock::Error> {
330    /// use fslock::LockFile;
331    ///
332    /// let mut file = LockFile::open("testfiles/pid_attempt_panic.lock")?;
333    /// file.lock_with_pid()?;
334    /// file.try_lock_with_pid()?;
335    ///
336    /// # Ok(())
337    /// # }
338    /// ```
339    pub fn try_lock_with_pid(&mut self) -> Result<bool, Error> {
340        match self.try_lock() {
341            Ok(true) => (),
342            Ok(false) => return Ok(false),
343            Err(error) => return Err(error),
344        }
345
346        let result = sys::truncate(self.desc)
347            .and_then(|_| writeln!(fmt::Writer(self.desc), "{}", sys::pid()));
348        if result.is_err() {
349            let _ = self.unlock();
350        }
351        result.map(|_| true)
352    }
353
354    /// Returns whether this file handle owns the lock.
355    ///
356    /// # Example
357    /// ```
358    /// # use fslock_arti_fork as fslock;
359    /// use fslock::LockFile;
360    /// # fn main() -> Result<(), fslock::Error> {
361    ///
362    /// let mut file = LockFile::open("testfiles/maybeowned.lock")?;
363    /// do_stuff_with_lock(&mut file);
364    /// if !file.owns_lock() {
365    ///     file.lock()?;
366    ///     do_stuff();
367    ///     file.unlock()?;
368    /// }
369    ///
370    /// # Ok(())
371    /// # }
372    /// # fn do_stuff_with_lock(_lock: &mut LockFile) {
373    /// #    // doing stuff here.
374    /// # }
375    /// # fn do_stuff() {
376    /// #    // doing stuff here.
377    /// # }
378    /// ```
379    pub fn owns_lock(&self) -> bool {
380        self.locked
381    }
382
383    /// Unlocks this file. This file handle must own the file lock. If not
384    /// called manually, it is automatically called on `drop`.
385    ///
386    /// # Panics
387    /// Panics if this handle does not own the file.
388    ///
389    /// # Example
390    ///
391    /// ```
392    /// # use fslock_arti_fork as fslock;
393    /// # fn main() -> Result<(), fslock::Error> {
394    /// use fslock::LockFile;
395    ///
396    /// let mut file = LockFile::open("testfiles/endinglock.lock")?;
397    /// file.lock()?;
398    /// do_stuff();
399    /// file.unlock()?;
400    ///
401    /// # Ok(())
402    /// # }
403    /// # fn do_stuff() {
404    /// #    // doing stuff here.
405    /// # }
406    /// ```
407    ///
408    /// # Panicking Example
409    ///
410    /// ```should_panic
411    /// # use fslock_arti_fork as fslock;
412    /// # fn main() -> Result<(), fslock::Error> {
413    /// use fslock::LockFile;
414    ///
415    /// let mut file = LockFile::open("testfiles/endinglock.lock")?;
416    /// file.unlock()?;
417    ///
418    /// # Ok(())
419    /// # }
420    /// ```
421    pub fn unlock(&mut self) -> Result<(), Error> {
422        if !self.locked {
423            panic!("Attempted to unlock already unlocked lockfile");
424        }
425        self.locked = false;
426        sys::unlock(self.desc)?;
427        sys::truncate(self.desc)?;
428        Ok(())
429    }
430}
431
432impl Drop for LockFile {
433    fn drop(&mut self) {
434        if self.locked {
435            let _ = self.unlock();
436        }
437        sys::close(self.desc);
438    }
439}
440
441// Safe because:
442// 1. We never actually access the contents of the pointer that represents the
443// Windows Handle.
444//
445// 2. We require a mutable reference to actually mutate the file
446// system.
447
448#[cfg(windows)]
449unsafe impl Send for LockFile {}
450
451#[cfg(windows)]
452unsafe impl Sync for LockFile {}
453
454#[cfg(all(unix, feature = "std"))]
455mod as_fd {
456    use crate::LockFile;
457    use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
458    impl AsRawFd for LockFile {
459        fn as_raw_fd(&self) -> RawFd {
460            self.desc
461        }
462    }
463    impl AsFd for LockFile {
464        fn as_fd(&self) -> BorrowedFd<'_> {
465            // SAFETY: We will not close the file descriptor until
466            // self is dropped.
467            unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
468        }
469    }
470}
471
472#[cfg(all(windows, feature = "std"))]
473mod as_handle {
474    use crate::LockFile;
475    use std::os::windows::io::{
476        AsHandle,
477        AsRawHandle,
478        BorrowedHandle,
479        RawHandle,
480    };
481    impl AsRawHandle for LockFile {
482        fn as_raw_handle(&self) -> RawHandle {
483            // "desc" is a `HANDLE`, which is a a type alias for
484            // *mut winapi::cytpes::c_void
485            //
486            // We want to convert into a RawHandle, which is a type alias for
487            // *mut std::os::raw::c_void
488            //
489            // Since these are both c_voids, and both are handled by the
490            // underlying OS, this pointer cast is okay.
491            self.desc as RawHandle
492        }
493    }
494    impl AsHandle for LockFile {
495        fn as_handle(&self) -> BorrowedHandle<'_> {
496            // SAFETY: We will not close the handle until
497            // self is dropped.
498            unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
499        }
500    }
501}