cli/util/
file_lock.rs

1/*---------------------------------------------------------------------------------------------
2 *  Copyright (c) Microsoft Corporation. All rights reserved.
3 *  Licensed under the MIT License. See License.txt in the project root for license information.
4 *--------------------------------------------------------------------------------------------*/
5
6use crate::util::errors::CodeError;
7use std::{fs::File, io};
8
9pub struct FileLock {
10	file: File,
11	#[cfg(windows)]
12	overlapped: winapi::um::minwinbase::OVERLAPPED,
13}
14
15#[cfg(windows)] // overlapped is thread-safe, mark it so with this
16unsafe impl Send for FileLock {}
17
18pub enum Lock {
19	Acquired(FileLock),
20	AlreadyLocked(File),
21}
22
23/// Number of locked bytes in the file. On Windows, locking prevents reads,
24/// but consumers of the lock may still want to read what the locking file
25/// as written. Thus, only PREFIX_LOCKED_BYTES are locked, and any globally-
26/// readable content should be written after the prefix.
27#[cfg(windows)]
28pub const PREFIX_LOCKED_BYTES: usize = 1;
29
30#[cfg(unix)]
31pub const PREFIX_LOCKED_BYTES: usize = 0;
32
33impl FileLock {
34	#[cfg(windows)]
35	pub fn acquire(file: File) -> Result<Lock, CodeError> {
36		use std::os::windows::prelude::AsRawHandle;
37		use winapi::{
38			shared::winerror::{ERROR_IO_PENDING, ERROR_LOCK_VIOLATION},
39			um::{
40				fileapi::LockFileEx,
41				minwinbase::{LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY},
42			},
43		};
44
45		let handle = file.as_raw_handle();
46		let (overlapped, ok) = unsafe {
47			let mut overlapped = std::mem::zeroed();
48			let ok = LockFileEx(
49				handle,
50				LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
51				0,
52				PREFIX_LOCKED_BYTES as u32,
53				0,
54				&mut overlapped,
55			);
56
57			(overlapped, ok)
58		};
59
60		if ok != 0 {
61			return Ok(Lock::Acquired(Self { file, overlapped }));
62		}
63
64		let err = io::Error::last_os_error();
65		let raw = err.raw_os_error();
66		// docs report it should return ERROR_IO_PENDING, but in my testing it actually
67		// returns ERROR_LOCK_VIOLATION. Or maybe winapi is wrong?
68		if raw == Some(ERROR_IO_PENDING as i32) || raw == Some(ERROR_LOCK_VIOLATION as i32) {
69			return Ok(Lock::AlreadyLocked(file));
70		}
71
72		Err(CodeError::SingletonLockfileOpenFailed(err))
73	}
74
75	#[cfg(unix)]
76	pub fn acquire(file: File) -> Result<Lock, CodeError> {
77		use std::os::unix::io::AsRawFd;
78
79		let fd = file.as_raw_fd();
80		let res = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
81		if res == 0 {
82			return Ok(Lock::Acquired(Self { file }));
83		}
84
85		let err = io::Error::last_os_error();
86		if err.kind() == io::ErrorKind::WouldBlock {
87			return Ok(Lock::AlreadyLocked(file));
88		}
89
90		Err(CodeError::SingletonLockfileOpenFailed(err))
91	}
92
93	pub fn file(&self) -> &File {
94		&self.file
95	}
96
97	pub fn file_mut(&mut self) -> &mut File {
98		&mut self.file
99	}
100}
101
102impl Drop for FileLock {
103	#[cfg(windows)]
104	fn drop(&mut self) {
105		use std::os::windows::prelude::AsRawHandle;
106		use winapi::um::fileapi::UnlockFileEx;
107
108		unsafe {
109			UnlockFileEx(
110				self.file.as_raw_handle(),
111				0,
112				u32::MAX,
113				u32::MAX,
114				&mut self.overlapped,
115			)
116		};
117	}
118
119	#[cfg(unix)]
120	fn drop(&mut self) {
121		use std::os::unix::io::AsRawFd;
122
123		unsafe { libc::flock(self.file.as_raw_fd(), libc::LOCK_UN) };
124	}
125}