1use std::{fs::File, path::Path};
2
3pub struct LockedFile {
4 file: File,
5}
6
7impl Drop for LockedFile {
8 fn drop(&mut self) {
9 let _ = sys::unlock(&self.file);
10 }
11}
12
13impl std::ops::Deref for LockedFile {
14 type Target = File;
15
16 fn deref(&self) -> &Self::Target {
17 &self.file
18 }
19}
20
21impl std::ops::DerefMut for LockedFile {
22 fn deref_mut(&mut self) -> &mut Self::Target {
23 &mut self.file
24 }
25}
26
27impl std::io::Write for LockedFile {
28 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
29 self.file.write(buf)
30 }
31
32 fn flush(&mut self) -> std::io::Result<()> {
33 self.file.flush()
34 }
35}
36
37impl std::io::Read for LockedFile {
38 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
39 self.file.read(buf)
40 }
41}
42
43impl std::io::Seek for LockedFile {
44 fn seek(&mut self, to: std::io::SeekFrom) -> std::io::Result<u64> {
45 self.file.seek(to)
46 }
47}
48
49impl LockedFile {
50 pub fn try_open_exclusively(
51 filename: &Path,
52 open_options: &std::fs::OpenOptions,
53 ) -> std::io::Result<Self> {
54 let file = open_options.open(filename)?;
55 sys::try_lock_exclusive(&file)?;
56
57 Ok(Self { file })
58 }
59}
60
61#[cfg(unix)]
62mod sys {
63 use std::{fs::File, os::unix::io::AsRawFd};
64
65 fn flock(file: &File, flag: libc::c_int) -> std::io::Result<()> {
66 let ret = unsafe { libc::flock(file.as_raw_fd(), flag) };
67
68 if ret < 0 {
69 let error = std::io::Error::last_os_error();
70
71 match error.kind() {
72 std::io::ErrorKind::Unsupported => Ok(()), _ => Err(error),
74 }
75 } else {
76 Ok(())
77 }
78 }
79
80 pub(super) fn try_lock_exclusive(file: &File) -> std::io::Result<()> {
81 flock(file, libc::LOCK_EX | libc::LOCK_NB).map_err(|e| {
82 std::io::Error::new(
83 std::io::ErrorKind::WouldBlock,
84 format!("Unable to lock the file: {:?}", e),
85 )
86 })
87 }
88
89 pub(super) fn unlock(file: &File) -> std::io::Result<()> {
90 flock(file, libc::LOCK_UN).map_err(|e| {
91 std::io::Error::new(
92 std::io::ErrorKind::WouldBlock,
93 format!("Unable to unlock the file: {:?}", e),
94 )
95 })
96 }
97}
98
99#[cfg(windows)]
100mod sys {
101 use std::{fs::File, mem, os::windows::io::AsRawHandle};
102
103 use windows_sys::Win32::{
104 Foundation::HANDLE,
105 Storage::FileSystem::{
106 LockFileEx, UnlockFile, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY,
107 },
108 };
109
110 pub(super) fn try_lock_exclusive(file: &File) -> std::io::Result<()> {
111 let flags = LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY;
112
113 let ret = unsafe {
114 let mut overlapped = mem::zeroed();
115 LockFileEx(
116 file.as_raw_handle() as HANDLE,
117 flags,
118 0,
119 !0,
120 !0,
121 &mut overlapped,
122 )
123 };
124
125 if ret == 0 {
126 let error = std::io::Error::last_os_error();
127
128 match error.kind() {
129 std::io::ErrorKind::Unsupported => Ok(()), _ => Err(error),
131 }
132 } else {
133 Ok(())
134 }
135 }
136
137 pub(super) fn unlock(file: &File) -> Result<()> {
138 let ret = unsafe { UnlockFile(file.as_raw_handle() as HANDLE, 0, 0, !0, !0) };
139
140 if ret == 0 {
141 Err(std::io::Error::last_os_error())
142 } else {
143 Ok(())
144 }
145 }
146}
147
148#[cfg(not(any(unix, windows)))]
149mod sys {
150 use std::fs::File;
151
152 pub(super) fn try_lock_exclusive(_file: &File) -> std::io::Result<()> {
153 Ok(())
154 }
155
156 pub(super) fn unlock(_file: &File) -> std::io::Result<()> {
157 Ok(())
158 }
159}