Skip to content
Open
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 @@ -306,6 +306,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
59 changes: 59 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 crate::shims::files::{FileDescription, FileHandle};
use crate::shims::windows::handle::{EvalContextExt as _, Handle};
Expand Down Expand Up @@ -373,6 +374,64 @@ 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("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_info = this.read_pointer(file_information)?;
Copy link
Member

Choose a reason for hiding this comment

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

Please pick a consistent name (file_information vs file_info).

this.check_ptr_access(
file_info,
Size::from_bytes(buffer_size),
CheckInAllocMsg::MemoryAccess,
)?;

let file = this.read_handle(file, "GetFileInformationByHandle")?;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let file = this.read_handle(file, "GetFileInformationByHandle")?;
let file = this.read_handle(file, "SetFileInformationByHandle")?;

Happens a few more times below.

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 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 ptr = this.deref_pointer_as(
file_information,
this.windows_ty_layout("FILE_END_OF_FILE_INFO"),
)?;
Comment on lines +414 to +417
Copy link
Member

Choose a reason for hiding this comment

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

This re-reads the pointer. Since you already read the pointer, you can just call ptr_to_mplace directly.

let new_len =
this.read_scalar(&this.project_field_named(&ptr, "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 {
throw_unsup_format!(
"SetFileInformationByHandle: Unsupported `FileInformationClass` value {}",
class
)
}
}

fn DeleteFileW(
&mut self,
file_name: &OpTy<'tcx>, // LPCWSTR
Expand Down
25 changes: 22 additions & 3 deletions tests/pass-dep/shims/windows-fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//@compile-flags: -Zmiri-disable-isolation
#![allow(nonstandard_style)]

use std::io::{ErrorKind, Read, Write};
use std::io::{ErrorKind, Read, Seek, SeekFrom, Write};
use std::os::windows::ffi::OsStrExt;
use std::os::windows::io::AsRawHandle;
use std::path::Path;
Expand All @@ -20,8 +20,10 @@ 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_END_OF_FILE_INFO, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT,
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FileEndOfFileInfo,
GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING, SetFileInformationByHandle,
SetFilePointerEx,
};
use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;

Expand All @@ -36,6 +38,7 @@ fn main() {
test_ntstatus_to_dos();
test_file_read_write();
test_file_seek();
test_set_file_info();
}
}

Expand Down Expand Up @@ -273,6 +276,22 @@ 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);
}

unsafe fn test_file_seek() {
let temp = utils::tmp().join("test_file_seek.txt");
let mut file = fs::File::options().create(true).write(true).read(true).open(&temp).unwrap();
Expand Down
8 changes: 5 additions & 3 deletions tests/pass/shims/fs.rs
Copy link
Member

Choose a reason for hiding this comment

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

Can you also add a File::create on a file that already exists somewhere suitable in this file so we ensure we cover that codepath?

Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,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 @@ -85,7 +85,6 @@ fn test_file_partial_reads_writes() {

// Ensure we sometimes do incomplete writes.
let got_short_write = (0..16).any(|_| {
let _ = remove_file(&path); // FIXME(win, issue #4483): errors if the file already exists
let mut file = File::create(&path).unwrap();
file.write(&[0; 4]).unwrap() != 4
});
Expand Down Expand Up @@ -209,7 +208,10 @@ 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());
assert_eq!(
if cfg!(windows) { ErrorKind::PermissionDenied } else { ErrorKind::InvalidInput },
Copy link
Member

Choose a reason for hiding this comment

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

Since we pass through the host OS error, I doubt this will work properly. Just check that it's one of the two (with a comment explaining why).

file.set_len(14).unwrap_err().kind()
);

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