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 {}