]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_data_structures/src/flock.rs
Auto merge of #91182 - ChrisDenton:command-broken-symlink, r=m-ou-se
[rust.git] / compiler / rustc_data_structures / src / flock.rs
1 //! Simple file-locking apis for each OS.
2 //!
3 //! This is not meant to be in the standard library, it does nothing with
4 //! green/native threading. This is just a bare-bones enough solution for
5 //! librustdoc, it is not production quality at all.
6
7 #![allow(non_camel_case_types)]
8 #![allow(nonstandard_style)]
9
10 use std::fs::{File, OpenOptions};
11 use std::io;
12 use std::path::Path;
13
14 cfg_if! {
15     // We use `flock` rather than `fcntl` on Linux, because WSL1 does not support
16     // `fcntl`-style advisory locks properly (rust-lang/rust#72157).
17     //
18     // For other Unix targets we still use `fcntl` because it's more portable than
19     // `flock`.
20     if #[cfg(target_os = "linux")] {
21         use std::os::unix::prelude::*;
22
23         #[derive(Debug)]
24         pub struct Lock {
25             _file: File,
26         }
27
28         impl Lock {
29             pub fn new(p: &Path,
30                        wait: bool,
31                        create: bool,
32                        exclusive: bool)
33                        -> io::Result<Lock> {
34                 let file = OpenOptions::new()
35                     .read(true)
36                     .write(true)
37                     .create(create)
38                     .mode(libc::S_IRWXU as u32)
39                     .open(p)?;
40
41                 let mut operation = if exclusive {
42                     libc::LOCK_EX
43                 } else {
44                     libc::LOCK_SH
45                 };
46                 if !wait {
47                     operation |= libc::LOCK_NB
48                 }
49
50                 let ret = unsafe { libc::flock(file.as_raw_fd(), operation) };
51                 if ret == -1 {
52                     Err(io::Error::last_os_error())
53                 } else {
54                     Ok(Lock { _file: file })
55                 }
56             }
57
58             pub fn error_unsupported(err: &io::Error) -> bool {
59                 matches!(err.raw_os_error(), Some(libc::ENOTSUP) | Some(libc::ENOSYS))
60             }
61         }
62
63         // Note that we don't need a Drop impl to execute `flock(fd, LOCK_UN)`. Lock acquired by
64         // `flock` is associated with the file descriptor and closing the file release it
65         // automatically.
66     } else if #[cfg(unix)] {
67         use std::mem;
68         use std::os::unix::prelude::*;
69
70         #[derive(Debug)]
71         pub struct Lock {
72             file: File,
73         }
74
75         impl Lock {
76             pub fn new(p: &Path,
77                        wait: bool,
78                        create: bool,
79                        exclusive: bool)
80                        -> io::Result<Lock> {
81                 let file = OpenOptions::new()
82                     .read(true)
83                     .write(true)
84                     .create(create)
85                     .mode(libc::S_IRWXU as u32)
86                     .open(p)?;
87
88                 let lock_type = if exclusive {
89                     libc::F_WRLCK
90                 } else {
91                     libc::F_RDLCK
92                 };
93
94                 let mut flock: libc::flock = unsafe { mem::zeroed() };
95                 flock.l_type = lock_type as libc::c_short;
96                 flock.l_whence = libc::SEEK_SET as libc::c_short;
97                 flock.l_start = 0;
98                 flock.l_len = 0;
99
100                 let cmd = if wait { libc::F_SETLKW } else { libc::F_SETLK };
101                 let ret = unsafe {
102                     libc::fcntl(file.as_raw_fd(), cmd, &flock)
103                 };
104                 if ret == -1 {
105                     Err(io::Error::last_os_error())
106                 } else {
107                     Ok(Lock { file })
108                 }
109             }
110
111             pub fn error_unsupported(err: &io::Error) -> bool {
112                 matches!(err.raw_os_error(), Some(libc::ENOTSUP) | Some(libc::ENOSYS))
113             }
114         }
115
116         impl Drop for Lock {
117             fn drop(&mut self) {
118                 let mut flock: libc::flock = unsafe { mem::zeroed() };
119                 flock.l_type = libc::F_UNLCK as libc::c_short;
120                 flock.l_whence = libc::SEEK_SET as libc::c_short;
121                 flock.l_start = 0;
122                 flock.l_len = 0;
123
124                 unsafe {
125                     libc::fcntl(self.file.as_raw_fd(), libc::F_SETLK, &flock);
126                 }
127             }
128         }
129     } else if #[cfg(windows)] {
130         use std::mem;
131         use std::os::windows::prelude::*;
132
133         use winapi::shared::winerror::ERROR_INVALID_FUNCTION;
134         use winapi::um::minwinbase::{OVERLAPPED, LOCKFILE_FAIL_IMMEDIATELY, LOCKFILE_EXCLUSIVE_LOCK};
135         use winapi::um::fileapi::LockFileEx;
136         use winapi::um::winnt::{FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE};
137
138         #[derive(Debug)]
139         pub struct Lock {
140             _file: File,
141         }
142
143         impl Lock {
144             pub fn new(p: &Path,
145                        wait: bool,
146                        create: bool,
147                        exclusive: bool)
148                        -> io::Result<Lock> {
149                 assert!(p.parent().unwrap().exists(),
150                     "Parent directory of lock-file must exist: {}",
151                     p.display());
152
153                 let share_mode = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE;
154
155                 let mut open_options = OpenOptions::new();
156                 open_options.read(true)
157                             .share_mode(share_mode);
158
159                 if create {
160                     open_options.create(true)
161                                 .write(true);
162                 }
163
164                 debug!("attempting to open lock file `{}`", p.display());
165                 let file = match open_options.open(p) {
166                     Ok(file) => {
167                         debug!("lock file opened successfully");
168                         file
169                     }
170                     Err(err) => {
171                         debug!("error opening lock file: {}", err);
172                         return Err(err)
173                     }
174                 };
175
176                 let ret = unsafe {
177                     let mut overlapped: OVERLAPPED = mem::zeroed();
178
179                     let mut dwFlags = 0;
180                     if !wait {
181                         dwFlags |= LOCKFILE_FAIL_IMMEDIATELY;
182                     }
183
184                     if exclusive {
185                         dwFlags |= LOCKFILE_EXCLUSIVE_LOCK;
186                     }
187
188                     debug!("attempting to acquire lock on lock file `{}`",
189                            p.display());
190                     LockFileEx(file.as_raw_handle(),
191                                dwFlags,
192                                0,
193                                0xFFFF_FFFF,
194                                0xFFFF_FFFF,
195                                &mut overlapped)
196                 };
197                 if ret == 0 {
198                     let err = io::Error::last_os_error();
199                     debug!("failed acquiring file lock: {}", err);
200                     Err(err)
201                 } else {
202                     debug!("successfully acquired lock");
203                     Ok(Lock { _file: file })
204                 }
205             }
206
207             pub fn error_unsupported(err: &io::Error) -> bool {
208                 err.raw_os_error() == Some(ERROR_INVALID_FUNCTION as i32)
209             }
210         }
211
212         // Note that we don't need a Drop impl on the Windows: The file is unlocked
213         // automatically when it's closed.
214     } else {
215         #[derive(Debug)]
216         pub struct Lock(());
217
218         impl Lock {
219             pub fn new(_p: &Path, _wait: bool, _create: bool, _exclusive: bool)
220                 -> io::Result<Lock>
221             {
222                 let msg = "file locks not supported on this platform";
223                 Err(io::Error::new(io::ErrorKind::Other, msg))
224             }
225
226             pub fn error_unsupported(_err: &io::Error) -> bool {
227                 true
228             }
229         }
230     }
231 }