Skip to content

Commit 2c3c11e

Browse files
committed
Implement SetFileInformationByHandle, as well as direct shim test for it
1 parent dc1ede0 commit 2c3c11e

File tree

4 files changed

+133
-6
lines changed

4 files changed

+133
-6
lines changed

src/shims/windows/foreign_items.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
307307
let res = this.GetFileInformationByHandle(handle, info)?;
308308
this.write_scalar(res, dest)?;
309309
}
310+
"SetFileInformationByHandle" => {
311+
let [handle, class, info, size] =
312+
this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
313+
let res = this.SetFileInformationByHandle(handle, class, info, size)?;
314+
this.write_scalar(res, dest)?;
315+
}
310316
"DeleteFileW" => {
311317
let [file_name] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
312318
let res = this.DeleteFileW(file_name)?;

src/shims/windows/fs.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::path::PathBuf;
55
use std::time::SystemTime;
66

77
use bitflags::bitflags;
8+
use rustc_abi::Size;
89
use rustc_target::spec::Os;
910

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

376+
fn SetFileInformationByHandle(
377+
&mut self,
378+
file: &OpTy<'tcx>, // HANDLE
379+
class: &OpTy<'tcx>, // FILE_INFO_BY_HANDLE_CLASS
380+
file_information: &OpTy<'tcx>, // LPVOID
381+
buffer_size: &OpTy<'tcx>, // DWORD
382+
) -> InterpResult<'tcx, Scalar> {
383+
// ^ Returns BOOL (i32 on Windows)
384+
let this = self.eval_context_mut();
385+
this.assert_target_os(Os::Windows, "SetFileInformationByHandle");
386+
this.check_no_isolation("`SetFileInformationByHandle`")?;
387+
388+
let class = this.read_scalar(class)?.to_u32()?;
389+
let buffer_size = this.read_scalar(buffer_size)?.to_u32()?;
390+
let file_information = this.read_pointer(file_information)?;
391+
this.check_ptr_access(
392+
file_information,
393+
Size::from_bytes(buffer_size),
394+
CheckInAllocMsg::MemoryAccess,
395+
)?;
396+
397+
let file = this.read_handle(file, "SetFileInformationByHandle")?;
398+
let Handle::File(fd_num) = file else { this.invalid_handle("SetFileInformationByHandle")? };
399+
let Some(desc) = this.machine.fds.get(fd_num) else {
400+
this.invalid_handle("SetFileInformationByHandle")?
401+
};
402+
let file = desc.downcast::<FileHandle>().ok_or_else(|| {
403+
err_unsup_format!(
404+
"`SetFileInformationByHandle` is only supported on file-backed file descriptors"
405+
)
406+
})?;
407+
408+
if class == this.eval_windows_u32("c", "FileEndOfFileInfo") {
409+
let place = this
410+
.ptr_to_mplace(file_information, this.windows_ty_layout("FILE_END_OF_FILE_INFO"));
411+
let new_len =
412+
this.read_scalar(&this.project_field_named(&place, "EndOfFile")?)?.to_i64()?;
413+
match file.file.set_len(new_len.try_into().unwrap()) {
414+
Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
415+
Err(e) => {
416+
this.set_last_error(e)?;
417+
interp_ok(this.eval_windows("c", "FALSE"))
418+
}
419+
}
420+
} else if class == this.eval_windows_u32("c", "FileAllocationInfo") {
421+
// On Windows, files are somewhat similar to a `Vec` in that they have a separate
422+
// "length" (called "EOF position") and "capacity" (called "allocation size").
423+
// Growing the allocation size is largely a performance hint which we can
424+
// ignore -- it can also be directly queried, but we currently do not support that.
425+
// So we only need to do something if this operation shrinks the allocation size
426+
// so far that it affects the EOF position.
427+
let place = this
428+
.ptr_to_mplace(file_information, this.windows_ty_layout("FILE_ALLOCATION_INFO"));
429+
let new_alloc_size: u64 = this
430+
.read_scalar(&this.project_field_named(&place, "AllocationSize")?)?
431+
.to_i64()?
432+
.try_into()
433+
.unwrap();
434+
let old_len = match file.file.metadata() {
435+
Ok(m) => m.len(),
436+
Err(e) => {
437+
this.set_last_error(e)?;
438+
return interp_ok(this.eval_windows("c", "FALSE"));
439+
}
440+
};
441+
if new_alloc_size < old_len {
442+
match file.file.set_len(new_alloc_size) {
443+
Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
444+
Err(e) => {
445+
this.set_last_error(e)?;
446+
interp_ok(this.eval_windows("c", "FALSE"))
447+
}
448+
}
449+
} else {
450+
interp_ok(this.eval_windows("c", "TRUE"))
451+
}
452+
} else {
453+
throw_unsup_format!(
454+
"SetFileInformationByHandle: Unsupported `FileInformationClass` value {}",
455+
class
456+
)
457+
}
458+
}
459+
375460
fn DeleteFileW(
376461
&mut self,
377462
file_name: &OpTy<'tcx>, // LPCWSTR

tests/pass-dep/shims/windows-fs.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ use windows_sys::Win32::Foundation::{
1919
};
2020
use windows_sys::Win32::Storage::FileSystem::{
2121
BY_HANDLE_FILE_INFORMATION, CREATE_ALWAYS, CREATE_NEW, CreateFileW, DeleteFileW,
22-
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_BEGIN, FILE_CURRENT,
23-
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ,
24-
FILE_SHARE_WRITE, GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING, SetFilePointerEx,
22+
FILE_ALLOCATION_INFO, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_BEGIN,
23+
FILE_CURRENT, FILE_END_OF_FILE_INFO, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT,
24+
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FileAllocationInfo, FileEndOfFileInfo,
25+
GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING, SetFileInformationByHandle,
26+
SetFilePointerEx,
2527
};
2628
use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;
2729
use windows_sys::Win32::System::Threading::GetCurrentProcess;
@@ -37,6 +39,7 @@ fn main() {
3739
test_ntstatus_to_dos();
3840
test_file_read_write();
3941
test_file_seek();
42+
test_set_file_info();
4043
test_dup_handle();
4144
}
4245
}
@@ -275,6 +278,32 @@ unsafe fn test_file_read_write() {
275278
assert_eq!(GetLastError(), 1234);
276279
}
277280

281+
unsafe fn test_set_file_info() {
282+
let temp = utils::tmp().join("test_set_file.txt");
283+
let mut file = fs::File::create(&temp).unwrap();
284+
let handle = file.as_raw_handle();
285+
286+
let info = FILE_END_OF_FILE_INFO { EndOfFile: 20 };
287+
let res = SetFileInformationByHandle(
288+
handle,
289+
FileEndOfFileInfo,
290+
ptr::from_ref(&info).cast(),
291+
size_of::<FILE_END_OF_FILE_INFO>().try_into().unwrap(),
292+
);
293+
assert!(res != 0);
294+
assert_eq!(file.seek(SeekFrom::End(0)).unwrap(), 20);
295+
296+
let info = FILE_ALLOCATION_INFO { AllocationSize: 0 };
297+
let res = SetFileInformationByHandle(
298+
handle,
299+
FileAllocationInfo,
300+
ptr::from_ref(&info).cast(),
301+
size_of::<FILE_ALLOCATION_INFO>().try_into().unwrap(),
302+
);
303+
assert!(res != 0);
304+
assert_eq!(file.metadata().unwrap().len(), 0);
305+
}
306+
278307
unsafe fn test_dup_handle() {
279308
let temp = utils::tmp().join("test_dup.txt");
280309

tests/pass/shims/fs.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ fn main() {
2828
test_errors();
2929
test_from_raw_os_error();
3030
test_file_clone();
31+
test_file_set_len();
3132
// Windows file handling is very incomplete.
3233
if cfg!(not(windows)) {
33-
test_file_set_len();
3434
test_file_sync();
3535
test_rename();
3636
test_directory();
@@ -77,6 +77,9 @@ fn test_file() {
7777
// However, writing 0 bytes can succeed or fail.
7878
let _ignore = file.write(&[]);
7979

80+
// Test calling File::create on an existing file, since that uses a different code path
81+
File::create(&path).unwrap();
82+
8083
// Removing file should succeed.
8184
remove_file(&path).unwrap();
8285
}
@@ -87,7 +90,6 @@ fn test_file_partial_reads_writes() {
8790

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

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

215222
remove_file(&path).unwrap();
216223
}

0 commit comments

Comments
 (0)