Implement trivial file operations - opening and closing handles. Just enough to get file metadata.

This commit is contained in:
Rune Tynan 2024-12-01 19:33:41 -08:00
parent 92e6f08fad
commit 209ce59195
18 changed files with 838 additions and 160 deletions

View file

@ -538,6 +538,7 @@ name = "miri"
version = "0.1.0"
dependencies = [
"aes",
"bitflags",
"chrono",
"chrono-tz",
"colored",

View file

@ -26,6 +26,7 @@ measureme = "12"
chrono = { version = "0.4.38", default-features = false }
chrono-tz = "0.10"
directories = "6"
bitflags = "2.6"
# Copied from `compiler/rustc/Cargo.toml`.
# But only for some targets, it fails for others. Rustc configures this in its CI, but we can't

View file

@ -1,6 +1,7 @@
use std::any::Any;
use std::collections::BTreeMap;
use std::io::{IsTerminal, SeekFrom, Write};
use std::fs::{File, Metadata};
use std::io::{IsTerminal, Seek, SeekFrom, Write};
use std::marker::CoercePointee;
use std::ops::Deref;
use std::rc::{Rc, Weak};
@ -192,7 +193,7 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
false
}
fn as_unix(&self) -> &dyn UnixFileDescription {
fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
panic!("Not a unix file descriptor: {}", self.name());
}
}
@ -278,6 +279,97 @@ impl FileDescription for io::Stderr {
}
}
#[derive(Debug)]
pub struct FileHandle {
pub(crate) file: File,
pub(crate) writable: bool,
}
impl FileDescription for FileHandle {
fn name(&self) -> &'static str {
"file"
}
fn read<'tcx>(
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ptr: Pointer,
len: usize,
ecx: &mut MiriInterpCx<'tcx>,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
assert!(communicate_allowed, "isolation should have prevented even opening a file");
let result = ecx.read_from_host(&self.file, len, ptr)?;
finish.call(ecx, result)
}
fn write<'tcx>(
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ptr: Pointer,
len: usize,
ecx: &mut MiriInterpCx<'tcx>,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
assert!(communicate_allowed, "isolation should have prevented even opening a file");
let result = ecx.write_to_host(&self.file, len, ptr)?;
finish.call(ecx, result)
}
fn seek<'tcx>(
&self,
communicate_allowed: bool,
offset: SeekFrom,
) -> InterpResult<'tcx, io::Result<u64>> {
assert!(communicate_allowed, "isolation should have prevented even opening a file");
interp_ok((&mut &self.file).seek(offset))
}
fn close<'tcx>(
self,
communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
assert!(communicate_allowed, "isolation should have prevented even opening a file");
// We sync the file if it was opened in a mode different than read-only.
if self.writable {
// `File::sync_all` does the checks that are done when closing a file. We do this to
// to handle possible errors correctly.
let result = self.file.sync_all();
// Now we actually close the file and return the result.
drop(self.file);
interp_ok(result)
} else {
// We drop the file, this closes it but ignores any errors
// produced when closing it. This is done because
// `File::sync_all` cannot be done over files like
// `/dev/urandom` which are read-only. Check
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
// for a deeper discussion.
drop(self.file);
interp_ok(Ok(()))
}
}
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
interp_ok(self.file.metadata())
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.file.is_terminal()
}
fn as_unix<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
assert!(
ecx.target_os_is_unix(),
"unix file operations are only available for unix targets"
);
self
}
}
/// Like /dev/null
#[derive(Debug)]
pub struct NullOutput;
@ -300,10 +392,13 @@ impl FileDescription for NullOutput {
}
}
/// Internal type of a file-descriptor - this is what [`FdTable`] expects
pub type FdNum = i32;
/// The file descriptor table
#[derive(Debug)]
pub struct FdTable {
pub fds: BTreeMap<i32, DynFileDescriptionRef>,
pub fds: BTreeMap<FdNum, DynFileDescriptionRef>,
/// Unique identifier for file description, used to differentiate between various file description.
next_file_description_id: FdId,
}
@ -339,12 +434,12 @@ impl FdTable {
}
/// Insert a new file description to the FdTable.
pub fn insert_new(&mut self, fd: impl FileDescription) -> i32 {
pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
let fd_ref = self.new_ref(fd);
self.insert(fd_ref)
}
pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> i32 {
pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
self.insert_with_min_num(fd_ref, 0)
}
@ -352,8 +447,8 @@ impl FdTable {
pub fn insert_with_min_num(
&mut self,
file_handle: DynFileDescriptionRef,
min_fd_num: i32,
) -> i32 {
min_fd_num: FdNum,
) -> FdNum {
// Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
// between used FDs, the find_map combinator will return it. If the first such unused FD
// is after all other used FDs, the find_map combinator will return None, and we will use
@ -379,16 +474,16 @@ impl FdTable {
new_fd_num
}
pub fn get(&self, fd_num: i32) -> Option<DynFileDescriptionRef> {
pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
let fd = self.fds.get(&fd_num)?;
Some(fd.clone())
}
pub fn remove(&mut self, fd_num: i32) -> Option<DynFileDescriptionRef> {
pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
self.fds.remove(&fd_num)
}
pub fn is_fd_num(&self, fd_num: i32) -> bool {
pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
self.fds.contains_key(&fd_num)
}
}

View file

@ -219,16 +219,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let filetime = this.deref_pointer_as(LPFILETIME_op, this.windows_ty_layout("FILETIME"))?;
let NANOS_PER_SEC = this.eval_windows_u64("time", "NANOS_PER_SEC");
let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC");
let INTERVALS_TO_UNIX_EPOCH = this.eval_windows_u64("time", "INTERVALS_TO_UNIX_EPOCH");
let NANOS_PER_INTERVAL = NANOS_PER_SEC / INTERVALS_PER_SEC;
let SECONDS_TO_UNIX_EPOCH = INTERVALS_TO_UNIX_EPOCH / INTERVALS_PER_SEC;
let duration = system_time_to_duration(&SystemTime::now())?
+ Duration::from_secs(SECONDS_TO_UNIX_EPOCH);
let duration_ticks = u64::try_from(duration.as_nanos() / u128::from(NANOS_PER_INTERVAL))
.map_err(|_| err_unsup_format!("programs running more than 2^64 Windows ticks after the Windows epoch are not supported"))?;
let duration = this.system_time_since_windows_epoch(&SystemTime::now())?;
let duration_ticks = this.windows_ticks_for(duration)?;
let dwLowDateTime = u32::try_from(duration_ticks & 0x00000000FFFFFFFF).unwrap();
let dwHighDateTime = u32::try_from((duration_ticks & 0xFFFFFFFF00000000) >> 32).unwrap();
@ -281,6 +273,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(Scalar::from_i32(-1)) // Return non-zero on success
}
#[allow(non_snake_case, clippy::arithmetic_side_effects)]
fn system_time_since_windows_epoch(&self, time: &SystemTime) -> InterpResult<'tcx, Duration> {
let this = self.eval_context_ref();
let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC");
let INTERVALS_TO_UNIX_EPOCH = this.eval_windows_u64("time", "INTERVALS_TO_UNIX_EPOCH");
let SECONDS_TO_UNIX_EPOCH = INTERVALS_TO_UNIX_EPOCH / INTERVALS_PER_SEC;
interp_ok(system_time_to_duration(time)? + Duration::from_secs(SECONDS_TO_UNIX_EPOCH))
}
#[allow(non_snake_case, clippy::arithmetic_side_effects)]
fn windows_ticks_for(&self, duration: Duration) -> InterpResult<'tcx, u64> {
let this = self.eval_context_ref();
let NANOS_PER_SEC = this.eval_windows_u64("time", "NANOS_PER_SEC");
let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC");
let NANOS_PER_INTERVAL = NANOS_PER_SEC / INTERVALS_PER_SEC;
let ticks = u64::try_from(duration.as_nanos() / u128::from(NANOS_PER_INTERVAL))
.map_err(|_| err_unsup_format!("programs running more than 2^64 Windows ticks after the Windows epoch are not supported"))?;
interp_ok(ticks)
}
fn mach_absolute_time(&self) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_ref();

View file

@ -121,7 +121,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
throw_unsup_format!("unsupported flags {:#x}", op);
};
let result = fd.as_unix().flock(this.machine.communicate(), parsed_op)?;
let result = fd.as_unix(this).flock(this.machine.communicate(), parsed_op)?;
// return `0` if flock is successful
let result = result.map(|()| 0i32);
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
@ -273,7 +273,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let Ok(offset) = u64::try_from(offset) else {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
};
fd.as_unix().pread(communicate, offset, buf, count, this, finish)?
fd.as_unix(this).pread(communicate, offset, buf, count, this, finish)?
}
};
interp_ok(())
@ -333,7 +333,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let Ok(offset) = u64::try_from(offset) else {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
};
fd.as_unix().pwrite(communicate, buf, count, offset, this, finish)?
fd.as_unix(this).pwrite(communicate, buf, count, offset, this, finish)?
}
};
interp_ok(())

View file

@ -2,10 +2,9 @@
use std::borrow::Cow;
use std::fs::{
DirBuilder, File, FileType, Metadata, OpenOptions, ReadDir, read_dir, remove_dir, remove_file,
rename,
DirBuilder, File, FileType, OpenOptions, ReadDir, read_dir, remove_dir, remove_file, rename,
};
use std::io::{self, ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write};
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
@ -14,98 +13,11 @@ use rustc_data_structures::fx::FxHashMap;
use self::shims::time::system_time_to_duration;
use crate::helpers::check_min_vararg_count;
use crate::shims::files::{EvalContextExt as _, FileDescription, FileDescriptionRef};
use crate::shims::files::FileHandle;
use crate::shims::os_str::bytes_to_os_str;
use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
use crate::*;
#[derive(Debug)]
struct FileHandle {
file: File,
writable: bool,
}
impl FileDescription for FileHandle {
fn name(&self) -> &'static str {
"file"
}
fn read<'tcx>(
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ptr: Pointer,
len: usize,
ecx: &mut MiriInterpCx<'tcx>,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
assert!(communicate_allowed, "isolation should have prevented even opening a file");
let result = ecx.read_from_host(&self.file, len, ptr)?;
finish.call(ecx, result)
}
fn write<'tcx>(
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ptr: Pointer,
len: usize,
ecx: &mut MiriInterpCx<'tcx>,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
assert!(communicate_allowed, "isolation should have prevented even opening a file");
let result = ecx.write_to_host(&self.file, len, ptr)?;
finish.call(ecx, result)
}
fn seek<'tcx>(
&self,
communicate_allowed: bool,
offset: SeekFrom,
) -> InterpResult<'tcx, io::Result<u64>> {
assert!(communicate_allowed, "isolation should have prevented even opening a file");
interp_ok((&mut &self.file).seek(offset))
}
fn close<'tcx>(
self,
communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
assert!(communicate_allowed, "isolation should have prevented even opening a file");
// We sync the file if it was opened in a mode different than read-only.
if self.writable {
// `File::sync_all` does the checks that are done when closing a file. We do this to
// to handle possible errors correctly.
let result = self.file.sync_all();
// Now we actually close the file and return the result.
drop(self.file);
interp_ok(result)
} else {
// We drop the file, this closes it but ignores any errors
// produced when closing it. This is done because
// `File::sync_all` cannot be done over files like
// `/dev/urandom` which are read-only. Check
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
// for a deeper discussion.
drop(self.file);
interp_ok(Ok(()))
}
}
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
interp_ok(self.file.metadata())
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.file.is_terminal()
}
fn as_unix(&self) -> &dyn UnixFileDescription {
self
}
}
impl UnixFileDescription for FileHandle {
fn pread<'tcx>(
&self,

View file

@ -153,7 +153,7 @@ impl FileDescription for Epoll {
interp_ok(Ok(()))
}
fn as_unix(&self) -> &dyn UnixFileDescription {
fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
self
}
}
@ -590,7 +590,7 @@ fn check_and_update_one_event_interest<'tcx>(
ecx: &MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, bool> {
// Get the bitmask of ready events for a file description.
let ready_events_bitmask = fd_ref.as_unix().get_epoll_ready_events()?.get_event_bitmask(ecx);
let ready_events_bitmask = fd_ref.as_unix(ecx).get_epoll_ready_events()?.get_event_bitmask(ecx);
let epoll_event_interest = interest.borrow();
let epfd = epoll_event_interest.weak_epfd.upgrade().unwrap();
// This checks if any of the events specified in epoll_event_interest.events

View file

@ -100,7 +100,7 @@ impl FileDescription for EventFd {
eventfd_write(buf_place, self, ecx, finish)
}
fn as_unix(&self) -> &dyn UnixFileDescription {
fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
self
}
}

View file

@ -107,7 +107,7 @@ impl FileDescription for AnonSocket {
anonsocket_write(self, ptr, len, ecx, finish)
}
fn as_unix(&self) -> &dyn UnixFileDescription {
fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
self
}
}

View file

@ -241,6 +241,32 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
};
this.write_scalar(result, dest)?;
}
"CreateFileW" => {
let [
file_name,
desired_access,
share_mode,
security_attributes,
creation_disposition,
flags_and_attributes,
template_file,
] = this.check_shim(abi, sys_conv, link_name, args)?;
let handle = this.CreateFileW(
file_name,
desired_access,
share_mode,
security_attributes,
creation_disposition,
flags_and_attributes,
template_file,
)?;
this.write_scalar(handle.to_scalar(this), dest)?;
}
"GetFileInformationByHandle" => {
let [handle, info] = this.check_shim(abi, sys_conv, link_name, args)?;
let res = this.GetFileInformationByHandle(handle, info)?;
this.write_scalar(res, dest)?;
}
// Allocation
"HeapAlloc" => {
@ -493,7 +519,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"SetThreadDescription" => {
let [handle, name] = this.check_shim(abi, sys_conv, link_name, args)?;
let handle = this.read_handle(handle)?;
let handle = this.read_handle(handle, "SetThreadDescription")?;
let name = this.read_wide_str(this.read_pointer(name)?)?;
let thread = match handle {
@ -508,7 +534,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"GetThreadDescription" => {
let [handle, name_ptr] = this.check_shim(abi, sys_conv, link_name, args)?;
let handle = this.read_handle(handle)?;
let handle = this.read_handle(handle, "GetThreadDescription")?;
let name_ptr = this.deref_pointer_as(name_ptr, this.machine.layouts.mut_raw_ptr)?; // the pointer where we should store the ptr to the name
let thread = match handle {
@ -618,7 +644,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let [handle, filename, size] = this.check_shim(abi, sys_conv, link_name, args)?;
this.check_no_isolation("`GetModuleFileNameW`")?;
let handle = this.read_handle(handle)?;
let handle = this.read_handle(handle, "GetModuleFileNameW")?;
let filename = this.read_pointer(filename)?;
let size = this.read_scalar(size)?.to_u32()?;

View file

@ -0,0 +1,402 @@
use std::fs::{Metadata, OpenOptions};
use std::io;
use std::path::PathBuf;
use std::time::SystemTime;
use bitflags::bitflags;
use crate::shims::files::{FileDescription, FileHandle};
use crate::shims::windows::handle::{EvalContextExt as _, Handle};
use crate::*;
#[derive(Debug)]
pub struct DirHandle {
pub(crate) path: PathBuf,
}
impl FileDescription for DirHandle {
fn name(&self) -> &'static str {
"directory"
}
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
interp_ok(self.path.metadata())
}
fn close<'tcx>(
self,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
interp_ok(Ok(()))
}
}
/// Windows supports handles without any read/write/delete permissions - these handles can get
/// metadata, but little else. We represent that by storing the metadata from the time the handle
/// was opened.
#[derive(Debug)]
pub struct MetadataHandle {
pub(crate) meta: Metadata,
}
impl FileDescription for MetadataHandle {
fn name(&self) -> &'static str {
"metadata-only"
}
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
interp_ok(Ok(self.meta.clone()))
}
fn close<'tcx>(
self,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
interp_ok(Ok(()))
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
enum CreationDisposition {
CreateAlways,
CreateNew,
OpenAlways,
OpenExisting,
TruncateExisting,
}
impl CreationDisposition {
fn new<'tcx>(
value: u32,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, CreationDisposition> {
let create_always = ecx.eval_windows_u32("c", "CREATE_ALWAYS");
let create_new = ecx.eval_windows_u32("c", "CREATE_NEW");
let open_always = ecx.eval_windows_u32("c", "OPEN_ALWAYS");
let open_existing = ecx.eval_windows_u32("c", "OPEN_EXISTING");
let truncate_existing = ecx.eval_windows_u32("c", "TRUNCATE_EXISTING");
let out = if value == create_always {
CreationDisposition::CreateAlways
} else if value == create_new {
CreationDisposition::CreateNew
} else if value == open_always {
CreationDisposition::OpenAlways
} else if value == open_existing {
CreationDisposition::OpenExisting
} else if value == truncate_existing {
CreationDisposition::TruncateExisting
} else {
throw_unsup_format!("CreateFileW: Unsupported creation disposition: {value}");
};
interp_ok(out)
}
}
bitflags! {
#[derive(PartialEq)]
struct FileAttributes: u32 {
const ZERO = 0;
const NORMAL = 1 << 0;
/// This must be passed to allow getting directory handles. If not passed, we error on trying
/// to open directories
const BACKUP_SEMANTICS = 1 << 1;
/// Open a reparse point as a regular file - this is basically similar to 'readlink' in Unix
/// terminology. A reparse point is a file with custom logic when navigated to, of which
/// a symlink is one specific example.
const OPEN_REPARSE = 1 << 2;
}
}
impl FileAttributes {
fn new<'tcx>(
mut value: u32,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, FileAttributes> {
let file_attribute_normal = ecx.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL");
let file_flag_backup_semantics = ecx.eval_windows_u32("c", "FILE_FLAG_BACKUP_SEMANTICS");
let file_flag_open_reparse_point =
ecx.eval_windows_u32("c", "FILE_FLAG_OPEN_REPARSE_POINT");
let mut out = FileAttributes::ZERO;
if value & file_flag_backup_semantics != 0 {
value &= !file_flag_backup_semantics;
out |= FileAttributes::BACKUP_SEMANTICS;
}
if value & file_flag_open_reparse_point != 0 {
value &= !file_flag_open_reparse_point;
out |= FileAttributes::OPEN_REPARSE;
}
if value & file_attribute_normal != 0 {
value &= !file_attribute_normal;
out |= FileAttributes::NORMAL;
}
if value != 0 {
throw_unsup_format!("CreateFileW: Unsupported flags_and_attributes: {value}");
}
if out == FileAttributes::ZERO {
// NORMAL is equivalent to 0. Avoid needing to check both cases by unifying the two.
out = FileAttributes::NORMAL;
}
interp_ok(out)
}
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
#[allow(non_snake_case)]
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn CreateFileW(
&mut self,
file_name: &OpTy<'tcx>, // LPCWSTR
desired_access: &OpTy<'tcx>, // DWORD
share_mode: &OpTy<'tcx>, // DWORD
security_attributes: &OpTy<'tcx>, // LPSECURITY_ATTRIBUTES
creation_disposition: &OpTy<'tcx>, // DWORD
flags_and_attributes: &OpTy<'tcx>, // DWORD
template_file: &OpTy<'tcx>, // HANDLE
) -> InterpResult<'tcx, Handle> {
// ^ Returns HANDLE
use CreationDisposition::*;
let this = self.eval_context_mut();
this.assert_target_os("windows", "CreateFileW");
this.check_no_isolation("`CreateFileW`")?;
// This function appears to always set the error to 0. This is important for some flag
// combinations, which may set error code on success.
this.set_last_error(IoError::Raw(Scalar::from_i32(0)))?;
let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
let mut desired_access = this.read_scalar(desired_access)?.to_u32()?;
let share_mode = this.read_scalar(share_mode)?.to_u32()?;
let security_attributes = this.read_pointer(security_attributes)?;
let creation_disposition = this.read_scalar(creation_disposition)?.to_u32()?;
let flags_and_attributes = this.read_scalar(flags_and_attributes)?.to_u32()?;
let template_file = this.read_target_usize(template_file)?;
let generic_read = this.eval_windows_u32("c", "GENERIC_READ");
let generic_write = this.eval_windows_u32("c", "GENERIC_WRITE");
let file_share_delete = this.eval_windows_u32("c", "FILE_SHARE_DELETE");
let file_share_read = this.eval_windows_u32("c", "FILE_SHARE_READ");
let file_share_write = this.eval_windows_u32("c", "FILE_SHARE_WRITE");
let creation_disposition = CreationDisposition::new(creation_disposition, this)?;
let attributes = FileAttributes::new(flags_and_attributes, this)?;
if share_mode != (file_share_delete | file_share_read | file_share_write) {
throw_unsup_format!("CreateFileW: Unsupported share mode: {share_mode}");
}
if !this.ptr_is_null(security_attributes)? {
throw_unsup_format!("CreateFileW: Security attributes are not supported");
}
if attributes.contains(FileAttributes::OPEN_REPARSE) && creation_disposition == CreateAlways
{
throw_machine_stop!(TerminationInfo::Abort("Invalid CreateFileW argument combination: FILE_FLAG_OPEN_REPARSE_POINT with CREATE_ALWAYS".to_string()));
}
if template_file != 0 {
throw_unsup_format!("CreateFileW: Template files are not supported");
}
// We need to know if the file is a directory to correctly open directory handles.
// This is racy, but currently the stdlib doesn't appear to offer a better solution.
let is_dir = file_name.is_dir();
// BACKUP_SEMANTICS is how Windows calls the act of opening a directory handle.
if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) && is_dir {
this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
return interp_ok(Handle::Invalid);
}
let desired_read = desired_access & generic_read != 0;
let desired_write = desired_access & generic_write != 0;
let mut options = OpenOptions::new();
if desired_read {
desired_access &= !generic_read;
options.read(true);
}
if desired_write {
desired_access &= !generic_write;
options.write(true);
}
if desired_access != 0 {
throw_unsup_format!(
"CreateFileW: Unsupported bits set for access mode: {desired_access:#x}"
);
}
// Per the documentation:
// If the specified file exists and is writable, the function truncates the file,
// the function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS.
// If the specified file does not exist and is a valid path, a new file is created,
// the function succeeds, and the last-error code is set to zero.
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
//
// This is racy, but there doesn't appear to be an std API that both succeeds if a
// file exists but tells us it isn't new. Either we accept racing one way or another,
// or we use an iffy heuristic like file creation time. This implementation prefers
// to fail in the direction of erroring more often.
if let CreateAlways | OpenAlways = creation_disposition
&& file_name.exists()
{
this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
}
let handle = if is_dir {
// Open this as a directory.
let fd_num = this.machine.fds.insert_new(DirHandle { path: file_name });
Ok(Handle::File(fd_num))
} else if creation_disposition == OpenExisting && !(desired_read || desired_write) {
// Windows supports handles with no permissions. These allow things such as reading
// metadata, but not file content.
file_name.metadata().map(|meta| {
let fd_num = this.machine.fds.insert_new(MetadataHandle { meta });
Handle::File(fd_num)
})
} else {
// Open this as a standard file.
match creation_disposition {
CreateAlways | OpenAlways => {
options.create(true);
if creation_disposition == CreateAlways {
options.truncate(true);
}
}
CreateNew => {
options.create_new(true);
// Per `create_new` documentation:
// The file must be opened with write or append access in order to create a new file.
// https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new
if !desired_write {
options.append(true);
}
}
OpenExisting => {} // Default options
TruncateExisting => {
options.truncate(true);
}
}
options.open(file_name).map(|file| {
let fd_num =
this.machine.fds.insert_new(FileHandle { file, writable: desired_write });
Handle::File(fd_num)
})
};
match handle {
Ok(handle) => interp_ok(handle),
Err(e) => {
this.set_last_error(e)?;
interp_ok(Handle::Invalid)
}
}
}
fn GetFileInformationByHandle(
&mut self,
file: &OpTy<'tcx>, // HANDLE
file_information: &OpTy<'tcx>, // LPBY_HANDLE_FILE_INFORMATION
) -> InterpResult<'tcx, Scalar> {
// ^ Returns BOOL (i32 on Windows)
let this = self.eval_context_mut();
this.assert_target_os("windows", "GetFileInformationByHandle");
this.check_no_isolation("`GetFileInformationByHandle`")?;
let file = this.read_handle(file, "GetFileInformationByHandle")?;
let file_information = this.deref_pointer_as(
file_information,
this.windows_ty_layout("BY_HANDLE_FILE_INFORMATION"),
)?;
let fd_num = if let Handle::File(fd_num) = file {
fd_num
} else {
this.invalid_handle("GetFileInformationByHandle")?
};
let Some(desc) = this.machine.fds.get(fd_num) else {
this.invalid_handle("GetFileInformationByHandle")?
};
let metadata = match desc.metadata()? {
Ok(meta) => meta,
Err(e) => {
this.set_last_error(e)?;
return interp_ok(this.eval_windows("c", "FALSE"));
}
};
let size = metadata.len();
let file_type = metadata.file_type();
let attributes = if file_type.is_dir() {
this.eval_windows_u32("c", "FILE_ATTRIBUTE_DIRECTORY")
} else if file_type.is_file() {
this.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL")
} else {
this.eval_windows_u32("c", "FILE_ATTRIBUTE_DEVICE")
};
// Per the Windows documentation:
// "If the underlying file system does not support the [...] time, this member is zero (0)."
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information
let created = extract_windows_epoch(this, metadata.created())?.unwrap_or((0, 0));
let accessed = extract_windows_epoch(this, metadata.accessed())?.unwrap_or((0, 0));
let written = extract_windows_epoch(this, metadata.modified())?.unwrap_or((0, 0));
this.write_int_fields_named(&[("dwFileAttributes", attributes.into())], &file_information)?;
write_filetime_field(this, &file_information, "ftCreationTime", created)?;
write_filetime_field(this, &file_information, "ftLastAccessTime", accessed)?;
write_filetime_field(this, &file_information, "ftLastWriteTime", written)?;
this.write_int_fields_named(
&[
("dwVolumeSerialNumber", 0),
("nFileSizeHigh", (size >> 32).into()),
("nFileSizeLow", (size & 0xFFFFFFFF).into()),
("nNumberOfLinks", 1),
("nFileIndexHigh", 0),
("nFileIndexLow", 0),
],
&file_information,
)?;
interp_ok(this.eval_windows("c", "TRUE"))
}
}
/// Windows FILETIME is measured in 100-nanosecs since 1601
fn extract_windows_epoch<'tcx>(
ecx: &MiriInterpCx<'tcx>,
time: io::Result<SystemTime>,
) -> InterpResult<'tcx, Option<(u32, u32)>> {
match time.ok() {
Some(time) => {
let duration = ecx.system_time_since_windows_epoch(&time)?;
let duration_ticks = ecx.windows_ticks_for(duration)?;
#[allow(clippy::cast_possible_truncation)]
interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
}
None => interp_ok(None),
}
}
fn write_filetime_field<'tcx>(
cx: &mut MiriInterpCx<'tcx>,
val: &MPlaceTy<'tcx>,
name: &str,
(low, high): (u32, u32),
) -> InterpResult<'tcx> {
cx.write_int_fields_named(
&[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
&cx.project_field_named(val, name)?,
)
}

View file

@ -1,9 +1,9 @@
use std::mem::variant_count;
use std::panic::Location;
use rustc_abi::HasDataLayout;
use crate::concurrency::thread::ThreadNotFound;
use crate::shims::files::FdNum;
use crate::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@ -17,7 +17,7 @@ pub enum Handle {
Null,
Pseudo(PseudoHandle),
Thread(ThreadId),
File(i32),
File(FdNum),
Invalid,
}
@ -51,6 +51,8 @@ impl Handle {
const PSEUDO_DISCRIMINANT: u32 = 1;
const THREAD_DISCRIMINANT: u32 = 2;
const FILE_DISCRIMINANT: u32 = 3;
// Chosen to ensure Handle::Invalid encodes to -1. Update this value if there are ever more than
// 8 discriminants.
const INVALID_DISCRIMINANT: u32 = 7;
fn discriminant(self) -> u32 {
@ -70,20 +72,25 @@ impl Handle {
Self::Thread(thread) => thread.to_u32(),
#[expect(clippy::cast_sign_loss)]
Self::File(fd) => fd as u32,
// INVALID_HANDLE_VALUE is -1. This fact is explicitly declared or implied in several
// pages of Windows documentation.
// 1: https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.safehandles.safefilehandle?view=net-9.0
// 2: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=msvc-170
Self::Invalid => 0x1FFFFFFF,
}
}
fn packed_disc_size() -> u32 {
// ceil(log2(x)) is how many bits it takes to store x numbers
// We ensure that INVALID_HANDLE_VALUE (0xFFFFFFFF) decodes to Handle::Invalid
// see https://devblogs.microsoft.com/oldnewthing/20230914-00/?p=108766
// ceil(log2(x)) is how many bits it takes to store x numbers.
// We ensure that INVALID_HANDLE_VALUE (0xFFFFFFFF) decodes to Handle::Invalid.
// see https://devblogs.microsoft.com/oldnewthing/20230914-00/?p=108766 for more detail on
// INVALID_HANDLE_VALUE.
let variant_count = variant_count::<Self>();
// however, std's ilog2 is floor(log2(x))
// However, std's ilog2 is floor(log2(x)).
let floor_log2 = variant_count.ilog2();
// we need to add one for non powers of two to compensate for the difference
// We need to add one for non powers of two to compensate for the difference.
#[expect(clippy::arithmetic_side_effects)] // cannot overflow
if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }
}
@ -105,7 +112,7 @@ impl Handle {
assert!(discriminant < 2u32.pow(disc_size));
// make sure the data fits into `data_size` bits
assert!(data <= 2u32.pow(data_size));
assert!(data < 2u32.pow(data_size));
// packs the data into the lower `data_size` bits
// and packs the discriminant right above the data
@ -118,7 +125,11 @@ impl Handle {
Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)),
Self::THREAD_DISCRIMINANT => Some(Self::Thread(ThreadId::new_unchecked(data))),
#[expect(clippy::cast_possible_wrap)]
Self::FILE_DISCRIMINANT => Some(Self::File(data as i32)),
Self::FILE_DISCRIMINANT => {
// This cast preserves all bits.
assert_eq!(size_of_val(&data), size_of::<FdNum>());
Some(Self::File(data as FdNum))
}
Self::INVALID_DISCRIMINANT => Some(Self::Invalid),
_ => None,
}
@ -154,7 +165,7 @@ impl Handle {
/// Structurally invalid handles return [`HandleError::InvalidHandle`].
/// If the handle is structurally valid but semantically invalid, e.g. a for non-existent thread
/// ID, returns [`HandleError::ThreadNotFound`].
pub fn try_from_scalar<'tcx>(
fn try_from_scalar<'tcx>(
handle: Scalar,
cx: &MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, Result<Self, HandleError>> {
@ -186,22 +197,23 @@ impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
#[allow(non_snake_case)]
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Convert a scalar into a structured `Handle`.
/// If the handle is invalid, or references a non-existent item, execution is aborted.
#[track_caller]
fn read_handle(&self, handle: &OpTy<'tcx>) -> InterpResult<'tcx, Handle> {
fn read_handle(&self, handle: &OpTy<'tcx>, function_name: &str) -> InterpResult<'tcx, Handle> {
let this = self.eval_context_ref();
let handle = this.read_scalar(handle)?;
match Handle::try_from_scalar(handle, this)? {
Ok(handle) => interp_ok(handle),
Err(HandleError::InvalidHandle) =>
throw_machine_stop!(TerminationInfo::Abort(format!(
"invalid handle {} at {}",
"invalid handle {} passed to {function_name}",
handle.to_target_isize(this)?,
Location::caller(),
))),
Err(HandleError::ThreadNotFound(_)) =>
throw_machine_stop!(TerminationInfo::Abort(format!(
"invalid thread ID: {}",
Location::caller()
"invalid thread ID {} passed to {function_name}",
handle.to_target_isize(this)?,
))),
}
}
@ -215,15 +227,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let handle = this.read_handle(handle_op)?;
let handle = this.read_handle(handle_op, "CloseHandle")?;
let ret = match handle {
Handle::Thread(thread) => {
this.detach_thread(thread, /*allow_terminated_joined*/ true)?;
this.eval_windows("c", "TRUE")
}
Handle::File(fd) =>
if let Some(file) = this.machine.fds.get(fd) {
let err = file.close(this.machine.communicate(), this)?;
Handle::File(fd_num) =>
if let Some(fd) = this.machine.fds.remove(fd_num) {
let err = fd.close_ref(this.machine.communicate(), this)?;
if let Err(e) = err {
this.set_last_error(e)?;
this.eval_windows("c", "FALSE")
@ -239,3 +251,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(ret)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_encoding() {
// Ensure the invalid handle encodes to `u32::MAX`/`INVALID_HANDLE_VALUE`.
assert_eq!(Handle::Invalid.to_packed(), u32::MAX)
}
}

View file

@ -1,12 +1,14 @@
pub mod foreign_items;
mod env;
mod fs;
mod handle;
mod sync;
mod thread;
// All the Windows-specific extension traits
pub use self::env::{EvalContextExt as _, WindowsEnvVars};
pub use self::fs::EvalContextExt as _;
pub use self::handle::EvalContextExt as _;
pub use self::sync::EvalContextExt as _;
pub use self::thread::EvalContextExt as _;

View file

@ -62,7 +62,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let handle = this.read_handle(handle_op)?;
let handle = this.read_handle(handle_op, "WaitForSingleObject")?;
let timeout = this.read_scalar(timeout_op)?.to_u32()?;
let thread = match handle {

View file

@ -25,6 +25,6 @@ page_size = "0.6"
tokio = { version = "1.24", features = ["macros", "rt-multi-thread", "time", "net", "fs", "sync", "signal", "io-util"] }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59", features = [ "Win32_Foundation", "Win32_System_Threading" ] }
windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_Storage_FileSystem", "Win32_Security"] }
[workspace]

View file

@ -13,7 +13,7 @@ use windows_sys::Win32::System::Threading::{INFINITE, WaitForSingleObject};
// XXX HACK: This is how miri represents the handle for thread 0.
// This value can be "legitimately" obtained by using `GetCurrentThread` with `DuplicateHandle`
// but miri does not implement `DuplicateHandle` yet.
const MAIN_THREAD: HANDLE = (2i32 << 30) as HANDLE;
const MAIN_THREAD: HANDLE = (2i32 << 29) as HANDLE;
fn main() {
thread::spawn(|| {

View file

@ -0,0 +1,198 @@
//@only-target: windows # this directly tests windows-only functions
//@compile-flags: -Zmiri-disable-isolation
#![allow(nonstandard_style)]
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use std::ptr;
#[path = "../../utils/mod.rs"]
mod utils;
use windows_sys::Win32::Foundation::{
CloseHandle, ERROR_ALREADY_EXISTS, GENERIC_READ, GENERIC_WRITE, GetLastError,
};
use windows_sys::Win32::Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, CREATE_ALWAYS, CREATE_NEW, CreateFileW, FILE_ATTRIBUTE_DIRECTORY,
FILE_ATTRIBUTE_NORMAL, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT,
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, GetFileInformationByHandle, OPEN_ALWAYS,
OPEN_EXISTING,
};
fn main() {
unsafe {
test_create_dir_file();
test_create_normal_file();
test_create_always_twice();
test_open_always_twice();
test_open_dir_reparse();
}
}
unsafe fn test_create_dir_file() {
let temp = utils::tmp();
let raw_path = to_wide_cstr(&temp);
// Open the `temp` directory.
let handle = CreateFileW(
raw_path.as_ptr(),
GENERIC_READ,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
ptr::null_mut(),
);
assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
let mut info = std::mem::zeroed::<BY_HANDLE_FILE_INFORMATION>();
if GetFileInformationByHandle(handle, &mut info) == 0 {
panic!("Failed to get file information")
};
assert!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0);
if CloseHandle(handle) == 0 {
panic!("Failed to close file")
};
}
unsafe fn test_create_normal_file() {
let temp = utils::tmp().join("test.txt");
let raw_path = to_wide_cstr(&temp);
let handle = CreateFileW(
raw_path.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
CREATE_NEW,
0,
ptr::null_mut(),
);
assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
let mut info = std::mem::zeroed::<BY_HANDLE_FILE_INFORMATION>();
if GetFileInformationByHandle(handle, &mut info) == 0 {
panic!("Failed to get file information: {}", GetLastError())
};
assert!(info.dwFileAttributes & FILE_ATTRIBUTE_NORMAL != 0);
if CloseHandle(handle) == 0 {
panic!("Failed to close file")
};
// Test metadata-only handle
let handle = CreateFileW(
raw_path.as_ptr(),
0,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_EXISTING,
0,
ptr::null_mut(),
);
assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
let mut info = std::mem::zeroed::<BY_HANDLE_FILE_INFORMATION>();
if GetFileInformationByHandle(handle, &mut info) == 0 {
panic!("Failed to get file information: {}", GetLastError())
};
assert!(info.dwFileAttributes & FILE_ATTRIBUTE_NORMAL != 0);
if CloseHandle(handle) == 0 {
panic!("Failed to close file")
};
}
/// Tests that CREATE_ALWAYS sets the error value correctly based on whether the file already exists
unsafe fn test_create_always_twice() {
let temp = utils::tmp().join("test_create_always.txt");
let raw_path = to_wide_cstr(&temp);
let handle = CreateFileW(
raw_path.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
CREATE_ALWAYS,
0,
ptr::null_mut(),
);
assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
assert_eq!(GetLastError(), 0);
if CloseHandle(handle) == 0 {
panic!("Failed to close file")
};
let handle = CreateFileW(
raw_path.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
CREATE_ALWAYS,
0,
ptr::null_mut(),
);
assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
assert_eq!(GetLastError(), ERROR_ALREADY_EXISTS);
if CloseHandle(handle) == 0 {
panic!("Failed to close file")
};
}
/// Tests that OPEN_ALWAYS sets the error value correctly based on whether the file already exists
unsafe fn test_open_always_twice() {
let temp = utils::tmp().join("test_open_always.txt");
let raw_path = to_wide_cstr(&temp);
let handle = CreateFileW(
raw_path.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_ALWAYS,
0,
ptr::null_mut(),
);
assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
assert_eq!(GetLastError(), 0);
if CloseHandle(handle) == 0 {
panic!("Failed to close file")
};
let handle = CreateFileW(
raw_path.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_ALWAYS,
0,
ptr::null_mut(),
);
assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
assert_eq!(GetLastError(), ERROR_ALREADY_EXISTS);
if CloseHandle(handle) == 0 {
panic!("Failed to close file")
};
}
// TODO: Once we support more of the std API, it would be nice to test against an actual symlink
unsafe fn test_open_dir_reparse() {
let temp = utils::tmp();
let raw_path = to_wide_cstr(&temp);
// Open the `temp` directory.
let handle = CreateFileW(
raw_path.as_ptr(),
GENERIC_READ,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
ptr::null_mut(),
);
assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
let mut info = std::mem::zeroed::<BY_HANDLE_FILE_INFORMATION>();
if GetFileInformationByHandle(handle, &mut info) == 0 {
panic!("Failed to get file information")
};
assert!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0);
if CloseHandle(handle) == 0 {
panic!("Failed to close file")
};
}
fn to_wide_cstr(path: &Path) -> Vec<u16> {
let mut raw_path = path.as_os_str().encode_wide().collect::<Vec<_>>();
raw_path.extend([0, 0]);
raw_path
}

View file

@ -1,4 +1,3 @@
//@ignore-target: windows # File handling is not implemented yet
//@compile-flags: -Zmiri-disable-isolation
#![feature(io_error_more)]
@ -18,20 +17,23 @@ mod utils;
fn main() {
test_path_conversion();
test_file();
test_file_clone();
test_file_create_new();
test_seek();
test_metadata();
test_file_set_len();
test_file_sync();
test_errors();
test_rename();
test_directory();
test_canonicalize();
test_from_raw_os_error();
#[cfg(unix)]
test_pread_pwrite();
// Windows file handling is very incomplete.
if cfg!(not(windows)) {
test_file();
test_file_create_new();
test_seek();
test_file_clone();
test_metadata();
test_file_set_len();
test_file_sync();
test_errors();
test_rename();
test_directory();
test_canonicalize();
test_from_raw_os_error();
#[cfg(unix)]
test_pread_pwrite();
}
}
fn test_path_conversion() {
@ -144,10 +146,10 @@ fn test_metadata() {
let path = utils::prepare_with_content("miri_test_fs_metadata.txt", bytes);
// Test that metadata of an absolute path is correct.
check_metadata(bytes, &path).unwrap();
check_metadata(bytes, &path).expect("absolute path metadata");
// Test that metadata of a relative path is correct.
std::env::set_current_dir(path.parent().unwrap()).unwrap();
check_metadata(bytes, Path::new(path.file_name().unwrap())).unwrap();
check_metadata(bytes, Path::new(path.file_name().unwrap())).expect("relative path metadata");
// Removing file should succeed.
remove_file(&path).unwrap();