diff --git a/src/shims/windows/foreign_items.rs b/src/shims/windows/foreign_items.rs index c824147ad4..7d30582c58 100644 --- a/src/shims/windows/foreign_items.rs +++ b/src/shims/windows/foreign_items.rs @@ -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)?; diff --git a/src/shims/windows/fs.rs b/src/shims/windows/fs.rs index ad22df2425..967164cb25 100644 --- a/src/shims/windows/fs.rs +++ b/src/shims/windows/fs.rs @@ -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}; @@ -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::().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") { + // 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 diff --git a/tests/pass-dep/shims/windows-fs.rs b/tests/pass-dep/shims/windows-fs.rs index 7b756603d9..0037f34d3c 100644 --- a/tests/pass-dep/shims/windows-fs.rs +++ b/tests/pass-dep/shims/windows-fs.rs @@ -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; @@ -37,6 +39,7 @@ fn main() { test_ntstatus_to_dos(); test_file_read_write(); test_file_seek(); + test_set_file_info(); test_dup_handle(); } } @@ -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::().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::().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"); diff --git a/tests/pass/shims/fs.rs b/tests/pass/shims/fs.rs index 43fbf6b085..70bca703cc 100644 --- a/tests/pass/shims/fs.rs +++ b/tests/pass/shims/fs.rs @@ -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(); @@ -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(); } @@ -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 }); @@ -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(); }