Echo Writes Code

file_ext.rs

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
use crate::errors::{ Result };

/// A trait providing a file locking API for [`std::fs::File`]. Bring this
/// trait into scope in order to call the methods within.
///
/// All provided methods return a lock guard on success. The lock guard must be
/// kept alive for as long as the lock needs to be held, and cannot exceed the
/// lifetime of the locked [`File`](std::fs::File) object.
///
/// ##### For POSIX Systems
///
/// - The underlying locking primitive is
///   [`flock()`](https://man7.org/linux/man-pages/man2/flock.2.html). Note that
///   on some systems this will interoperate with `fcntl()` or `lockf()` locks,
///   but it is not guaranteed.
/// - The locks provided by these methods are advisory only. Other processes
///   that are unaware of a file lock can still open and modify the locked file.
/// - You must open a new [`File`](std::fs::File) object each time you wish to
///   take the lock. Locking the same file object twice will result in unexpected
///   behavior, such as being able to obtain two simultaneous exclusive locks on
///   the same file.
/// - `flock()` locks are not generally well-behaved across network shares
///   (like NFS or SMB), so don't use this crate if you need to support this use
///   case.
///
/// ##### For Windows Systems
///
/// - The underlying locking primitives are
///   [`LockFileEx()`](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex)
///   and
///   [`UnlockFile()`](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-unlockfile).
/// - The locks obtained by these methods are mandatory, meaning that other
///   processes (and even other file operations in the same program) cannot touch
///   the locked file until the lock is released.
/// - It is valid to re-use the same [`File`](std::fs::File) object each time you
///   take the lock; however, for portability, this is discouraged.
pub trait FileExt {
	/// A guard type protecting the file lock. It has a [`Drop`] implementation
	/// that unlocks the file. The lifetime parameter is the lifetime of the
	/// underlying file primitive.
	type LockGuard<'a> where Self: 'a;

	/// Locks the file for shared access. Blocks if the lock is already held for
	/// exclusive access. Returns
	/// [`LockFailed`](crate::errors::Error::LockFailed) if the underlying
	/// locking primitive fails.
	///
	/// # Examples
	///
	/// ```
	/// # use std::fs::{ self };
	/// use std::fs::{ File };
	/// use file_locking::{ FileExt };
	///
	/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
	/// let f = File::options()
	///   .create(true)
	///   .write(true)
	///   .open("shared.lock")?;
	///
	/// {
	///   let _guard = f.lock_shared()?;
	///
	///   // ... do whatever you need to do while the file is locked ...
	/// }
	///
	/// // the lock is now released
	/// # fs::remove_file("shared.lock")?;
	/// # Ok(())
	/// # }
	/// ```
	fn lock_shared(&self) -> Result<Self::LockGuard<'_>>;

	/// Locks the file for exclusive access. Blocks if the lock is already held
	/// for shared or exclusive access. Returns
	/// [`LockFailed`](crate::errors::Error::LockFailed) if the underlying
	/// locking primitive fails.
	///
	/// # Examples
	///
	/// ```
	/// # use std::fs::{ self };
	/// use std::fs::{ File };
	/// use file_locking::{ FileExt };
	///
	/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
	/// let f = File::options()
	///   .create(true)
	///   .write(true)
	///   .open("exclusive.lock")?;
	///
	/// {
	///   let _guard = f.lock_exclusive()?;
	///
	///   // ... do whatever you need to do while the file is locked ...
	/// }
	///
	/// // the lock is now released
	/// # fs::remove_file("exclusive.lock")?;
	/// # Ok(())
	/// # }
	/// ```
	fn lock_exclusive(&self) -> Result<Self::LockGuard<'_>>;

	/// Attempts to lock the file for shared access. Returns
	/// [`AlreadyLocked`](crate::errors::Error::AlreadyLocked) if the lock is
	/// already held for exclusive access, or
	/// [`LockFailed`](crate::errors::Error::LockFailed) if the underlying
	/// locking primitive fails for some other reason.
	///
	/// # Examples
	///
	/// ```
	/// # use std::fs::{ self };
	/// use std::fs::{ File };
	/// use file_locking::{ Error, FileExt };
	///
	/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
	/// let f = File::options()
	///   .create(true)
	///   .write(true)
	///   .open("try_shared.lock")?;
	///
	/// {
	///   let _guard = f.lock_exclusive()?;
	///
	///   // Because this is a non-blocking operation, we can recover from not
	///   // obtaining the lock:
	///   let f2 = File::options()
	///     .read(true)
	///     .open("try_shared.lock")?;
	///
	///   assert_eq!(f2.try_lock_shared().err(), Some(Error::AlreadyLocked));
	/// }
	///
	/// // the lock is now released
	/// # fs::remove_file("try_shared.lock")?;
	/// # Ok(())
	/// # }
	/// ```
	fn try_lock_shared(&self) -> Result<Self::LockGuard<'_>>;

	/// Attempts to lock the file for exclusive access. Returns
	/// [`AlreadyLocked`](crate::errors::Error::AlreadyLocked) if the lock is
	/// currently held for shared or exclusive access, or
	/// [`LockFailed`](crate::errors::Error::LockFailed) if the underlying
	/// locking primitive fails for some other reason.
	///
	/// # Examples
	///
	/// ```
	/// # use std::fs::{ self };
	/// use std::fs::{ File };
	/// use file_locking::{ Error, FileExt };
	///
	/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
	/// let f = File::options()
	///   .create(true)
	///   .write(true)
	///   .open("try_exclusive.lock")?;
	///
	/// {
	///   let _guard = f.try_lock_exclusive()?;
	///
	///   // Because this is a non-blocking operation, we can recover from not
	///   // obtaining the lock:
	///   let f2 = File::options()
	///     .read(true)
	///     .open("try_exclusive.lock")?;
	///
	///   assert_eq!(f2.try_lock_exclusive().err(), Some(Error::AlreadyLocked));
	/// }
	///
	/// // the lock is now released
	/// # fs::remove_file("try_exclusive.lock")?;
	/// # Ok(())
	/// # }
	/// ```
	fn try_lock_exclusive(&self) -> Result<Self::LockGuard<'_>>;
}