Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/shims/windows/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let res = this.GetFileInformationByHandle(handle, info)?;
this.write_scalar(res, dest)?;
}
"SetFileInformationByHandle" => {
let [handle, class, info, size] =
this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
let res = this.SetFileInformationByHandle(handle, class, info, size)?;
this.write_scalar(res, dest)?;
}
"DeleteFileW" => {
let [file_name] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
let res = this.DeleteFileW(file_name)?;
Expand Down
85 changes: 85 additions & 0 deletions src/shims/windows/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::PathBuf;
use std::time::SystemTime;

use bitflags::bitflags;
use rustc_abi::Size;
use rustc_target::spec::Os;

use crate::shims::files::{FdId, FileDescription, FileHandle};
Expand Down Expand Up @@ -372,6 +373,90 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(this.eval_windows("c", "TRUE"))
}

fn SetFileInformationByHandle(
&mut self,
file: &OpTy<'tcx>, // HANDLE
class: &OpTy<'tcx>, // FILE_INFO_BY_HANDLE_CLASS
file_information: &OpTy<'tcx>, // LPVOID
buffer_size: &OpTy<'tcx>, // DWORD
) -> InterpResult<'tcx, Scalar> {
// ^ Returns BOOL (i32 on Windows)
let this = self.eval_context_mut();
this.assert_target_os(Os::Windows, "SetFileInformationByHandle");
this.check_no_isolation("`SetFileInformationByHandle`")?;

let class = this.read_scalar(class)?.to_u32()?;
let buffer_size = this.read_scalar(buffer_size)?.to_u32()?;
let file_information = this.read_pointer(file_information)?;
this.check_ptr_access(
file_information,
Size::from_bytes(buffer_size),
CheckInAllocMsg::MemoryAccess,
)?;

let file = this.read_handle(file, "SetFileInformationByHandle")?;
let Handle::File(fd_num) = file else { this.invalid_handle("SetFileInformationByHandle")? };
let Some(desc) = this.machine.fds.get(fd_num) else {
this.invalid_handle("SetFileInformationByHandle")?
};
let file = desc.downcast::<FileHandle>().ok_or_else(|| {
err_unsup_format!(
"`SetFileInformationByHandle` is only supported on file-backed file descriptors"
)
})?;

if class == this.eval_windows_u32("c", "FileEndOfFileInfo") {
let place = this
.ptr_to_mplace(file_information, this.windows_ty_layout("FILE_END_OF_FILE_INFO"));
let new_len =
this.read_scalar(&this.project_field_named(&place, "EndOfFile")?)?.to_i64()?;
match file.file.set_len(new_len.try_into().unwrap()) {
Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
Err(e) => {
this.set_last_error(e)?;
interp_ok(this.eval_windows("c", "FALSE"))
}
}
} else if class == this.eval_windows_u32("c", "FileAllocationInfo") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't have a direct test.

// On Windows, files are somewhat similar to a `Vec` in that they have a separate
// "length" (called "EOF position") and "capacity" (called "allocation size").
// Growing the allocation size is largely a performance hint which we can
// ignore -- it can also be directly queried, but we currently do not support that.
// So we only need to do something if this operation shrinks the allocation size
// so far that it affects the EOF position.
let place = this
.ptr_to_mplace(file_information, this.windows_ty_layout("FILE_ALLOCATION_INFO"));
let new_alloc_size: u64 = this
.read_scalar(&this.project_field_named(&place, "AllocationSize")?)?
.to_i64()?
.try_into()
.unwrap();
let old_len = match file.file.metadata() {
Ok(m) => m.len(),
Err(e) => {
this.set_last_error(e)?;
return interp_ok(this.eval_windows("c", "FALSE"));
}
};
if new_alloc_size < old_len {
match file.file.set_len(new_alloc_size) {
Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
Err(e) => {
this.set_last_error(e)?;
interp_ok(this.eval_windows("c", "FALSE"))
}
}
} else {
interp_ok(this.eval_windows("c", "TRUE"))
}
} else {
throw_unsup_format!(
"SetFileInformationByHandle: Unsupported `FileInformationClass` value {}",
class
)
}
}

fn DeleteFileW(
&mut self,
file_name: &OpTy<'tcx>, // LPCWSTR
Expand Down
35 changes: 32 additions & 3 deletions tests/pass-dep/shims/windows-fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ use windows_sys::Win32::Foundation::{
};
use windows_sys::Win32::Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, CREATE_ALWAYS, CREATE_NEW, CreateFileW, DeleteFileW,
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_BEGIN, FILE_CURRENT,
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ,
FILE_SHARE_WRITE, GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING, SetFilePointerEx,
FILE_ALLOCATION_INFO, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_BEGIN,
FILE_CURRENT, FILE_END_OF_FILE_INFO, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT,
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FileAllocationInfo, FileEndOfFileInfo,
GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING, SetFileInformationByHandle,
SetFilePointerEx,
};
use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;
use windows_sys::Win32::System::Threading::GetCurrentProcess;
Expand All @@ -37,6 +39,7 @@ fn main() {
test_ntstatus_to_dos();
test_file_read_write();
test_file_seek();
test_set_file_info();
test_dup_handle();
}
}
Expand Down Expand Up @@ -275,6 +278,32 @@ unsafe fn test_file_read_write() {
assert_eq!(GetLastError(), 1234);
}

unsafe fn test_set_file_info() {
let temp = utils::tmp().join("test_set_file.txt");
let mut file = fs::File::create(&temp).unwrap();
let handle = file.as_raw_handle();

let info = FILE_END_OF_FILE_INFO { EndOfFile: 20 };
let res = SetFileInformationByHandle(
handle,
FileEndOfFileInfo,
ptr::from_ref(&info).cast(),
size_of::<FILE_END_OF_FILE_INFO>().try_into().unwrap(),
);
assert!(res != 0);
assert_eq!(file.seek(SeekFrom::End(0)).unwrap(), 20);

let info = FILE_ALLOCATION_INFO { AllocationSize: 0 };
let res = SetFileInformationByHandle(
handle,
FileAllocationInfo,
ptr::from_ref(&info).cast(),
size_of::<FILE_ALLOCATION_INFO>().try_into().unwrap(),
);
assert!(res != 0);
assert_eq!(file.metadata().unwrap().len(), 0);
}

unsafe fn test_dup_handle() {
let temp = utils::tmp().join("test_dup.txt");

Expand Down
13 changes: 10 additions & 3 deletions tests/pass/shims/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ fn main() {
test_errors();
test_from_raw_os_error();
test_file_clone();
test_file_set_len();
// Windows file handling is very incomplete.
if cfg!(not(windows)) {
test_file_set_len();
test_file_sync();
test_rename();
test_directory();
Expand Down Expand Up @@ -77,6 +77,9 @@ fn test_file() {
// However, writing 0 bytes can succeed or fail.
let _ignore = file.write(&[]);

// Test calling File::create on an existing file, since that uses a different code path
File::create(&path).unwrap();

// Removing file should succeed.
remove_file(&path).unwrap();
}
Expand All @@ -87,7 +90,6 @@ fn test_file_partial_reads_writes() {

// Ensure we sometimes do incomplete writes.
check_nondet(|| {
let _ = remove_file(&path1); // FIXME(win, issue #4483): errors if the file already exists
let mut file = File::create(&path1).unwrap();
file.write(&[0; 4]).unwrap() == 4
});
Expand Down Expand Up @@ -210,7 +212,12 @@ fn test_file_set_len() {

// Can't use set_len on a file not opened for writing
let file = OpenOptions::new().read(true).open(&path).unwrap();
assert_eq!(ErrorKind::InvalidInput, file.set_len(14).unwrap_err().kind());
// Due to https://github.com/rust-lang/miri/issues/4457, we have to assume the failure could
// be either of the Windows or Unix kind, no matter which platform we're on.
assert!(
[ErrorKind::PermissionDenied, ErrorKind::InvalidInput]
.contains(&file.set_len(14).unwrap_err().kind())
);

remove_file(&path).unwrap();
}
Expand Down