commit
37e7688992
@ -563,6 +563,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"ui_test",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -9,12 +9,12 @@ default-run = "miri"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
test = true # we have unit tests
|
||||
test = true # we have unit tests
|
||||
doctest = false # but no doc tests
|
||||
|
||||
[[bin]]
|
||||
name = "miri"
|
||||
test = false # we have no unit tests
|
||||
test = false # we have no unit tests
|
||||
doctest = false # and no doc tests
|
||||
|
||||
[dependencies]
|
||||
@ -42,6 +42,13 @@ libc = "0.2"
|
||||
libffi = "3.2.0"
|
||||
libloading = "0.8"
|
||||
|
||||
[target.'cfg(target_family = "windows")'.dependencies]
|
||||
windows-sys = { version = "0.52", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_IO",
|
||||
"Win32_Storage_FileSystem",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
colored = "2"
|
||||
ui_test = "0.21.1"
|
||||
|
@ -77,6 +77,14 @@ fn close<'tcx>(
|
||||
throw_unsup_format!("cannot close {}", self.name());
|
||||
}
|
||||
|
||||
fn flock<'tcx>(
|
||||
&self,
|
||||
_communicate_allowed: bool,
|
||||
_op: FlockOp,
|
||||
) -> InterpResult<'tcx, io::Result<()>> {
|
||||
throw_unsup_format!("cannot flock {}", self.name());
|
||||
}
|
||||
|
||||
fn is_tty(&self, _communicate_allowed: bool) -> bool {
|
||||
// Most FDs are not tty's and the consequence of a wrong `false` are minor,
|
||||
// so we use a default impl here.
|
||||
@ -324,6 +332,40 @@ fn dup2(&mut self, old_fd: i32, new_fd: i32) -> InterpResult<'tcx, i32> {
|
||||
Ok(new_fd)
|
||||
}
|
||||
|
||||
fn flock(&mut self, fd: i32, op: i32) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
let Some(file_descriptor) = this.machine.fds.get(fd) else {
|
||||
return Ok(Scalar::from_i32(this.fd_not_found()?));
|
||||
};
|
||||
|
||||
// We need to check that there aren't unsupported options in `op`.
|
||||
let lock_sh = this.eval_libc_i32("LOCK_SH");
|
||||
let lock_ex = this.eval_libc_i32("LOCK_EX");
|
||||
let lock_nb = this.eval_libc_i32("LOCK_NB");
|
||||
let lock_un = this.eval_libc_i32("LOCK_UN");
|
||||
|
||||
use FlockOp::*;
|
||||
let parsed_op = if op == lock_sh {
|
||||
SharedLock { nonblocking: false }
|
||||
} else if op == lock_sh | lock_nb {
|
||||
SharedLock { nonblocking: true }
|
||||
} else if op == lock_ex {
|
||||
ExclusiveLock { nonblocking: false }
|
||||
} else if op == lock_ex | lock_nb {
|
||||
ExclusiveLock { nonblocking: true }
|
||||
} else if op == lock_un {
|
||||
Unlock
|
||||
} else {
|
||||
throw_unsup_format!("unsupported flags {:#x}", op);
|
||||
};
|
||||
|
||||
let result = file_descriptor.flock(this.machine.communicate(), parsed_op)?;
|
||||
drop(file_descriptor);
|
||||
// return `0` if flock is successful
|
||||
let result = result.map(|()| 0i32);
|
||||
Ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
|
||||
}
|
||||
|
||||
fn fcntl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
@ -520,3 +562,10 @@ fn write(
|
||||
this.try_unwrap_io_result(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub(crate) enum FlockOp {
|
||||
SharedLock { nonblocking: bool },
|
||||
ExclusiveLock { nonblocking: bool },
|
||||
Unlock,
|
||||
}
|
||||
|
@ -170,6 +170,13 @@ fn emulate_foreign_item_inner(
|
||||
let result = this.dup2(old_fd, new_fd)?;
|
||||
this.write_scalar(Scalar::from_i32(result), dest)?;
|
||||
}
|
||||
"flock" => {
|
||||
let [fd, op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
|
||||
let fd = this.read_scalar(fd)?.to_i32()?;
|
||||
let op = this.read_scalar(op)?.to_i32()?;
|
||||
let result = this.flock(fd, op)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
// File and file system access
|
||||
"open" | "open64" => {
|
||||
|
@ -16,6 +16,8 @@
|
||||
use crate::*;
|
||||
use shims::time::system_time_to_duration;
|
||||
|
||||
use self::fd::FlockOp;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FileHandle {
|
||||
file: File,
|
||||
@ -127,6 +129,97 @@ fn close<'tcx>(
|
||||
}
|
||||
}
|
||||
|
||||
fn flock<'tcx>(
|
||||
&self,
|
||||
communicate_allowed: bool,
|
||||
op: FlockOp,
|
||||
) -> InterpResult<'tcx, io::Result<()>> {
|
||||
assert!(communicate_allowed, "isolation should have prevented even opening a file");
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
use FlockOp::*;
|
||||
// We always use non-blocking call to prevent interpreter from being blocked
|
||||
let (host_op, lock_nb) = match op {
|
||||
SharedLock { nonblocking } => (libc::LOCK_SH | libc::LOCK_NB, nonblocking),
|
||||
ExclusiveLock { nonblocking } => (libc::LOCK_EX | libc::LOCK_NB, nonblocking),
|
||||
Unlock => (libc::LOCK_UN, false),
|
||||
};
|
||||
|
||||
let fd = self.file.as_raw_fd();
|
||||
let ret = unsafe { libc::flock(fd, host_op) };
|
||||
let res = match ret {
|
||||
0 => Ok(()),
|
||||
-1 => {
|
||||
let err = io::Error::last_os_error();
|
||||
if !lock_nb && err.kind() == io::ErrorKind::WouldBlock {
|
||||
throw_unsup_format!("blocking `flock` is not currently supported");
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
ret => panic!("Unexpected return value from flock: {ret}"),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, HANDLE, TRUE},
|
||||
Storage::FileSystem::{
|
||||
LockFileEx, UnlockFile, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY,
|
||||
},
|
||||
};
|
||||
let fh = self.file.as_raw_handle() as HANDLE;
|
||||
|
||||
use FlockOp::*;
|
||||
let (ret, lock_nb) = match op {
|
||||
SharedLock { nonblocking } | ExclusiveLock { nonblocking } => {
|
||||
// We always use non-blocking call to prevent interpreter from being blocked
|
||||
let mut flags = LOCKFILE_FAIL_IMMEDIATELY;
|
||||
if matches!(op, ExclusiveLock { .. }) {
|
||||
flags |= LOCKFILE_EXCLUSIVE_LOCK;
|
||||
}
|
||||
let ret = unsafe { LockFileEx(fh, flags, 0, !0, !0, &mut std::mem::zeroed()) };
|
||||
(ret, nonblocking)
|
||||
}
|
||||
Unlock => {
|
||||
let ret = unsafe { UnlockFile(fh, 0, 0, !0, !0) };
|
||||
(ret, false)
|
||||
}
|
||||
};
|
||||
|
||||
let res = match ret {
|
||||
TRUE => Ok(()),
|
||||
FALSE => {
|
||||
let mut err = io::Error::last_os_error();
|
||||
let code: u32 = err.raw_os_error().unwrap().try_into().unwrap();
|
||||
if matches!(code, ERROR_IO_PENDING | ERROR_LOCK_VIOLATION) {
|
||||
if lock_nb {
|
||||
// Replace error with a custom WouldBlock error, which later will be
|
||||
// mapped in the `helpers` module
|
||||
let desc = format!("LockFileEx wouldblock error: {err}");
|
||||
err = io::Error::new(io::ErrorKind::WouldBlock, desc);
|
||||
} else {
|
||||
throw_unsup_format!("blocking `flock` is not currently supported");
|
||||
}
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
_ => panic!("Unexpected return value: {ret}"),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_family = "unix", target_family = "windows")))]
|
||||
{
|
||||
let _ = op;
|
||||
compile_error!("flock is supported only on UNIX and Windows hosts");
|
||||
}
|
||||
}
|
||||
|
||||
fn is_tty(&self, communicate_allowed: bool) -> bool {
|
||||
communicate_allowed && self.file.is_terminal()
|
||||
}
|
||||
|
71
src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs
Normal file
71
src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs
Normal file
@ -0,0 +1,71 @@
|
||||
// Flock tests are separate since they don't in general work on a Windows host.
|
||||
//@ignore-target-windows: File handling is not implemented yet
|
||||
//@compile-flags: -Zmiri-disable-isolation
|
||||
|
||||
use std::{fs::File, io::Error, os::fd::AsRawFd};
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
mod utils;
|
||||
|
||||
fn main() {
|
||||
let bytes = b"Hello, World!\n";
|
||||
let path = utils::prepare_with_content("miri_test_fs_shared_lock.txt", bytes);
|
||||
|
||||
let files: Vec<File> = (0..3).map(|_| File::open(&path).unwrap()).collect();
|
||||
|
||||
// Test that we can apply many shared locks
|
||||
for file in files.iter() {
|
||||
let fd = file.as_raw_fd();
|
||||
let ret = unsafe { libc::flock(fd, libc::LOCK_SH) };
|
||||
if ret != 0 {
|
||||
panic!("flock error: {}", Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
// Test that shared lock prevents exclusive lock
|
||||
{
|
||||
let fd = files[0].as_raw_fd();
|
||||
let ret = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
|
||||
assert_eq!(ret, -1);
|
||||
let err = Error::last_os_error().raw_os_error().unwrap();
|
||||
assert_eq!(err, libc::EWOULDBLOCK);
|
||||
}
|
||||
|
||||
// Unlock shared lock
|
||||
for file in files.iter() {
|
||||
let fd = file.as_raw_fd();
|
||||
let ret = unsafe { libc::flock(fd, libc::LOCK_UN) };
|
||||
if ret != 0 {
|
||||
panic!("flock error: {}", Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
// Take exclusive lock
|
||||
{
|
||||
let fd = files[0].as_raw_fd();
|
||||
let ret = unsafe { libc::flock(fd, libc::LOCK_EX) };
|
||||
assert_eq!(ret, 0);
|
||||
}
|
||||
|
||||
// Test that shared lock prevents exclusive and shared locks
|
||||
{
|
||||
let fd = files[1].as_raw_fd();
|
||||
let ret = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
|
||||
assert_eq!(ret, -1);
|
||||
let err = Error::last_os_error().raw_os_error().unwrap();
|
||||
assert_eq!(err, libc::EWOULDBLOCK);
|
||||
|
||||
let fd = files[2].as_raw_fd();
|
||||
let ret = unsafe { libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB) };
|
||||
assert_eq!(ret, -1);
|
||||
let err = Error::last_os_error().raw_os_error().unwrap();
|
||||
assert_eq!(err, libc::EWOULDBLOCK);
|
||||
}
|
||||
|
||||
// Unlock exclusive lock
|
||||
{
|
||||
let fd = files[0].as_raw_fd();
|
||||
let ret = unsafe { libc::flock(fd, libc::LOCK_UN) };
|
||||
assert_eq!(ret, 0);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user