diff --git a/src/shims/windows/foreign_items.rs b/src/shims/windows/foreign_items.rs index 7b13f1d908..90f7806bb5 100644 --- a/src/shims/windows/foreign_items.rs +++ b/src/shims/windows/foreign_items.rs @@ -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)?; diff --git a/src/shims/windows/fs.rs b/src/shims/windows/fs.rs index e4ec1b0130..1815b04186 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 crate::shims::files::{FileDescription, FileHandle}; use crate::shims::windows::handle::{EvalContextExt as _, Handle}; @@ -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)?; + this.check_ptr_access( + file_info, + Size::from_bytes(buffer_size), + CheckInAllocMsg::MemoryAccess, + )?; + + let file = this.read_handle(file, "GetFileInformationByHandle")?; + 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::().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"), + )?; + 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 diff --git a/tests/pass-dep/shims/windows-fs.rs b/tests/pass-dep/shims/windows-fs.rs index 4ca19046b6..a8941fe53c 100644 --- a/tests/pass-dep/shims/windows-fs.rs +++ b/tests/pass-dep/shims/windows-fs.rs @@ -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; @@ -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; @@ -36,6 +38,7 @@ fn main() { test_ntstatus_to_dos(); test_file_read_write(); test_file_seek(); + test_set_file_info(); } } @@ -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::().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(); diff --git a/tests/pass/shims/fs.rs b/tests/pass/shims/fs.rs index 022dcc5dcb..9915b06b3d 100644 --- a/tests/pass/shims/fs.rs +++ b/tests/pass/shims/fs.rs @@ -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(); @@ -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 }); @@ -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 }, + file.set_len(14).unwrap_err().kind() + ); remove_file(&path).unwrap(); }