Skip to content

Commit 3fbdc82

Browse files
committed
Auto merge of #3759 - newpavlov:flock, r=oli-obk
Add `flock` shim
2 parents 2d1e563 + ed3ae03 commit 3fbdc82

File tree

6 files changed

+230
-2
lines changed

6 files changed

+230
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ default-run = "miri"
99
edition = "2021"
1010

1111
[lib]
12-
test = true # we have unit tests
12+
test = true # we have unit tests
1313
doctest = false # but no doc tests
1414

1515
[[bin]]
1616
name = "miri"
17-
test = false # we have no unit tests
17+
test = false # we have no unit tests
1818
doctest = false # and no doc tests
1919

2020
[dependencies]
@@ -42,6 +42,13 @@ libc = "0.2"
4242
libffi = "3.2.0"
4343
libloading = "0.8"
4444

45+
[target.'cfg(target_family = "windows")'.dependencies]
46+
windows-sys = { version = "0.52", features = [
47+
"Win32_Foundation",
48+
"Win32_System_IO",
49+
"Win32_Storage_FileSystem",
50+
] }
51+
4552
[dev-dependencies]
4653
colored = "2"
4754
ui_test = "0.21.1"

src/shims/unix/fd.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ pub trait FileDescription: std::fmt::Debug + Any {
7777
throw_unsup_format!("cannot close {}", self.name());
7878
}
7979

80+
fn flock<'tcx>(
81+
&self,
82+
_communicate_allowed: bool,
83+
_op: FlockOp,
84+
) -> InterpResult<'tcx, io::Result<()>> {
85+
throw_unsup_format!("cannot flock {}", self.name());
86+
}
87+
8088
fn is_tty(&self, _communicate_allowed: bool) -> bool {
8189
// Most FDs are not tty's and the consequence of a wrong `false` are minor,
8290
// so we use a default impl here.
@@ -324,6 +332,40 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
324332
Ok(new_fd)
325333
}
326334

335+
fn flock(&mut self, fd: i32, op: i32) -> InterpResult<'tcx, Scalar> {
336+
let this = self.eval_context_mut();
337+
let Some(file_descriptor) = this.machine.fds.get(fd) else {
338+
return Ok(Scalar::from_i32(this.fd_not_found()?));
339+
};
340+
341+
// We need to check that there aren't unsupported options in `op`.
342+
let lock_sh = this.eval_libc_i32("LOCK_SH");
343+
let lock_ex = this.eval_libc_i32("LOCK_EX");
344+
let lock_nb = this.eval_libc_i32("LOCK_NB");
345+
let lock_un = this.eval_libc_i32("LOCK_UN");
346+
347+
use FlockOp::*;
348+
let parsed_op = if op == lock_sh {
349+
SharedLock { nonblocking: false }
350+
} else if op == lock_sh | lock_nb {
351+
SharedLock { nonblocking: true }
352+
} else if op == lock_ex {
353+
ExclusiveLock { nonblocking: false }
354+
} else if op == lock_ex | lock_nb {
355+
ExclusiveLock { nonblocking: true }
356+
} else if op == lock_un {
357+
Unlock
358+
} else {
359+
throw_unsup_format!("unsupported flags {:#x}", op);
360+
};
361+
362+
let result = file_descriptor.flock(this.machine.communicate(), parsed_op)?;
363+
drop(file_descriptor);
364+
// return `0` if flock is successful
365+
let result = result.map(|()| 0i32);
366+
Ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
367+
}
368+
327369
fn fcntl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, i32> {
328370
let this = self.eval_context_mut();
329371

@@ -520,3 +562,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
520562
this.try_unwrap_io_result(result)
521563
}
522564
}
565+
566+
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
567+
pub(crate) enum FlockOp {
568+
SharedLock { nonblocking: bool },
569+
ExclusiveLock { nonblocking: bool },
570+
Unlock,
571+
}

src/shims/unix/foreign_items.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
170170
let result = this.dup2(old_fd, new_fd)?;
171171
this.write_scalar(Scalar::from_i32(result), dest)?;
172172
}
173+
"flock" => {
174+
let [fd, op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
175+
let fd = this.read_scalar(fd)?.to_i32()?;
176+
let op = this.read_scalar(op)?.to_i32()?;
177+
let result = this.flock(fd, op)?;
178+
this.write_scalar(result, dest)?;
179+
}
173180

174181
// File and file system access
175182
"open" | "open64" => {

src/shims/unix/fs.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use crate::shims::unix::*;
1616
use crate::*;
1717
use shims::time::system_time_to_duration;
1818

19+
use self::fd::FlockOp;
20+
1921
#[derive(Debug)]
2022
struct FileHandle {
2123
file: File,
@@ -127,6 +129,97 @@ impl FileDescription for FileHandle {
127129
}
128130
}
129131

132+
fn flock<'tcx>(
133+
&self,
134+
communicate_allowed: bool,
135+
op: FlockOp,
136+
) -> InterpResult<'tcx, io::Result<()>> {
137+
assert!(communicate_allowed, "isolation should have prevented even opening a file");
138+
#[cfg(target_family = "unix")]
139+
{
140+
use std::os::fd::AsRawFd;
141+
142+
use FlockOp::*;
143+
// We always use non-blocking call to prevent interpreter from being blocked
144+
let (host_op, lock_nb) = match op {
145+
SharedLock { nonblocking } => (libc::LOCK_SH | libc::LOCK_NB, nonblocking),
146+
ExclusiveLock { nonblocking } => (libc::LOCK_EX | libc::LOCK_NB, nonblocking),
147+
Unlock => (libc::LOCK_UN, false),
148+
};
149+
150+
let fd = self.file.as_raw_fd();
151+
let ret = unsafe { libc::flock(fd, host_op) };
152+
let res = match ret {
153+
0 => Ok(()),
154+
-1 => {
155+
let err = io::Error::last_os_error();
156+
if !lock_nb && err.kind() == io::ErrorKind::WouldBlock {
157+
throw_unsup_format!("blocking `flock` is not currently supported");
158+
}
159+
Err(err)
160+
}
161+
ret => panic!("Unexpected return value from flock: {ret}"),
162+
};
163+
Ok(res)
164+
}
165+
166+
#[cfg(target_family = "windows")]
167+
{
168+
use std::os::windows::io::AsRawHandle;
169+
use windows_sys::Win32::{
170+
Foundation::{ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, HANDLE, TRUE},
171+
Storage::FileSystem::{
172+
LockFileEx, UnlockFile, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY,
173+
},
174+
};
175+
let fh = self.file.as_raw_handle() as HANDLE;
176+
177+
use FlockOp::*;
178+
let (ret, lock_nb) = match op {
179+
SharedLock { nonblocking } | ExclusiveLock { nonblocking } => {
180+
// We always use non-blocking call to prevent interpreter from being blocked
181+
let mut flags = LOCKFILE_FAIL_IMMEDIATELY;
182+
if matches!(op, ExclusiveLock { .. }) {
183+
flags |= LOCKFILE_EXCLUSIVE_LOCK;
184+
}
185+
let ret = unsafe { LockFileEx(fh, flags, 0, !0, !0, &mut std::mem::zeroed()) };
186+
(ret, nonblocking)
187+
}
188+
Unlock => {
189+
let ret = unsafe { UnlockFile(fh, 0, 0, !0, !0) };
190+
(ret, false)
191+
}
192+
};
193+
194+
let res = match ret {
195+
TRUE => Ok(()),
196+
FALSE => {
197+
let mut err = io::Error::last_os_error();
198+
let code: u32 = err.raw_os_error().unwrap().try_into().unwrap();
199+
if matches!(code, ERROR_IO_PENDING | ERROR_LOCK_VIOLATION) {
200+
if lock_nb {
201+
// Replace error with a custom WouldBlock error, which later will be
202+
// mapped in the `helpers` module
203+
let desc = format!("LockFileEx wouldblock error: {err}");
204+
err = io::Error::new(io::ErrorKind::WouldBlock, desc);
205+
} else {
206+
throw_unsup_format!("blocking `flock` is not currently supported");
207+
}
208+
}
209+
Err(err)
210+
}
211+
_ => panic!("Unexpected return value: {ret}"),
212+
};
213+
Ok(res)
214+
}
215+
216+
#[cfg(not(any(target_family = "unix", target_family = "windows")))]
217+
{
218+
let _ = op;
219+
compile_error!("flock is supported only on UNIX and Windows hosts");
220+
}
221+
}
222+
130223
fn is_tty(&self, communicate_allowed: bool) -> bool {
131224
communicate_allowed && self.file.is_terminal()
132225
}

tests/pass-dep/libc/libc-fs-flock.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Flock tests are separate since they don't in general work on a Windows host.
2+
//@ignore-target-windows: File handling is not implemented yet
3+
//@compile-flags: -Zmiri-disable-isolation
4+
5+
use std::{fs::File, io::Error, os::fd::AsRawFd};
6+
7+
#[path = "../../utils/mod.rs"]
8+
mod utils;
9+
10+
fn main() {
11+
let bytes = b"Hello, World!\n";
12+
let path = utils::prepare_with_content("miri_test_fs_shared_lock.txt", bytes);
13+
14+
let files: Vec<File> = (0..3).map(|_| File::open(&path).unwrap()).collect();
15+
16+
// Test that we can apply many shared locks
17+
for file in files.iter() {
18+
let fd = file.as_raw_fd();
19+
let ret = unsafe { libc::flock(fd, libc::LOCK_SH) };
20+
if ret != 0 {
21+
panic!("flock error: {}", Error::last_os_error());
22+
}
23+
}
24+
25+
// Test that shared lock prevents exclusive lock
26+
{
27+
let fd = files[0].as_raw_fd();
28+
let ret = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
29+
assert_eq!(ret, -1);
30+
let err = Error::last_os_error().raw_os_error().unwrap();
31+
assert_eq!(err, libc::EWOULDBLOCK);
32+
}
33+
34+
// Unlock shared lock
35+
for file in files.iter() {
36+
let fd = file.as_raw_fd();
37+
let ret = unsafe { libc::flock(fd, libc::LOCK_UN) };
38+
if ret != 0 {
39+
panic!("flock error: {}", Error::last_os_error());
40+
}
41+
}
42+
43+
// Take exclusive lock
44+
{
45+
let fd = files[0].as_raw_fd();
46+
let ret = unsafe { libc::flock(fd, libc::LOCK_EX) };
47+
assert_eq!(ret, 0);
48+
}
49+
50+
// Test that shared lock prevents exclusive and shared locks
51+
{
52+
let fd = files[1].as_raw_fd();
53+
let ret = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
54+
assert_eq!(ret, -1);
55+
let err = Error::last_os_error().raw_os_error().unwrap();
56+
assert_eq!(err, libc::EWOULDBLOCK);
57+
58+
let fd = files[2].as_raw_fd();
59+
let ret = unsafe { libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB) };
60+
assert_eq!(ret, -1);
61+
let err = Error::last_os_error().raw_os_error().unwrap();
62+
assert_eq!(err, libc::EWOULDBLOCK);
63+
}
64+
65+
// Unlock exclusive lock
66+
{
67+
let fd = files[0].as_raw_fd();
68+
let ret = unsafe { libc::flock(fd, libc::LOCK_UN) };
69+
assert_eq!(ret, 0);
70+
}
71+
}

0 commit comments

Comments
 (0)