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}