Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 95175f8

Browse files
authoredNov 11, 2024
Rollup merge of #130999 - cberner:flock_pr, r=joboet
Implement file_lock feature This adds lock(), lock_shared(), try_lock(), try_lock_shared(), and unlock() to File gated behind the file_lock feature flag This is the initial implementation of #130994 for Unix and Windows platforms. I will follow it up with an implementation for WASI preview 2
2 parents d4abc31 + 9330786 commit 95175f8

File tree

10 files changed

+575
-0
lines changed

10 files changed

+575
-0
lines changed
 

‎library/std/src/fs.rs

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,223 @@ impl File {
624624
self.inner.datasync()
625625
}
626626

627+
/// Acquire an exclusive advisory lock on the file. Blocks until the lock can be acquired.
628+
///
629+
/// This acquires an exclusive advisory lock; no other file handle to this file may acquire
630+
/// another lock.
631+
///
632+
/// If this file handle, or a clone of it, already holds an advisory lock the exact behavior is
633+
/// unspecified and platform dependent, including the possibility that it will deadlock.
634+
/// However, if this method returns, then an exclusive lock is held.
635+
///
636+
/// If the file not open for writing, it is unspecified whether this function returns an error.
637+
///
638+
/// Note, this is an advisory lock meant to interact with [`lock_shared`], [`try_lock`],
639+
/// [`try_lock_shared`], and [`unlock`]. Its interactions with other methods, such as [`read`]
640+
/// and [`write`] are platform specific, and it may or may not cause non-lockholders to block.
641+
///
642+
/// # Platform-specific behavior
643+
///
644+
/// This function currently corresponds to the `flock` function on Unix with the `LOCK_EX` flag,
645+
/// and the `LockFileEx` function on Windows with the `LOCKFILE_EXCLUSIVE_LOCK` flag. Note that,
646+
/// this [may change in the future][changes].
647+
///
648+
/// [changes]: io#platform-specific-behavior
649+
///
650+
/// [`lock_shared`]: File::lock_shared
651+
/// [`try_lock`]: File::try_lock
652+
/// [`try_lock_shared`]: File::try_lock_shared
653+
/// [`unlock`]: File::unlock
654+
/// [`read`]: Read::read
655+
/// [`write`]: Write::write
656+
///
657+
/// # Examples
658+
///
659+
/// ```no_run
660+
/// #![feature(file_lock)]
661+
/// use std::fs::File;
662+
///
663+
/// fn main() -> std::io::Result<()> {
664+
/// let f = File::open("foo.txt")?;
665+
/// f.lock()?;
666+
/// Ok(())
667+
/// }
668+
/// ```
669+
#[unstable(feature = "file_lock", issue = "130994")]
670+
pub fn lock(&self) -> io::Result<()> {
671+
self.inner.lock()
672+
}
673+
674+
/// Acquire a shared advisory lock on the file. Blocks until the lock can be acquired.
675+
///
676+
/// This acquires a shared advisory lock; more than one file handle may hold a shared lock, but
677+
/// none may hold an exclusive lock.
678+
///
679+
/// If this file handle, or a clone of it, already holds an advisory lock, the exact behavior is
680+
/// unspecified and platform dependent, including the possibility that it will deadlock.
681+
/// However, if this method returns, then a shared lock is held.
682+
///
683+
/// Note, this is an advisory lock meant to interact with [`lock`], [`try_lock`],
684+
/// [`try_lock_shared`], and [`unlock`]. Its interactions with other methods, such as [`read`]
685+
/// and [`write`] are platform specific, and it may or may not cause non-lockholders to block.
686+
///
687+
/// # Platform-specific behavior
688+
///
689+
/// This function currently corresponds to the `flock` function on Unix with the `LOCK_SH` flag,
690+
/// and the `LockFileEx` function on Windows. Note that, this
691+
/// [may change in the future][changes].
692+
///
693+
/// [changes]: io#platform-specific-behavior
694+
///
695+
/// [`lock`]: File::lock
696+
/// [`try_lock`]: File::try_lock
697+
/// [`try_lock_shared`]: File::try_lock_shared
698+
/// [`unlock`]: File::unlock
699+
/// [`read`]: Read::read
700+
/// [`write`]: Write::write
701+
///
702+
/// # Examples
703+
///
704+
/// ```no_run
705+
/// #![feature(file_lock)]
706+
/// use std::fs::File;
707+
///
708+
/// fn main() -> std::io::Result<()> {
709+
/// let f = File::open("foo.txt")?;
710+
/// f.lock_shared()?;
711+
/// Ok(())
712+
/// }
713+
/// ```
714+
#[unstable(feature = "file_lock", issue = "130994")]
715+
pub fn lock_shared(&self) -> io::Result<()> {
716+
self.inner.lock_shared()
717+
}
718+
719+
/// Acquire an exclusive advisory lock on the file. Returns `Ok(false)` if the file is locked.
720+
///
721+
/// This acquires an exclusive advisory lock; no other file handle to this file may acquire
722+
/// another lock.
723+
///
724+
/// If this file handle, or a clone of it, already holds an advisory lock, the exact behavior is
725+
/// unspecified and platform dependent, including the possibility that it will deadlock.
726+
/// However, if this method returns, then an exclusive lock is held.
727+
///
728+
/// If the file not open for writing, it is unspecified whether this function returns an error.
729+
///
730+
/// Note, this is an advisory lock meant to interact with [`lock`], [`lock_shared`],
731+
/// [`try_lock_shared`], and [`unlock`]. Its interactions with other methods, such as [`read`]
732+
/// and [`write`] are platform specific, and it may or may not cause non-lockholders to block.
733+
///
734+
/// # Platform-specific behavior
735+
///
736+
/// This function currently corresponds to the `flock` function on Unix with the `LOCK_EX` and
737+
/// `LOCK_NB` flags, and the `LockFileEx` function on Windows with the `LOCKFILE_EXCLUSIVE_LOCK`
738+
/// and `LOCKFILE_FAIL_IMMEDIATELY` flags. Note that, this
739+
/// [may change in the future][changes].
740+
///
741+
/// [changes]: io#platform-specific-behavior
742+
///
743+
/// [`lock`]: File::lock
744+
/// [`lock_shared`]: File::lock_shared
745+
/// [`try_lock_shared`]: File::try_lock_shared
746+
/// [`unlock`]: File::unlock
747+
/// [`read`]: Read::read
748+
/// [`write`]: Write::write
749+
///
750+
/// # Examples
751+
///
752+
/// ```no_run
753+
/// #![feature(file_lock)]
754+
/// use std::fs::File;
755+
///
756+
/// fn main() -> std::io::Result<()> {
757+
/// let f = File::open("foo.txt")?;
758+
/// f.try_lock()?;
759+
/// Ok(())
760+
/// }
761+
/// ```
762+
#[unstable(feature = "file_lock", issue = "130994")]
763+
pub fn try_lock(&self) -> io::Result<bool> {
764+
self.inner.try_lock()
765+
}
766+
767+
/// Acquire a shared advisory lock on the file.
768+
/// Returns `Ok(false)` if the file is exclusively locked.
769+
///
770+
/// This acquires a shared advisory lock; more than one file handle may hold a shared lock, but
771+
/// none may hold an exclusive lock.
772+
///
773+
/// If this file handle, or a clone of it, already holds an advisory lock, the exact behavior is
774+
/// unspecified and platform dependent, including the possibility that it will deadlock.
775+
/// However, if this method returns, then a shared lock is held.
776+
///
777+
/// Note, this is an advisory lock meant to interact with [`lock`], [`try_lock`],
778+
/// [`try_lock`], and [`unlock`]. Its interactions with other methods, such as [`read`]
779+
/// and [`write`] are platform specific, and it may or may not cause non-lockholders to block.
780+
///
781+
/// # Platform-specific behavior
782+
///
783+
/// This function currently corresponds to the `flock` function on Unix with the `LOCK_SH` and
784+
/// `LOCK_NB` flags, and the `LockFileEx` function on Windows with the
785+
/// `LOCKFILE_FAIL_IMMEDIATELY` flag. Note that, this
786+
/// [may change in the future][changes].
787+
///
788+
/// [changes]: io#platform-specific-behavior
789+
///
790+
/// [`lock`]: File::lock
791+
/// [`lock_shared`]: File::lock_shared
792+
/// [`try_lock`]: File::try_lock
793+
/// [`unlock`]: File::unlock
794+
/// [`read`]: Read::read
795+
/// [`write`]: Write::write
796+
///
797+
/// # Examples
798+
///
799+
/// ```no_run
800+
/// #![feature(file_lock)]
801+
/// use std::fs::File;
802+
///
803+
/// fn main() -> std::io::Result<()> {
804+
/// let f = File::open("foo.txt")?;
805+
/// f.try_lock_shared()?;
806+
/// Ok(())
807+
/// }
808+
/// ```
809+
#[unstable(feature = "file_lock", issue = "130994")]
810+
pub fn try_lock_shared(&self) -> io::Result<bool> {
811+
self.inner.try_lock_shared()
812+
}
813+
814+
/// Release all locks on the file.
815+
///
816+
/// All remaining locks are released when the file handle, and all clones of it, are dropped.
817+
///
818+
/// # Platform-specific behavior
819+
///
820+
/// This function currently corresponds to the `flock` function on Unix with the `LOCK_UN` flag,
821+
/// and the `UnlockFile` function on Windows. Note that, this
822+
/// [may change in the future][changes].
823+
///
824+
/// [changes]: io#platform-specific-behavior
825+
///
826+
/// # Examples
827+
///
828+
/// ```no_run
829+
/// #![feature(file_lock)]
830+
/// use std::fs::File;
831+
///
832+
/// fn main() -> std::io::Result<()> {
833+
/// let f = File::open("foo.txt")?;
834+
/// f.lock()?;
835+
/// f.unlock()?;
836+
/// Ok(())
837+
/// }
838+
/// ```
839+
#[unstable(feature = "file_lock", issue = "130994")]
840+
pub fn unlock(&self) -> io::Result<()> {
841+
self.inner.unlock()
842+
}
843+
627844
/// Truncates or extends the underlying file, updating the size of
628845
/// this file to become `size`.
629846
///

‎library/std/src/fs/tests.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,124 @@ fn file_test_io_seek_and_write() {
203203
assert!(read_str == final_msg);
204204
}
205205

206+
#[test]
207+
fn file_lock_multiple_shared() {
208+
let tmpdir = tmpdir();
209+
let filename = &tmpdir.join("file_lock_multiple_shared_test.txt");
210+
let f1 = check!(File::create(filename));
211+
let f2 = check!(OpenOptions::new().write(true).open(filename));
212+
213+
// Check that we can acquire concurrent shared locks
214+
check!(f1.lock_shared());
215+
check!(f2.lock_shared());
216+
check!(f1.unlock());
217+
check!(f2.unlock());
218+
assert!(check!(f1.try_lock_shared()));
219+
assert!(check!(f2.try_lock_shared()));
220+
}
221+
222+
#[test]
223+
fn file_lock_blocking() {
224+
let tmpdir = tmpdir();
225+
let filename = &tmpdir.join("file_lock_blocking_test.txt");
226+
let f1 = check!(File::create(filename));
227+
let f2 = check!(OpenOptions::new().write(true).open(filename));
228+
229+
// Check that shared locks block exclusive locks
230+
check!(f1.lock_shared());
231+
assert!(!check!(f2.try_lock()));
232+
check!(f1.unlock());
233+
234+
// Check that exclusive locks block shared locks
235+
check!(f1.lock());
236+
assert!(!check!(f2.try_lock_shared()));
237+
}
238+
239+
#[test]
240+
fn file_lock_drop() {
241+
let tmpdir = tmpdir();
242+
let filename = &tmpdir.join("file_lock_dup_test.txt");
243+
let f1 = check!(File::create(filename));
244+
let f2 = check!(OpenOptions::new().write(true).open(filename));
245+
246+
// Check that locks are released when the File is dropped
247+
check!(f1.lock_shared());
248+
assert!(!check!(f2.try_lock()));
249+
drop(f1);
250+
assert!(check!(f2.try_lock()));
251+
}
252+
253+
#[test]
254+
fn file_lock_dup() {
255+
let tmpdir = tmpdir();
256+
let filename = &tmpdir.join("file_lock_dup_test.txt");
257+
let f1 = check!(File::create(filename));
258+
let f2 = check!(OpenOptions::new().write(true).open(filename));
259+
260+
// Check that locks are not dropped if the File has been cloned
261+
check!(f1.lock_shared());
262+
assert!(!check!(f2.try_lock()));
263+
let cloned = check!(f1.try_clone());
264+
drop(f1);
265+
assert!(!check!(f2.try_lock()));
266+
drop(cloned)
267+
}
268+
269+
#[test]
270+
#[cfg(windows)]
271+
fn file_lock_double_unlock() {
272+
let tmpdir = tmpdir();
273+
let filename = &tmpdir.join("file_lock_double_unlock_test.txt");
274+
let f1 = check!(File::create(filename));
275+
let f2 = check!(OpenOptions::new().write(true).open(filename));
276+
277+
// On Windows a file handle may acquire both a shared and exclusive lock.
278+
// Check that both are released by unlock()
279+
check!(f1.lock());
280+
check!(f1.lock_shared());
281+
assert!(!check!(f2.try_lock()));
282+
check!(f1.unlock());
283+
assert!(check!(f2.try_lock()));
284+
}
285+
286+
#[test]
287+
#[cfg(windows)]
288+
fn file_lock_blocking_async() {
289+
use crate::thread::{sleep, spawn};
290+
const FILE_FLAG_OVERLAPPED: u32 = 0x40000000;
291+
292+
let tmpdir = tmpdir();
293+
let filename = &tmpdir.join("file_lock_blocking_async.txt");
294+
let f1 = check!(File::create(filename));
295+
let f2 =
296+
check!(OpenOptions::new().custom_flags(FILE_FLAG_OVERLAPPED).write(true).open(filename));
297+
298+
check!(f1.lock());
299+
300+
// Ensure that lock() is synchronous when the file is opened for asynchronous IO
301+
let t = spawn(move || {
302+
check!(f2.lock());
303+
});
304+
sleep(Duration::from_secs(1));
305+
assert!(!t.is_finished());
306+
check!(f1.unlock());
307+
t.join().unwrap();
308+
309+
// Ensure that lock_shared() is synchronous when the file is opened for asynchronous IO
310+
let f2 =
311+
check!(OpenOptions::new().custom_flags(FILE_FLAG_OVERLAPPED).write(true).open(filename));
312+
check!(f1.lock());
313+
314+
// Ensure that lock() is synchronous when the file is opened for asynchronous IO
315+
let t = spawn(move || {
316+
check!(f2.lock_shared());
317+
});
318+
sleep(Duration::from_secs(1));
319+
assert!(!t.is_finished());
320+
check!(f1.unlock());
321+
t.join().unwrap();
322+
}
323+
206324
#[test]
207325
fn file_test_io_seek_shakedown() {
208326
// 01234567890123

‎library/std/src/sys/pal/hermit/fs.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,26 @@ impl File {
364364
self.fsync()
365365
}
366366

367+
pub fn lock(&self) -> io::Result<()> {
368+
unsupported()
369+
}
370+
371+
pub fn lock_shared(&self) -> io::Result<()> {
372+
unsupported()
373+
}
374+
375+
pub fn try_lock(&self) -> io::Result<bool> {
376+
unsupported()
377+
}
378+
379+
pub fn try_lock_shared(&self) -> io::Result<bool> {
380+
unsupported()
381+
}
382+
383+
pub fn unlock(&self) -> io::Result<()> {
384+
unsupported()
385+
}
386+
367387
pub fn truncate(&self, _size: u64) -> io::Result<()> {
368388
Err(Error::from_raw_os_error(22))
369389
}

‎library/std/src/sys/pal/solid/fs.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,26 @@ impl File {
350350
self.flush()
351351
}
352352

353+
pub fn lock(&self) -> io::Result<()> {
354+
unsupported()
355+
}
356+
357+
pub fn lock_shared(&self) -> io::Result<()> {
358+
unsupported()
359+
}
360+
361+
pub fn try_lock(&self) -> io::Result<bool> {
362+
unsupported()
363+
}
364+
365+
pub fn try_lock_shared(&self) -> io::Result<bool> {
366+
unsupported()
367+
}
368+
369+
pub fn unlock(&self) -> io::Result<()> {
370+
unsupported()
371+
}
372+
353373
pub fn truncate(&self, _size: u64) -> io::Result<()> {
354374
unsupported()
355375
}

‎library/std/src/sys/pal/unix/fs.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,43 @@ impl File {
12541254
}
12551255
}
12561256

1257+
pub fn lock(&self) -> io::Result<()> {
1258+
cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX) })?;
1259+
return Ok(());
1260+
}
1261+
1262+
pub fn lock_shared(&self) -> io::Result<()> {
1263+
cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH) })?;
1264+
return Ok(());
1265+
}
1266+
1267+
pub fn try_lock(&self) -> io::Result<bool> {
1268+
let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) });
1269+
if let Err(ref err) = result {
1270+
if err.kind() == io::ErrorKind::WouldBlock {
1271+
return Ok(false);
1272+
}
1273+
}
1274+
result?;
1275+
return Ok(true);
1276+
}
1277+
1278+
pub fn try_lock_shared(&self) -> io::Result<bool> {
1279+
let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH | libc::LOCK_NB) });
1280+
if let Err(ref err) = result {
1281+
if err.kind() == io::ErrorKind::WouldBlock {
1282+
return Ok(false);
1283+
}
1284+
}
1285+
result?;
1286+
return Ok(true);
1287+
}
1288+
1289+
pub fn unlock(&self) -> io::Result<()> {
1290+
cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_UN) })?;
1291+
return Ok(());
1292+
}
1293+
12571294
pub fn truncate(&self, size: u64) -> io::Result<()> {
12581295
let size: off64_t =
12591296
size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;

‎library/std/src/sys/pal/unsupported/fs.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,26 @@ impl File {
198198
self.0
199199
}
200200

201+
pub fn lock(&self) -> io::Result<()> {
202+
self.0
203+
}
204+
205+
pub fn lock_shared(&self) -> io::Result<()> {
206+
self.0
207+
}
208+
209+
pub fn try_lock(&self) -> io::Result<bool> {
210+
self.0
211+
}
212+
213+
pub fn try_lock_shared(&self) -> io::Result<bool> {
214+
self.0
215+
}
216+
217+
pub fn unlock(&self) -> io::Result<()> {
218+
self.0
219+
}
220+
201221
pub fn truncate(&self, _size: u64) -> io::Result<()> {
202222
self.0
203223
}

‎library/std/src/sys/pal/wasi/fs.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,26 @@ impl File {
420420
self.fd.datasync()
421421
}
422422

423+
pub fn lock(&self) -> io::Result<()> {
424+
unsupported()
425+
}
426+
427+
pub fn lock_shared(&self) -> io::Result<()> {
428+
unsupported()
429+
}
430+
431+
pub fn try_lock(&self) -> io::Result<bool> {
432+
unsupported()
433+
}
434+
435+
pub fn try_lock_shared(&self) -> io::Result<bool> {
436+
unsupported()
437+
}
438+
439+
pub fn unlock(&self) -> io::Result<()> {
440+
unsupported()
441+
}
442+
423443
pub fn truncate(&self, size: u64) -> io::Result<()> {
424444
self.fd.filestat_set_size(size)
425445
}

‎library/std/src/sys/pal/windows/c/bindings.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2351,6 +2351,9 @@ Windows.Win32.Storage.FileSystem.GetFinalPathNameByHandleW
23512351
Windows.Win32.Storage.FileSystem.GetFullPathNameW
23522352
Windows.Win32.Storage.FileSystem.GetTempPathW
23532353
Windows.Win32.Storage.FileSystem.INVALID_FILE_ATTRIBUTES
2354+
Windows.Win32.Storage.FileSystem.LOCKFILE_EXCLUSIVE_LOCK
2355+
Windows.Win32.Storage.FileSystem.LOCKFILE_FAIL_IMMEDIATELY
2356+
Windows.Win32.Storage.FileSystem.LockFileEx
23542357
Windows.Win32.Storage.FileSystem.LPPROGRESS_ROUTINE
23552358
Windows.Win32.Storage.FileSystem.LPPROGRESS_ROUTINE_CALLBACK_REASON
23562359
Windows.Win32.Storage.FileSystem.MAXIMUM_REPARSE_DATA_BUFFER_SIZE
@@ -2396,6 +2399,7 @@ Windows.Win32.Storage.FileSystem.SYMBOLIC_LINK_FLAG_DIRECTORY
23962399
Windows.Win32.Storage.FileSystem.SYMBOLIC_LINK_FLAGS
23972400
Windows.Win32.Storage.FileSystem.SYNCHRONIZE
23982401
Windows.Win32.Storage.FileSystem.TRUNCATE_EXISTING
2402+
Windows.Win32.Storage.FileSystem.UnlockFile
23992403
Windows.Win32.Storage.FileSystem.VOLUME_NAME_DOS
24002404
Windows.Win32.Storage.FileSystem.VOLUME_NAME_GUID
24012405
Windows.Win32.Storage.FileSystem.VOLUME_NAME_NONE

‎library/std/src/sys/pal/windows/c/windows_sys.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ windows_targets::link!("kernel32.dll" "system" fn InitOnceBeginInitialize(lpinit
6565
windows_targets::link!("kernel32.dll" "system" fn InitOnceComplete(lpinitonce : *mut INIT_ONCE, dwflags : u32, lpcontext : *const core::ffi::c_void) -> BOOL);
6666
windows_targets::link!("kernel32.dll" "system" fn InitializeProcThreadAttributeList(lpattributelist : LPPROC_THREAD_ATTRIBUTE_LIST, dwattributecount : u32, dwflags : u32, lpsize : *mut usize) -> BOOL);
6767
windows_targets::link!("kernel32.dll" "system" fn LocalFree(hmem : HLOCAL) -> HLOCAL);
68+
windows_targets::link!("kernel32.dll" "system" fn LockFileEx(hfile : HANDLE, dwflags : LOCK_FILE_FLAGS, dwreserved : u32, nnumberofbytestolocklow : u32, nnumberofbytestolockhigh : u32, lpoverlapped : *mut OVERLAPPED) -> BOOL);
6869
windows_targets::link!("kernel32.dll" "system" fn MoveFileExW(lpexistingfilename : PCWSTR, lpnewfilename : PCWSTR, dwflags : MOVE_FILE_FLAGS) -> BOOL);
6970
windows_targets::link!("kernel32.dll" "system" fn MultiByteToWideChar(codepage : u32, dwflags : MULTI_BYTE_TO_WIDE_CHAR_FLAGS, lpmultibytestr : PCSTR, cbmultibyte : i32, lpwidecharstr : PWSTR, cchwidechar : i32) -> i32);
7071
windows_targets::link!("kernel32.dll" "system" fn QueryPerformanceCounter(lpperformancecount : *mut i64) -> BOOL);
@@ -96,6 +97,7 @@ windows_targets::link!("kernel32.dll" "system" fn TlsGetValue(dwtlsindex : u32)
9697
windows_targets::link!("kernel32.dll" "system" fn TlsSetValue(dwtlsindex : u32, lptlsvalue : *const core::ffi::c_void) -> BOOL);
9798
windows_targets::link!("kernel32.dll" "system" fn TryAcquireSRWLockExclusive(srwlock : *mut SRWLOCK) -> BOOLEAN);
9899
windows_targets::link!("kernel32.dll" "system" fn TryAcquireSRWLockShared(srwlock : *mut SRWLOCK) -> BOOLEAN);
100+
windows_targets::link!("kernel32.dll" "system" fn UnlockFile(hfile : HANDLE, dwfileoffsetlow : u32, dwfileoffsethigh : u32, nnumberofbytestounlocklow : u32, nnumberofbytestounlockhigh : u32) -> BOOL);
99101
windows_targets::link!("kernel32.dll" "system" fn UpdateProcThreadAttribute(lpattributelist : LPPROC_THREAD_ATTRIBUTE_LIST, dwflags : u32, attribute : usize, lpvalue : *const core::ffi::c_void, cbsize : usize, lppreviousvalue : *mut core::ffi::c_void, lpreturnsize : *const usize) -> BOOL);
100102
windows_targets::link!("kernel32.dll" "system" fn WaitForMultipleObjects(ncount : u32, lphandles : *const HANDLE, bwaitall : BOOL, dwmilliseconds : u32) -> WAIT_EVENT);
101103
windows_targets::link!("kernel32.dll" "system" fn WaitForSingleObject(hhandle : HANDLE, dwmilliseconds : u32) -> WAIT_EVENT);
@@ -2730,6 +2732,9 @@ pub struct LINGER {
27302732
pub l_onoff: u16,
27312733
pub l_linger: u16,
27322734
}
2735+
pub const LOCKFILE_EXCLUSIVE_LOCK: LOCK_FILE_FLAGS = 2u32;
2736+
pub const LOCKFILE_FAIL_IMMEDIATELY: LOCK_FILE_FLAGS = 1u32;
2737+
pub type LOCK_FILE_FLAGS = u32;
27332738
pub type LPOVERLAPPED_COMPLETION_ROUTINE = Option<
27342739
unsafe extern "system" fn(
27352740
dwerrorcode: u32,

‎library/std/src/sys/pal/windows/fs.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,120 @@ impl File {
346346
self.fsync()
347347
}
348348

349+
fn acquire_lock(&self, flags: c::LOCK_FILE_FLAGS) -> io::Result<()> {
350+
unsafe {
351+
let mut overlapped: c::OVERLAPPED = mem::zeroed();
352+
let event = c::CreateEventW(ptr::null_mut(), c::FALSE, c::FALSE, ptr::null());
353+
if event.is_null() {
354+
return Err(io::Error::last_os_error());
355+
}
356+
overlapped.hEvent = event;
357+
let lock_result = cvt(c::LockFileEx(
358+
self.handle.as_raw_handle(),
359+
flags,
360+
0,
361+
u32::MAX,
362+
u32::MAX,
363+
&mut overlapped,
364+
));
365+
366+
let final_result = match lock_result {
367+
Ok(_) => Ok(()),
368+
Err(err) => {
369+
if err.raw_os_error() == Some(c::ERROR_IO_PENDING as i32) {
370+
// Wait for the lock to be acquired, and get the lock operation status.
371+
// This can happen asynchronously, if the file handle was opened for async IO
372+
let mut bytes_transferred = 0;
373+
cvt(c::GetOverlappedResult(
374+
self.handle.as_raw_handle(),
375+
&mut overlapped,
376+
&mut bytes_transferred,
377+
c::TRUE,
378+
))
379+
.map(|_| ())
380+
} else {
381+
Err(err)
382+
}
383+
}
384+
};
385+
c::CloseHandle(overlapped.hEvent);
386+
final_result
387+
}
388+
}
389+
390+
pub fn lock(&self) -> io::Result<()> {
391+
self.acquire_lock(c::LOCKFILE_EXCLUSIVE_LOCK)
392+
}
393+
394+
pub fn lock_shared(&self) -> io::Result<()> {
395+
self.acquire_lock(0)
396+
}
397+
398+
pub fn try_lock(&self) -> io::Result<bool> {
399+
let result = cvt(unsafe {
400+
let mut overlapped = mem::zeroed();
401+
c::LockFileEx(
402+
self.handle.as_raw_handle(),
403+
c::LOCKFILE_EXCLUSIVE_LOCK | c::LOCKFILE_FAIL_IMMEDIATELY,
404+
0,
405+
u32::MAX,
406+
u32::MAX,
407+
&mut overlapped,
408+
)
409+
});
410+
411+
match result {
412+
Ok(_) => Ok(true),
413+
Err(err)
414+
if err.raw_os_error() == Some(c::ERROR_IO_PENDING as i32)
415+
|| err.raw_os_error() == Some(c::ERROR_LOCK_VIOLATION as i32) =>
416+
{
417+
Ok(false)
418+
}
419+
Err(err) => Err(err),
420+
}
421+
}
422+
423+
pub fn try_lock_shared(&self) -> io::Result<bool> {
424+
let result = cvt(unsafe {
425+
let mut overlapped = mem::zeroed();
426+
c::LockFileEx(
427+
self.handle.as_raw_handle(),
428+
c::LOCKFILE_FAIL_IMMEDIATELY,
429+
0,
430+
u32::MAX,
431+
u32::MAX,
432+
&mut overlapped,
433+
)
434+
});
435+
436+
match result {
437+
Ok(_) => Ok(true),
438+
Err(err)
439+
if err.raw_os_error() == Some(c::ERROR_IO_PENDING as i32)
440+
|| err.raw_os_error() == Some(c::ERROR_LOCK_VIOLATION as i32) =>
441+
{
442+
Ok(false)
443+
}
444+
Err(err) => Err(err),
445+
}
446+
}
447+
448+
pub fn unlock(&self) -> io::Result<()> {
449+
// Unlock the handle twice because LockFileEx() allows a file handle to acquire
450+
// both an exclusive and shared lock, in which case the documentation states that:
451+
// "...two unlock operations are necessary to unlock the region; the first unlock operation
452+
// unlocks the exclusive lock, the second unlock operation unlocks the shared lock"
453+
cvt(unsafe { c::UnlockFile(self.handle.as_raw_handle(), 0, 0, u32::MAX, u32::MAX) })?;
454+
let result =
455+
cvt(unsafe { c::UnlockFile(self.handle.as_raw_handle(), 0, 0, u32::MAX, u32::MAX) });
456+
match result {
457+
Ok(_) => Ok(()),
458+
Err(err) if err.raw_os_error() == Some(c::ERROR_NOT_LOCKED as i32) => Ok(()),
459+
Err(err) => Err(err),
460+
}
461+
}
462+
349463
pub fn truncate(&self, size: u64) -> io::Result<()> {
350464
let info = c::FILE_END_OF_FILE_INFO { EndOfFile: size as i64 };
351465
api::set_file_information_by_handle(self.handle.as_raw_handle(), &info).io_result()

0 commit comments

Comments
 (0)
Please sign in to comment.