1
+ //! File-locking support.
2
+ //!
3
+ //! This module defines the [`Filesystem`] type which is an abstraction over a
4
+ //! filesystem, ensuring that access to the filesystem is only done through
5
+ //! coordinated locks.
6
+ //!
7
+ //! The [`FileLock`] type represents a locked file, and provides access to the
8
+ //! file.
9
+
1
10
use std:: fs:: { File , OpenOptions } ;
2
11
use std:: io;
3
12
use std:: io:: { Read , Seek , SeekFrom , Write } ;
@@ -10,6 +19,18 @@ use anyhow::Context as _;
10
19
use cargo_util:: paths;
11
20
use sys:: * ;
12
21
22
+ /// A locked file.
23
+ ///
24
+ /// This provides access to file while holding a lock on the file. This type
25
+ /// implements the [`Read`], [`Write`], and [`Seek`] traits to provide access
26
+ /// to the underlying file.
27
+ ///
28
+ /// Locks are either shared (multiple processes can access the file) or
29
+ /// exclusive (only one process can access the file).
30
+ ///
31
+ /// This type is created via methods on the [`Filesystem`] type.
32
+ ///
33
+ /// When this value is dropped, the lock will be released.
13
34
#[ derive( Debug ) ]
14
35
pub struct FileLock {
15
36
f : Option < File > ,
@@ -95,6 +116,32 @@ impl Drop for FileLock {
95
116
/// The `Path` of a filesystem cannot be learned unless it's done in a locked
96
117
/// fashion, and otherwise functions on this structure are prepared to handle
97
118
/// concurrent invocations across multiple instances of Cargo.
119
+ ///
120
+ /// The methods on `Filesystem` that open files return a [`FileLock`] which
121
+ /// holds the lock, and that type provides methods for accessing the
122
+ /// underlying file.
123
+ ///
124
+ /// If the blocking methods (like [`Filesystem::open_ro_shared`]) detect that
125
+ /// they will block, then they will display a message to the user letting them
126
+ /// know it is blocked. There are non-blocking variants starting with the
127
+ /// `try_` prefix like [`Filesystem::try_open_ro_shared_create`].
128
+ ///
129
+ /// The behavior of locks acquired by the `Filesystem` depend on the operating
130
+ /// system. On unix-like system, they are advisory using [`flock`], and thus
131
+ /// not enforced against processes which do not try to acquire the lock. On
132
+ /// Windows, they are mandatory using [`LockFileEx`], enforced against all
133
+ /// processes.
134
+ ///
135
+ /// This **does not** guarantee that a lock is acquired. In some cases, for
136
+ /// example on filesystems that don't support locking, it will return a
137
+ /// [`FileLock`] even though the filesystem lock was not acquired. This is
138
+ /// intended to provide a graceful fallback instead of refusing to work.
139
+ /// Usually there aren't multiple processes accessing the same resource. In
140
+ /// that case, it is the user's responsibility to not run concurrent
141
+ /// processes.
142
+ ///
143
+ /// [`flock`]: https://linux.die.net/man/2/flock
144
+ /// [`LockFileEx`]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex
98
145
#[ derive( Clone , Debug ) ]
99
146
pub struct Filesystem {
100
147
root : PathBuf ,
@@ -147,17 +194,22 @@ impl Filesystem {
147
194
self . root . display ( )
148
195
}
149
196
150
- /// Opens exclusive access to a file, returning the locked version of a
151
- /// file.
197
+ /// Opens read-write exclusive access to a file, returning the locked
198
+ /// version of a file.
152
199
///
153
200
/// This function will create a file at `path` if it doesn't already exist
154
201
/// (including intermediate directories), and then it will acquire an
155
202
/// exclusive lock on `path`. If the process must block waiting for the
156
- /// lock, the `msg` is printed to `config` .
203
+ /// lock, the `msg` is printed to [`Config`] .
157
204
///
158
205
/// The returned file can be accessed to look at the path and also has
159
206
/// read/write access to the underlying file.
160
- pub fn open_rw < P > ( & self , path : P , config : & Config , msg : & str ) -> CargoResult < FileLock >
207
+ pub fn open_rw_exclusive_create < P > (
208
+ & self ,
209
+ path : P ,
210
+ config : & Config ,
211
+ msg : & str ,
212
+ ) -> CargoResult < FileLock >
161
213
where
162
214
P : AsRef < Path > ,
163
215
{
@@ -170,11 +222,14 @@ impl Filesystem {
170
222
Ok ( FileLock { f : Some ( f) , path } )
171
223
}
172
224
173
- /// A non-blocking version of [`Filesystem::open_rw `].
225
+ /// A non-blocking version of [`Filesystem::open_rw_exclusive_create `].
174
226
///
175
227
/// Returns `None` if the operation would block due to another process
176
228
/// holding the lock.
177
- pub fn try_open_rw < P : AsRef < Path > > ( & self , path : P ) -> CargoResult < Option < FileLock > > {
229
+ pub fn try_open_rw_exclusive_create < P : AsRef < Path > > (
230
+ & self ,
231
+ path : P ,
232
+ ) -> CargoResult < Option < FileLock > > {
178
233
let mut opts = OpenOptions :: new ( ) ;
179
234
opts. read ( true ) . write ( true ) . create ( true ) ;
180
235
let ( path, f) = self . open ( path. as_ref ( ) , & opts, true ) ?;
@@ -185,16 +240,16 @@ impl Filesystem {
185
240
}
186
241
}
187
242
188
- /// Opens shared access to a file, returning the locked version of a file.
243
+ /// Opens read-only shared access to a file, returning the locked version of a file.
189
244
///
190
245
/// This function will fail if `path` doesn't already exist, but if it does
191
246
/// then it will acquire a shared lock on `path`. If the process must block
192
- /// waiting for the lock, the `msg` is printed to `config` .
247
+ /// waiting for the lock, the `msg` is printed to [`Config`] .
193
248
///
194
249
/// The returned file can be accessed to look at the path and also has read
195
250
/// access to the underlying file. Any writes to the file will return an
196
251
/// error.
197
- pub fn open_ro < P > ( & self , path : P , config : & Config , msg : & str ) -> CargoResult < FileLock >
252
+ pub fn open_ro_shared < P > ( & self , path : P , config : & Config , msg : & str ) -> CargoResult < FileLock >
198
253
where
199
254
P : AsRef < Path > ,
200
255
{
@@ -205,11 +260,12 @@ impl Filesystem {
205
260
Ok ( FileLock { f : Some ( f) , path } )
206
261
}
207
262
208
- /// Opens shared access to a file, returning the locked version of a file.
263
+ /// Opens read-only shared access to a file, returning the locked version of a file.
209
264
///
210
- /// Compared to [`Filesystem::open_ro`], this will create the file (and
211
- /// any directories in the parent) if the file does not already exist.
212
- pub fn open_shared_create < P : AsRef < Path > > (
265
+ /// Compared to [`Filesystem::open_ro_shared`], this will create the file
266
+ /// (and any directories in the parent) if the file does not already
267
+ /// exist.
268
+ pub fn open_ro_shared_create < P : AsRef < Path > > (
213
269
& self ,
214
270
path : P ,
215
271
config : & Config ,
@@ -224,11 +280,14 @@ impl Filesystem {
224
280
Ok ( FileLock { f : Some ( f) , path } )
225
281
}
226
282
227
- /// A non-blocking version of [`Filesystem::open_shared_create `].
283
+ /// A non-blocking version of [`Filesystem::open_ro_shared_create `].
228
284
///
229
285
/// Returns `None` if the operation would block due to another process
230
286
/// holding the lock.
231
- pub fn try_open_shared_create < P : AsRef < Path > > ( & self , path : P ) -> CargoResult < Option < FileLock > > {
287
+ pub fn try_open_ro_shared_create < P : AsRef < Path > > (
288
+ & self ,
289
+ path : P ,
290
+ ) -> CargoResult < Option < FileLock > > {
232
291
let mut opts = OpenOptions :: new ( ) ;
233
292
opts. read ( true ) . write ( true ) . create ( true ) ;
234
293
let ( path, f) = self . open ( path. as_ref ( ) , & opts, true ) ?;
@@ -316,7 +375,7 @@ fn try_acquire(path: &Path, lock_try: &dyn Fn() -> io::Result<()>) -> CargoResul
316
375
/// This function will acquire the lock on a `path`, printing out a nice message
317
376
/// to the console if we have to wait for it. It will first attempt to use `try`
318
377
/// to acquire a lock on the crate, and in the case of contention it will emit a
319
- /// status message based on `msg` to `config` 's shell, and then use `block` to
378
+ /// status message based on `msg` to [`Config`] 's shell, and then use `block` to
320
379
/// block waiting to acquire a lock.
321
380
///
322
381
/// Returns an error if the lock could not be acquired or if any error other
0 commit comments