diff --git a/src/machine.rs b/src/machine.rs index 1c6c7894cb..f75adffd95 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -544,9 +544,6 @@ pub struct MiriMachine<'tcx> { /// Failure rate of compare_exchange_weak, between 0.0 and 1.0 pub(crate) cmpxchg_weak_failure_rate: f64, - /// Corresponds to -Zmiri-mute-stdout-stderr and doesn't write the output but acts as if it succeeded. - pub(crate) mute_stdout_stderr: bool, - /// The probability of the active thread being preempted at the end of each basic block. pub(crate) preemption_rate: f64, @@ -722,7 +719,6 @@ impl<'tcx> MiriMachine<'tcx> { track_alloc_accesses: config.track_alloc_accesses, check_alignment: config.check_alignment, cmpxchg_weak_failure_rate: config.cmpxchg_weak_failure_rate, - mute_stdout_stderr: config.mute_stdout_stderr, preemption_rate: config.preemption_rate, report_progress: config.report_progress, basic_block_count: 0, @@ -925,7 +921,6 @@ impl VisitProvenance for MiriMachine<'_> { track_alloc_accesses: _, check_alignment: _, cmpxchg_weak_failure_rate: _, - mute_stdout_stderr: _, preemption_rate: _, report_progress: _, basic_block_count: _, diff --git a/src/shims/io_error.rs b/src/shims/io_error.rs index acf3f74a93..e597b527cb 100644 --- a/src/shims/io_error.rs +++ b/src/shims/io_error.rs @@ -1,4 +1,5 @@ use std::io; +use std::io::ErrorKind; use crate::*; @@ -13,6 +14,29 @@ pub enum IoError { } pub use self::IoError::*; +impl IoError { + pub(crate) fn into_ntstatus(self) -> i32 { + let raw = match self { + HostError(e) => + match e.kind() { + // STATUS_MEDIA_WRITE_PROTECTED + ErrorKind::ReadOnlyFilesystem => 0xC00000A2u32, + // STATUS_FILE_INVALID + ErrorKind::InvalidInput => 0xC0000098, + // STATUS_DISK_FULL + ErrorKind::QuotaExceeded => 0xC000007F, + // STATUS_ACCESS_DENIED + ErrorKind::PermissionDenied => 0xC0000022, + // For the default error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR. + _ => 0xC0000185, + }, + // For the default error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR. + _ => 0xC0000185, + }; + raw.cast_signed() + } +} + impl From for IoError { fn from(value: io::Error) -> Self { IoError::HostError(value) diff --git a/src/shims/windows/foreign_items.rs b/src/shims/windows/foreign_items.rs index c80858c636..d822dd07fc 100644 --- a/src/shims/windows/foreign_items.rs +++ b/src/shims/windows/foreign_items.rs @@ -195,69 +195,52 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // File related shims "NtWriteFile" => { - if !this.frame_in_std() { - throw_unsup_format!( - "`NtWriteFile` support is crude and just enough for stdout to work" - ); - } - let [ handle, - _event, - _apc_routine, - _apc_context, + event, + apc_routine, + apc_context, io_status_block, buf, n, byte_offset, - _key, + key, ] = this.check_shim(abi, sys_conv, link_name, args)?; - let handle = this.read_target_isize(handle)?; - let buf = this.read_pointer(buf)?; - let n = this.read_scalar(n)?.to_u32()?; - let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer - let io_status_block = this - .deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?; - - if byte_offset != 0 { - throw_unsup_format!( - "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported" - ); - } - - let written = if handle == -11 || handle == -12 { - // stdout/stderr - use io::Write; - - let buf_cont = - this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(u64::from(n)))?; - let res = if this.machine.mute_stdout_stderr { - Ok(buf_cont.len()) - } else if handle == -11 { - io::stdout().write(buf_cont) - } else { - io::stderr().write(buf_cont) - }; - // We write at most `n` bytes, which is a `u32`, so we cannot have written more than that. - res.ok().map(|n| u32::try_from(n).unwrap()) - } else { - throw_unsup_format!( - "on Windows, writing to anything except stdout/stderr is not supported" - ) - }; - // We have to put the result into io_status_block. - if let Some(n) = written { - let io_status_information = - this.project_field_named(&io_status_block, "Information")?; - this.write_scalar( - Scalar::from_target_usize(n.into(), this), - &io_status_information, - )?; - } - // Return whether this was a success. >= 0 is success. - // For the error code we arbitrarily pick 0xC0000185, STATUS_IO_DEVICE_ERROR. - this.write_scalar( - Scalar::from_u32(if written.is_some() { 0 } else { 0xC0000185u32 }), + this.NtWriteFile( + handle, + event, + apc_routine, + apc_context, + io_status_block, + buf, + n, + byte_offset, + key, + dest, + )?; + } + "NtReadFile" => { + let [ + handle, + event, + apc_routine, + apc_context, + io_status_block, + buf, + n, + byte_offset, + key, + ] = this.check_shim(abi, sys_conv, link_name, args)?; + this.NtReadFile( + handle, + event, + apc_routine, + apc_context, + io_status_block, + buf, + n, + byte_offset, + key, dest, )?; } @@ -322,6 +305,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let res = this.DeleteFileW(file_name)?; this.write_scalar(res, dest)?; } + "SetFilePointerEx" => { + let [file, distance_to_move, new_file_pointer, move_method] = + this.check_shim(abi, sys_conv, link_name, args)?; + let res = + this.SetFilePointerEx(file, distance_to_move, new_file_pointer, move_method)?; + this.write_scalar(res, dest)?; + } // Allocation "HeapAlloc" => { @@ -700,12 +690,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "GetStdHandle" => { let [which] = this.check_shim(abi, sys_conv, link_name, args)?; - let which = this.read_scalar(which)?.to_i32()?; - // We just make this the identity function, so we know later in `NtWriteFile` which - // one it is. This is very fake, but libtest needs it so we cannot make it a - // std-only shim. - // FIXME: this should return real HANDLEs when io support is added - this.write_scalar(Scalar::from_target_isize(which.into(), this), dest)?; + let res = this.GetStdHandle(which)?; + this.write_scalar(res, dest)?; } "CloseHandle" => { let [handle] = this.check_shim(abi, sys_conv, link_name, args)?; diff --git a/src/shims/windows/fs.rs b/src/shims/windows/fs.rs index 0ac82d55b1..72e016c12e 100644 --- a/src/shims/windows/fs.rs +++ b/src/shims/windows/fs.rs @@ -1,5 +1,6 @@ use std::fs::{Metadata, OpenOptions}; use std::io; +use std::io::SeekFrom; use std::path::PathBuf; use std::time::SystemTime; @@ -390,6 +391,267 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } } + + fn NtWriteFile( + &mut self, + handle: &OpTy<'tcx>, // HANDLE + event: &OpTy<'tcx>, // HANDLE + apc_routine: &OpTy<'tcx>, // PIO_APC_ROUTINE + apc_ctx: &OpTy<'tcx>, // PVOID + io_status_block: &OpTy<'tcx>, // PIO_STATUS_BLOCK + buf: &OpTy<'tcx>, // PVOID + n: &OpTy<'tcx>, // ULONG + byte_offset: &OpTy<'tcx>, // PLARGE_INTEGER + key: &OpTy<'tcx>, // PULONG + dest: &MPlaceTy<'tcx>, // return type: NTSTATUS + ) -> InterpResult<'tcx, ()> { + let this = self.eval_context_mut(); + let handle = this.read_handle(handle, "NtWriteFile")?; + let event = this.read_handle(event, "NtWriteFile")?; + let apc_routine = this.read_pointer(apc_routine)?; + let apc_ctx = this.read_pointer(apc_ctx)?; + let buf = this.read_pointer(buf)?; + let count = this.read_scalar(n)?.to_u32()?; + let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer, but we only support null + let key = this.read_pointer(key)?; + let io_status_block = + this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?; + + if event != Handle::Null { + throw_unsup_format!( + "`NtWriteFile` `Event` parameter is non-null, which is unsupported" + ); + } + + if !this.ptr_is_null(apc_routine)? { + throw_unsup_format!( + "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported" + ); + } + + if !this.ptr_is_null(apc_ctx)? { + throw_unsup_format!( + "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported" + ); + } + + if byte_offset != 0 { + throw_unsup_format!( + "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported" + ); + } + + if !this.ptr_is_null(key)? { + throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported"); + } + + let fd = match handle { + Handle::File(fd) => fd, + _ => this.invalid_handle("NtWriteFile")?, + }; + + let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? }; + + // Windows writes the output code to IO_STATUS_BLOCK.Status, and number of bytes written + // to IO_STATUS_BLOCK.Information. + // The status block value and the returned value don't need to match - but + // for the cases implemented by miri so far, we can choose to decide that they do. + let io_status = { + let anon = this.project_field_named(&io_status_block, "Anonymous")?; + this.project_field_named(&anon, "Status")? + }; + let io_status_info = this.project_field_named(&io_status_block, "Information")?; + + let finish = { + let io_status = io_status.clone(); + let io_status_info = io_status_info.clone(); + let dest = dest.clone(); + callback!( + @capture<'tcx> { + count: u32, + io_status: MPlaceTy<'tcx>, + io_status_info: MPlaceTy<'tcx>, + dest: MPlaceTy<'tcx>, + } + |this, result: Result| { + match result { + Ok(read_size) => { + assert!(read_size <= count.try_into().unwrap()); + // This must fit since `count` fits. + this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?; + this.write_int(0, &io_status)?; + this.write_int(0, &dest) + } + Err(e) => { + this.write_int(0, &io_status_info)?; + let status = e.into_ntstatus(); + this.write_int(status, &io_status)?; + this.write_int(status, &dest) + } + }} + ) + }; + + desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?; + + // Return status is written to `dest` and `io_status_block` on callback completion. + interp_ok(()) + } + + fn NtReadFile( + &mut self, + handle: &OpTy<'tcx>, // HANDLE + event: &OpTy<'tcx>, // HANDLE + apc_routine: &OpTy<'tcx>, // PIO_APC_ROUTINE + apc_ctx: &OpTy<'tcx>, // PVOID + io_status_block: &OpTy<'tcx>, // PIO_STATUS_BLOCK + buf: &OpTy<'tcx>, // PVOID + n: &OpTy<'tcx>, // ULONG + byte_offset: &OpTy<'tcx>, // PLARGE_INTEGER + key: &OpTy<'tcx>, // PULONG + dest: &MPlaceTy<'tcx>, // return type: NTSTATUS + ) -> InterpResult<'tcx, ()> { + let this = self.eval_context_mut(); + let handle = this.read_handle(handle, "NtReadFile")?; + let event = this.read_handle(event, "NtReadFile")?; + let apc_routine = this.read_pointer(apc_routine)?; + let apc_ctx = this.read_pointer(apc_ctx)?; + let buf = this.read_pointer(buf)?; + let count = this.read_scalar(n)?.to_u32()?; + let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer, but we only support null + let key = this.read_pointer(key)?; + let io_status_block = + this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?; + + if event != Handle::Null { + throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported"); + } + + if !this.ptr_is_null(apc_routine)? { + throw_unsup_format!( + "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported" + ); + } + + if !this.ptr_is_null(apc_ctx)? { + throw_unsup_format!( + "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported" + ); + } + + if byte_offset != 0 { + throw_unsup_format!( + "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported" + ); + } + + if !this.ptr_is_null(key)? { + throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported"); + } + + // See NtWriteFile above for commentary on this + let io_status = { + let anon = this.project_field_named(&io_status_block, "Anonymous")?; + this.project_field_named(&anon, "Status")? + }; + let io_status_info = this.project_field_named(&io_status_block, "Information")?; + + let finish = { + let io_status = io_status.clone(); + let io_status_info = io_status_info.clone(); + let dest = dest.clone(); + callback!( + @capture<'tcx> { + count: u32, + io_status: MPlaceTy<'tcx>, + io_status_info: MPlaceTy<'tcx>, + dest: MPlaceTy<'tcx>, + } + |this, result: Result| { + match result { + Ok(read_size) => { + assert!(read_size <= count.try_into().unwrap()); + // This must fit since `count` fits. + this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?; + this.write_int(0, &io_status)?; + this.write_int(0, &dest) + } + Err(e) => { + this.write_int(0, &io_status_info)?; + let status = e.into_ntstatus(); + this.write_int(status, &io_status)?; + this.write_int(status, &dest) + } + }} + ) + }; + + let fd = match handle { + Handle::File(fd) => fd, + _ => this.invalid_handle("NtWriteFile")?, + }; + + let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? }; + + desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?; + + // See NtWriteFile for commentary on this + interp_ok(()) + } + + fn SetFilePointerEx( + &mut self, + file: &OpTy<'tcx>, // HANDLE + dist_to_move: &OpTy<'tcx>, // LARGE_INTEGER + new_fp: &OpTy<'tcx>, // PLARGE_INTEGER + move_method: &OpTy<'tcx>, // DWORD + ) -> InterpResult<'tcx, Scalar> { + // ^ Returns BOOL (i32 on Windows) + let this = self.eval_context_mut(); + let file = this.read_handle(file, "SetFilePointerEx")?; + let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?; + let new_fp_ptr = this.read_pointer(new_fp)?; + let move_method = this.read_scalar(move_method)?.to_u32()?; + + let fd = match file { + Handle::File(fd) => fd, + _ => this.invalid_handle("SetFilePointerEx")?, + }; + + let Some(desc) = this.machine.fds.get(fd) else { + throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles"); + }; + + let file_begin = this.eval_windows_u32("c", "FILE_BEGIN"); + let file_current = this.eval_windows_u32("c", "FILE_CURRENT"); + let file_end = this.eval_windows_u32("c", "FILE_END"); + + let seek = if move_method == file_begin { + SeekFrom::Start(dist_to_move.try_into().unwrap()) + } else if move_method == file_current { + SeekFrom::Current(dist_to_move) + } else if move_method == file_end { + SeekFrom::End(dist_to_move) + } else { + throw_unsup_format!("Invalid move method: {move_method}") + }; + + match desc.seek(this.machine.communicate(), seek)? { + Ok(n) => { + if !this.ptr_is_null(new_fp_ptr)? { + this.write_scalar( + Scalar::from_i64(n.try_into().unwrap()), + &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?, + )?; + } + interp_ok(this.eval_windows("c", "TRUE")) + } + Err(e) => { + this.set_last_error(e)?; + interp_ok(this.eval_windows("c", "FALSE")) + } + } + } } /// Windows FILETIME is measured in 100-nanosecs since 1601 diff --git a/src/shims/windows/handle.rs b/src/shims/windows/handle.rs index 09dac9c137..5c04271fac 100644 --- a/src/shims/windows/handle.rs +++ b/src/shims/windows/handle.rs @@ -220,6 +220,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ))) } + fn GetStdHandle(&mut self, which: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + let which = this.read_scalar(which)?.to_i32()?; + + let stdin = this.eval_windows("c", "STD_INPUT_HANDLE").to_i32()?; + let stdout = this.eval_windows("c", "STD_OUTPUT_HANDLE").to_i32()?; + let stderr = this.eval_windows("c", "STD_ERROR_HANDLE").to_i32()?; + + // These values don't mean anything on Windows, but Miri unconditionally sets them up to the + // unix in/out/err descriptors. So we take advantage of that. + // Due to the `Handle` encoding, these values will not be directly exposed to the user. + let fd_num = if which == stdin { + 0 + } else if which == stdout { + 1 + } else if which == stderr { + 2 + } else { + throw_unsup_format!("Invalid argument to `GetStdHandle`: {which}") + }; + let handle = Handle::File(fd_num); + interp_ok(handle.to_scalar(this)) + } + fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); diff --git a/test_dependencies/Cargo.toml b/test_dependencies/Cargo.toml index 653228a5e3..fa833b51fa 100644 --- a/test_dependencies/Cargo.toml +++ b/test_dependencies/Cargo.toml @@ -25,6 +25,13 @@ page_size = "0.6" tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "net", "fs", "sync", "signal", "io-util"] } [target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_Storage_FileSystem", "Win32_Security"] } +windows-sys = { version = "0.59", features = [ + "Win32_Foundation", + "Win32_System_Threading", + "Win32_Storage_FileSystem", + "Win32_Security", + "Win32_System_IO", + "Wdk_Storage_FileSystem", +] } [workspace] diff --git a/tests/fail/shims/isolated_stdin.rs b/tests/fail/shims/isolated_stdin.rs index 040c3cc216..9f809039ad 100644 --- a/tests/fail/shims/isolated_stdin.rs +++ b/tests/fail/shims/isolated_stdin.rs @@ -1,7 +1,7 @@ -//@ignore-target: windows # FIXME: stdin does not work on Windows //@error-in-other-file: `read` from stdin not available when isolation is enabled //@normalize-stderr-test: "src/sys/.*\.rs" -> "$$FILE" //@normalize-stderr-test: "\nLL \| .*" -> "" +//@normalize-stderr-test: "\n... .*" -> "" //@normalize-stderr-test: "\| +[|_^]+" -> "| ^" //@normalize-stderr-test: "\n *= note:.*" -> "" use std::io::{self, Read}; diff --git a/tests/pass-dep/shims/windows-fs.rs b/tests/pass-dep/shims/windows-fs.rs index 698ca4e0b4..4ca19046b6 100644 --- a/tests/pass-dep/shims/windows-fs.rs +++ b/tests/pass-dep/shims/windows-fs.rs @@ -2,25 +2,28 @@ //@compile-flags: -Zmiri-disable-isolation #![allow(nonstandard_style)] -use std::io::ErrorKind; +use std::io::{ErrorKind, Read, Write}; use std::os::windows::ffi::OsStrExt; +use std::os::windows::io::AsRawHandle; use std::path::Path; -use std::ptr; +use std::{fs, ptr}; #[path = "../../utils/mod.rs"] mod utils; +use windows_sys::Wdk::Storage::FileSystem::{NtReadFile, NtWriteFile}; use windows_sys::Win32::Foundation::{ CloseHandle, ERROR_ACCESS_DENIED, ERROR_ALREADY_EXISTS, ERROR_IO_DEVICE, GENERIC_READ, GENERIC_WRITE, GetLastError, RtlNtStatusToDosError, STATUS_ACCESS_DENIED, - STATUS_IO_DEVICE_ERROR, + STATUS_IO_DEVICE_ERROR, STATUS_SUCCESS, SetLastError, }; use windows_sys::Win32::Storage::FileSystem::{ BY_HANDLE_FILE_INFORMATION, CREATE_ALWAYS, CREATE_NEW, CreateFileW, DeleteFileW, - FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_BACKUP_SEMANTICS, - FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, - GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING, + 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, }; +use windows_sys::Win32::System::IO::IO_STATUS_BLOCK; fn main() { unsafe { @@ -31,6 +34,8 @@ fn main() { test_open_dir_reparse(); test_delete_file(); test_ntstatus_to_dos(); + test_file_read_write(); + test_file_seek(); } } @@ -199,13 +204,13 @@ unsafe fn test_open_dir_reparse() { unsafe fn test_delete_file() { let temp = utils::tmp().join("test_delete_file.txt"); let raw_path = to_wide_cstr(&temp); - let _ = std::fs::File::create(&temp).unwrap(); + let _ = fs::File::create(&temp).unwrap(); if DeleteFileW(raw_path.as_ptr()) == 0 { panic!("Failed to delete file"); } - match std::fs::File::open(temp) { + match fs::File::open(temp) { Ok(_) => panic!("File not deleted"), Err(e) => assert!(e.kind() == ErrorKind::NotFound, "File not deleted"), } @@ -217,6 +222,82 @@ unsafe fn test_ntstatus_to_dos() { assert_eq!(RtlNtStatusToDosError(STATUS_ACCESS_DENIED), ERROR_ACCESS_DENIED); } +unsafe fn test_file_read_write() { + let temp = utils::tmp().join("test_file_read_write.txt"); + let file = fs::File::create(&temp).unwrap(); + let handle = file.as_raw_handle(); + + // Testing NtWriteFile doesn't clobber the error + SetLastError(1234); + + let text = b"Example text!"; + let mut status = std::mem::zeroed::(); + let out = NtWriteFile( + handle, + ptr::null_mut(), + None, + ptr::null_mut(), + &mut status, + text.as_ptr().cast(), + text.len() as u32, + ptr::null_mut(), + ptr::null_mut(), + ); + + assert_eq!(out, status.Anonymous.Status); + assert_eq!(out, STATUS_SUCCESS); + assert_eq!(GetLastError(), 1234); + + let file = fs::File::open(&temp).unwrap(); + let handle = file.as_raw_handle(); + + // Testing NtReadFile doesn't clobber the error + SetLastError(1234); + + let mut buffer = vec![0; 13]; + let out = NtReadFile( + handle, + ptr::null_mut(), + None, + ptr::null_mut(), + &mut status, + buffer.as_mut_ptr().cast(), + buffer.len() as u32, + ptr::null_mut(), + ptr::null_mut(), + ); + + assert_eq!(out, status.Anonymous.Status); + assert_eq!(out, STATUS_SUCCESS); + assert_eq!(buffer, text); + assert_eq!(GetLastError(), 1234); +} + +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(); + file.write_all(b"Hello, World!\n").unwrap(); + + let handle = file.as_raw_handle(); + + if SetFilePointerEx(handle, 7, ptr::null_mut(), FILE_BEGIN) == 0 { + panic!("Failed to seek"); + } + + let mut buf = vec![0; 5]; + file.read(&mut buf).unwrap(); + assert_eq!(buf, b"World"); + + let mut pos = 0; + if SetFilePointerEx(handle, -7, &mut pos, FILE_CURRENT) == 0 { + panic!("Failed to seek"); + } + buf.truncate(2); + file.read_exact(&mut buf).unwrap(); + assert_eq!(buf, b", "); + assert_eq!(pos, 5); +} + fn to_wide_cstr(path: &Path) -> Vec { let mut raw_path = path.as_os_str().encode_wide().collect::>(); raw_path.extend([0, 0]); diff --git a/tests/pass/shims/fs.rs b/tests/pass/shims/fs.rs index d0a7f245ee..315637ff7e 100644 --- a/tests/pass/shims/fs.rs +++ b/tests/pass/shims/fs.rs @@ -17,20 +17,20 @@ mod utils; fn main() { test_path_conversion(); + test_file(); test_file_create_new(); + test_metadata(); + test_seek(); + test_errors(); + test_from_raw_os_error(); // Windows file handling is very incomplete. if cfg!(not(windows)) { - test_file(); - test_seek(); test_file_clone(); - test_metadata(); test_file_set_len(); test_file_sync(); - test_errors(); test_rename(); test_directory(); test_canonicalize(); - test_from_raw_os_error(); #[cfg(unix)] test_pread_pwrite(); }