fslock/
lib.rs

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