1use std::fs::{Metadata, OpenOptions};
2use std::io;
3use std::io::SeekFrom;
4use std::path::PathBuf;
5use std::time::SystemTime;
6
7use bitflags::bitflags;
8use rustc_abi::Size;
9use rustc_target::spec::Os;
10
11use crate::shims::files::{FdId, FileDescription, FileHandle};
12use crate::shims::windows::handle::{EvalContextExt as _, Handle};
13use crate::*;
14
15#[derive(Debug)]
16pub struct DirHandle {
17 pub(crate) path: PathBuf,
18}
19
20impl FileDescription for DirHandle {
21 fn name(&self) -> &'static str {
22 "directory"
23 }
24
25 fn metadata<'tcx>(
26 &self,
27 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
28 interp_ok(Either::Left(self.path.metadata()))
29 }
30
31 fn destroy<'tcx>(
32 self,
33 _self_id: FdId,
34 _communicate_allowed: bool,
35 _ecx: &mut MiriInterpCx<'tcx>,
36 ) -> InterpResult<'tcx, io::Result<()>> {
37 interp_ok(Ok(()))
38 }
39}
40
41#[derive(Debug)]
45pub struct MetadataHandle {
46 pub(crate) meta: Metadata,
47}
48
49impl FileDescription for MetadataHandle {
50 fn name(&self) -> &'static str {
51 "metadata-only"
52 }
53
54 fn metadata<'tcx>(
55 &self,
56 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
57 interp_ok(Either::Left(Ok(self.meta.clone())))
58 }
59
60 fn destroy<'tcx>(
61 self,
62 _self_id: FdId,
63 _communicate_allowed: bool,
64 _ecx: &mut MiriInterpCx<'tcx>,
65 ) -> InterpResult<'tcx, io::Result<()>> {
66 interp_ok(Ok(()))
67 }
68}
69
70#[derive(Copy, Clone, Debug, PartialEq)]
71enum CreationDisposition {
72 CreateAlways,
73 CreateNew,
74 OpenAlways,
75 OpenExisting,
76 TruncateExisting,
77}
78
79impl CreationDisposition {
80 fn new<'tcx>(
81 value: u32,
82 ecx: &mut MiriInterpCx<'tcx>,
83 ) -> InterpResult<'tcx, CreationDisposition> {
84 let create_always = ecx.eval_windows_u32("c", "CREATE_ALWAYS");
85 let create_new = ecx.eval_windows_u32("c", "CREATE_NEW");
86 let open_always = ecx.eval_windows_u32("c", "OPEN_ALWAYS");
87 let open_existing = ecx.eval_windows_u32("c", "OPEN_EXISTING");
88 let truncate_existing = ecx.eval_windows_u32("c", "TRUNCATE_EXISTING");
89
90 let out = if value == create_always {
91 CreationDisposition::CreateAlways
92 } else if value == create_new {
93 CreationDisposition::CreateNew
94 } else if value == open_always {
95 CreationDisposition::OpenAlways
96 } else if value == open_existing {
97 CreationDisposition::OpenExisting
98 } else if value == truncate_existing {
99 CreationDisposition::TruncateExisting
100 } else {
101 throw_unsup_format!("CreateFileW: Unsupported creation disposition: {value}");
102 };
103 interp_ok(out)
104 }
105}
106
107bitflags! {
108 #[derive(PartialEq)]
109 struct FileAttributes: u32 {
110 const ZERO = 0;
111 const NORMAL = 1 << 0;
112 const BACKUP_SEMANTICS = 1 << 1;
115 const OPEN_REPARSE = 1 << 2;
119 }
120}
121
122impl FileAttributes {
123 fn new<'tcx>(
124 mut value: u32,
125 ecx: &mut MiriInterpCx<'tcx>,
126 ) -> InterpResult<'tcx, FileAttributes> {
127 let file_attribute_normal = ecx.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL");
128 let file_flag_backup_semantics = ecx.eval_windows_u32("c", "FILE_FLAG_BACKUP_SEMANTICS");
129 let file_flag_open_reparse_point =
130 ecx.eval_windows_u32("c", "FILE_FLAG_OPEN_REPARSE_POINT");
131
132 let mut out = FileAttributes::ZERO;
133 if value & file_flag_backup_semantics != 0 {
134 value &= !file_flag_backup_semantics;
135 out |= FileAttributes::BACKUP_SEMANTICS;
136 }
137 if value & file_flag_open_reparse_point != 0 {
138 value &= !file_flag_open_reparse_point;
139 out |= FileAttributes::OPEN_REPARSE;
140 }
141 if value & file_attribute_normal != 0 {
142 value &= !file_attribute_normal;
143 out |= FileAttributes::NORMAL;
144 }
145
146 if value != 0 {
147 throw_unsup_format!("CreateFileW: Unsupported flags_and_attributes: {value}");
148 }
149
150 if out == FileAttributes::ZERO {
151 out = FileAttributes::NORMAL;
153 }
154 interp_ok(out)
155 }
156}
157
158impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
159#[allow(non_snake_case)]
160pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
161 fn CreateFileW(
162 &mut self,
163 file_name: &OpTy<'tcx>, desired_access: &OpTy<'tcx>, share_mode: &OpTy<'tcx>, security_attributes: &OpTy<'tcx>, creation_disposition: &OpTy<'tcx>, flags_and_attributes: &OpTy<'tcx>, template_file: &OpTy<'tcx>, ) -> InterpResult<'tcx, Handle> {
171 use CreationDisposition::*;
173
174 let this = self.eval_context_mut();
175 this.assert_target_os(Os::Windows, "CreateFileW");
176 this.check_no_isolation("`CreateFileW`")?;
177
178 this.set_last_error(IoError::Raw(Scalar::from_i32(0)))?;
181
182 let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
183 let mut desired_access = this.read_scalar(desired_access)?.to_u32()?;
184 let share_mode = this.read_scalar(share_mode)?.to_u32()?;
185 let security_attributes = this.read_pointer(security_attributes)?;
186 let creation_disposition = this.read_scalar(creation_disposition)?.to_u32()?;
187 let flags_and_attributes = this.read_scalar(flags_and_attributes)?.to_u32()?;
188 let template_file = this.read_target_usize(template_file)?;
189
190 let generic_read = this.eval_windows_u32("c", "GENERIC_READ");
191 let generic_write = this.eval_windows_u32("c", "GENERIC_WRITE");
192
193 let file_share_delete = this.eval_windows_u32("c", "FILE_SHARE_DELETE");
194 let file_share_read = this.eval_windows_u32("c", "FILE_SHARE_READ");
195 let file_share_write = this.eval_windows_u32("c", "FILE_SHARE_WRITE");
196
197 let creation_disposition = CreationDisposition::new(creation_disposition, this)?;
198 let attributes = FileAttributes::new(flags_and_attributes, this)?;
199
200 if share_mode != (file_share_delete | file_share_read | file_share_write) {
201 throw_unsup_format!("CreateFileW: Unsupported share mode: {share_mode}");
202 }
203 if !this.ptr_is_null(security_attributes)? {
204 throw_unsup_format!("CreateFileW: Security attributes are not supported");
205 }
206
207 if attributes.contains(FileAttributes::OPEN_REPARSE) && creation_disposition == CreateAlways
208 {
209 throw_machine_stop!(TerminationInfo::Abort("Invalid CreateFileW argument combination: FILE_FLAG_OPEN_REPARSE_POINT with CREATE_ALWAYS".to_string()));
210 }
211
212 if template_file != 0 {
213 throw_unsup_format!("CreateFileW: Template files are not supported");
214 }
215
216 let is_dir = file_name.is_dir();
219
220 if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) && is_dir {
222 this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
223 return interp_ok(Handle::Invalid);
224 }
225
226 let desired_read = desired_access & generic_read != 0;
227 let desired_write = desired_access & generic_write != 0;
228
229 let mut options = OpenOptions::new();
230 if desired_read {
231 desired_access &= !generic_read;
232 options.read(true);
233 }
234 if desired_write {
235 desired_access &= !generic_write;
236 options.write(true);
237 }
238
239 if desired_access != 0 {
240 throw_unsup_format!(
241 "CreateFileW: Unsupported bits set for access mode: {desired_access:#x}"
242 );
243 }
244
245 if let CreateAlways | OpenAlways = creation_disposition
257 && file_name.exists()
258 {
259 this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
260 }
261
262 let handle = if is_dir {
263 let fd_num = this.machine.fds.insert_new(DirHandle { path: file_name });
265 Ok(Handle::File(fd_num))
266 } else if creation_disposition == OpenExisting && !(desired_read || desired_write) {
267 file_name.metadata().map(|meta| {
270 let fd_num = this.machine.fds.insert_new(MetadataHandle { meta });
271 Handle::File(fd_num)
272 })
273 } else {
274 match creation_disposition {
276 CreateAlways | OpenAlways => {
277 options.create(true);
278 if creation_disposition == CreateAlways {
279 options.truncate(true);
280 }
281 }
282 CreateNew => {
283 options.create_new(true);
284 if !desired_write {
288 options.append(true);
289 }
290 }
291 OpenExisting => {} TruncateExisting => {
293 options.truncate(true);
294 }
295 }
296
297 options.open(file_name).map(|file| {
298 let fd_num =
299 this.machine.fds.insert_new(FileHandle { file, writable: desired_write });
300 Handle::File(fd_num)
301 })
302 };
303
304 match handle {
305 Ok(handle) => interp_ok(handle),
306 Err(e) => {
307 this.set_last_error(e)?;
308 interp_ok(Handle::Invalid)
309 }
310 }
311 }
312
313 fn GetFileInformationByHandle(
314 &mut self,
315 file: &OpTy<'tcx>, file_information: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
318 let this = self.eval_context_mut();
320 this.assert_target_os(Os::Windows, "GetFileInformationByHandle");
321 this.check_no_isolation("`GetFileInformationByHandle`")?;
322
323 let file = this.read_handle(file, "GetFileInformationByHandle")?;
324 let file_information = this.deref_pointer_as(
325 file_information,
326 this.windows_ty_layout("BY_HANDLE_FILE_INFORMATION"),
327 )?;
328
329 let Handle::File(fd_num) = file else { this.invalid_handle("GetFileInformationByHandle")? };
330
331 let Some(desc) = this.machine.fds.get(fd_num) else {
332 this.invalid_handle("GetFileInformationByHandle")?
333 };
334
335 let metadata = match desc.metadata()? {
336 Either::Left(Ok(meta)) => meta,
337 Either::Left(Err(e)) => {
338 this.set_last_error(e)?;
339 return interp_ok(this.eval_windows("c", "FALSE"));
340 }
341 Either::Right(_mode) =>
342 throw_unsup_format!(
343 "`GetFileInformationByHandle` is not supported on non-file-backed handles"
344 ),
345 };
346
347 let size = metadata.len();
348
349 let file_type = metadata.file_type();
350 let attributes = if file_type.is_dir() {
351 this.eval_windows_u32("c", "FILE_ATTRIBUTE_DIRECTORY")
352 } else if file_type.is_file() {
353 this.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL")
354 } else {
355 this.eval_windows_u32("c", "FILE_ATTRIBUTE_DEVICE")
356 };
357
358 let created = extract_windows_epoch(this, metadata.created())?.unwrap_or((0, 0));
362 let accessed = extract_windows_epoch(this, metadata.accessed())?.unwrap_or((0, 0));
363 let written = extract_windows_epoch(this, metadata.modified())?.unwrap_or((0, 0));
364
365 this.write_int_fields_named(&[("dwFileAttributes", attributes.into())], &file_information)?;
366 write_filetime_field(this, &file_information, "ftCreationTime", created)?;
367 write_filetime_field(this, &file_information, "ftLastAccessTime", accessed)?;
368 write_filetime_field(this, &file_information, "ftLastWriteTime", written)?;
369 this.write_int_fields_named(
370 &[
371 ("dwVolumeSerialNumber", 0),
372 ("nFileSizeHigh", (size >> 32).into()),
373 ("nFileSizeLow", (size & 0xFFFFFFFF).into()),
374 ("nNumberOfLinks", 1),
375 ("nFileIndexHigh", 0),
376 ("nFileIndexLow", 0),
377 ],
378 &file_information,
379 )?;
380
381 interp_ok(this.eval_windows("c", "TRUE"))
382 }
383
384 fn SetFileInformationByHandle(
385 &mut self,
386 file: &OpTy<'tcx>, class: &OpTy<'tcx>, file_information: &OpTy<'tcx>, buffer_size: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
391 let this = self.eval_context_mut();
393 this.assert_target_os(Os::Windows, "SetFileInformationByHandle");
394 this.check_no_isolation("`SetFileInformationByHandle`")?;
395
396 let class = this.read_scalar(class)?.to_u32()?;
397 let buffer_size = this.read_scalar(buffer_size)?.to_u32()?;
398 let file_information = this.read_pointer(file_information)?;
399 this.check_ptr_access(
400 file_information,
401 Size::from_bytes(buffer_size),
402 CheckInAllocMsg::MemoryAccess,
403 )?;
404
405 let file = this.read_handle(file, "SetFileInformationByHandle")?;
406 let Handle::File(fd_num) = file else { this.invalid_handle("SetFileInformationByHandle")? };
407 let Some(desc) = this.machine.fds.get(fd_num) else {
408 this.invalid_handle("SetFileInformationByHandle")?
409 };
410 let file = desc.downcast::<FileHandle>().ok_or_else(|| {
411 err_unsup_format!(
412 "`SetFileInformationByHandle` is only supported on file-backed file descriptors"
413 )
414 })?;
415
416 if class == this.eval_windows_u32("c", "FileEndOfFileInfo") {
417 let place = this
418 .ptr_to_mplace(file_information, this.windows_ty_layout("FILE_END_OF_FILE_INFO"));
419 let new_len =
420 this.read_scalar(&this.project_field_named(&place, "EndOfFile")?)?.to_i64()?;
421 match file.file.set_len(new_len.try_into().unwrap()) {
422 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
423 Err(e) => {
424 this.set_last_error(e)?;
425 interp_ok(this.eval_windows("c", "FALSE"))
426 }
427 }
428 } else if class == this.eval_windows_u32("c", "FileAllocationInfo") {
429 let place = this
436 .ptr_to_mplace(file_information, this.windows_ty_layout("FILE_ALLOCATION_INFO"));
437 let new_alloc_size: u64 = this
438 .read_scalar(&this.project_field_named(&place, "AllocationSize")?)?
439 .to_i64()?
440 .try_into()
441 .unwrap();
442 let old_len = match file.file.metadata() {
443 Ok(m) => m.len(),
444 Err(e) => {
445 this.set_last_error(e)?;
446 return interp_ok(this.eval_windows("c", "FALSE"));
447 }
448 };
449 if new_alloc_size < old_len {
450 match file.file.set_len(new_alloc_size) {
451 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
452 Err(e) => {
453 this.set_last_error(e)?;
454 interp_ok(this.eval_windows("c", "FALSE"))
455 }
456 }
457 } else {
458 interp_ok(this.eval_windows("c", "TRUE"))
459 }
460 } else {
461 throw_unsup_format!(
462 "SetFileInformationByHandle: Unsupported `FileInformationClass` value {}",
463 class
464 )
465 }
466 }
467
468 fn FlushFileBuffers(
469 &mut self,
470 file: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
472 let this = self.eval_context_mut();
474 this.assert_target_os(Os::Windows, "FlushFileBuffers");
475
476 let file = this.read_handle(file, "FlushFileBuffers")?;
477 let Handle::File(fd_num) = file else { this.invalid_handle("FlushFileBuffers")? };
478 let Some(desc) = this.machine.fds.get(fd_num) else {
479 this.invalid_handle("FlushFileBuffers")?
480 };
481 let file = desc.downcast::<FileHandle>().ok_or_else(|| {
482 err_unsup_format!(
483 "`FlushFileBuffers` is only supported on file-backed file descriptors"
484 )
485 })?;
486
487 if !file.writable {
488 this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
489 return interp_ok(this.eval_windows("c", "FALSE"));
490 }
491
492 match file.file.sync_all() {
493 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
494 Err(e) => {
495 this.set_last_error(e)?;
496 interp_ok(this.eval_windows("c", "FALSE"))
497 }
498 }
499 }
500
501 fn MoveFileExW(
502 &mut self,
503 existing_name: &OpTy<'tcx>,
504 new_name: &OpTy<'tcx>,
505 flags: &OpTy<'tcx>,
506 ) -> InterpResult<'tcx, Scalar> {
507 let this = self.eval_context_mut();
508
509 let existing_name = this.read_path_from_wide_str(this.read_pointer(existing_name)?)?;
510 let new_name = this.read_path_from_wide_str(this.read_pointer(new_name)?)?;
511
512 let flags = this.read_scalar(flags)?.to_u32()?;
513
514 let movefile_replace_existing = this.eval_windows_u32("c", "MOVEFILE_REPLACE_EXISTING");
517
518 if flags != movefile_replace_existing {
519 throw_unsup_format!("MoveFileExW: Unsupported `dwFlags` value {}", flags);
520 }
521
522 match std::fs::rename(existing_name, new_name) {
523 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
524 Err(e) => {
525 this.set_last_error(e)?;
526 interp_ok(this.eval_windows("c", "FALSE"))
527 }
528 }
529 }
530
531 fn DeleteFileW(
532 &mut self,
533 file_name: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
535 let this = self.eval_context_mut();
537 this.assert_target_os(Os::Windows, "DeleteFileW");
538 this.check_no_isolation("`DeleteFileW`")?;
539
540 let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
541 match std::fs::remove_file(file_name) {
542 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
543 Err(e) => {
544 this.set_last_error(e)?;
545 interp_ok(this.eval_windows("c", "FALSE"))
546 }
547 }
548 }
549
550 fn NtWriteFile(
551 &mut self,
552 handle: &OpTy<'tcx>, event: &OpTy<'tcx>, apc_routine: &OpTy<'tcx>, apc_ctx: &OpTy<'tcx>, io_status_block: &OpTy<'tcx>, buf: &OpTy<'tcx>, n: &OpTy<'tcx>, byte_offset: &OpTy<'tcx>, key: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> {
563 let this = self.eval_context_mut();
564 let handle = this.read_handle(handle, "NtWriteFile")?;
565 let event = this.read_handle(event, "NtWriteFile")?;
566 let apc_routine = this.read_pointer(apc_routine)?;
567 let apc_ctx = this.read_pointer(apc_ctx)?;
568 let buf = this.read_pointer(buf)?;
569 let count = this.read_scalar(n)?.to_u32()?;
570 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
572 let io_status_block =
573 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
574
575 if event != Handle::Null {
576 throw_unsup_format!(
577 "`NtWriteFile` `Event` parameter is non-null, which is unsupported"
578 );
579 }
580
581 if !this.ptr_is_null(apc_routine)? {
582 throw_unsup_format!(
583 "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported"
584 );
585 }
586
587 if !this.ptr_is_null(apc_ctx)? {
588 throw_unsup_format!(
589 "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported"
590 );
591 }
592
593 if byte_offset != 0 {
594 throw_unsup_format!(
595 "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
596 );
597 }
598
599 if !this.ptr_is_null(key)? {
600 throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported");
601 }
602
603 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
604
605 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? };
606
607 let io_status = {
612 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
613 this.project_field_named(&anon, "Status")?
614 };
615 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
616
617 let finish = {
621 let io_status = io_status.clone();
622 let io_status_info = io_status_info.clone();
623 let dest = dest.clone();
624 callback!(
625 @capture<'tcx> {
626 count: u32,
627 io_status: MPlaceTy<'tcx>,
628 io_status_info: MPlaceTy<'tcx>,
629 dest: MPlaceTy<'tcx>,
630 }
631 |this, result: Result<usize, IoError>| {
632 match result {
633 Ok(read_size) => {
634 assert!(read_size <= count.try_into().unwrap());
635 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
637 this.write_int(0, &io_status)?;
638 this.write_int(0, &dest)
639 }
640 Err(e) => {
641 this.write_int(0, &io_status_info)?;
642 let status = e.into_ntstatus();
643 this.write_int(status, &io_status)?;
644 this.write_int(status, &dest)
645 }
646 }}
647 )
648 };
649 desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
650
651 interp_ok(())
653 }
654
655 fn NtReadFile(
656 &mut self,
657 handle: &OpTy<'tcx>, event: &OpTy<'tcx>, apc_routine: &OpTy<'tcx>, apc_ctx: &OpTy<'tcx>, io_status_block: &OpTy<'tcx>, buf: &OpTy<'tcx>, n: &OpTy<'tcx>, byte_offset: &OpTy<'tcx>, key: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> {
668 let this = self.eval_context_mut();
669 let handle = this.read_handle(handle, "NtReadFile")?;
670 let event = this.read_handle(event, "NtReadFile")?;
671 let apc_routine = this.read_pointer(apc_routine)?;
672 let apc_ctx = this.read_pointer(apc_ctx)?;
673 let buf = this.read_pointer(buf)?;
674 let count = this.read_scalar(n)?.to_u32()?;
675 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
677 let io_status_block =
678 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
679
680 if event != Handle::Null {
681 throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
682 }
683
684 if !this.ptr_is_null(apc_routine)? {
685 throw_unsup_format!(
686 "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
687 );
688 }
689
690 if !this.ptr_is_null(apc_ctx)? {
691 throw_unsup_format!(
692 "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
693 );
694 }
695
696 if byte_offset != 0 {
697 throw_unsup_format!(
698 "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
699 );
700 }
701
702 if !this.ptr_is_null(key)? {
703 throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
704 }
705
706 let io_status = {
708 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
709 this.project_field_named(&anon, "Status")?
710 };
711 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
712
713 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
714
715 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
716
717 let finish = {
721 let io_status = io_status.clone();
722 let io_status_info = io_status_info.clone();
723 let dest = dest.clone();
724 callback!(
725 @capture<'tcx> {
726 count: u32,
727 io_status: MPlaceTy<'tcx>,
728 io_status_info: MPlaceTy<'tcx>,
729 dest: MPlaceTy<'tcx>,
730 }
731 |this, result: Result<usize, IoError>| {
732 match result {
733 Ok(read_size) => {
734 assert!(read_size <= count.try_into().unwrap());
735 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
737 this.write_int(0, &io_status)?;
738 this.write_int(0, &dest)
739 }
740 Err(e) => {
741 this.write_int(0, &io_status_info)?;
742 let status = e.into_ntstatus();
743 this.write_int(status, &io_status)?;
744 this.write_int(status, &dest)
745 }
746 }}
747 )
748 };
749 desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
750
751 interp_ok(())
753 }
754
755 fn SetFilePointerEx(
756 &mut self,
757 file: &OpTy<'tcx>, dist_to_move: &OpTy<'tcx>, new_fp: &OpTy<'tcx>, move_method: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
762 let this = self.eval_context_mut();
764 let file = this.read_handle(file, "SetFilePointerEx")?;
765 let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?;
766 let new_fp_ptr = this.read_pointer(new_fp)?;
767 let move_method = this.read_scalar(move_method)?.to_u32()?;
768
769 let Handle::File(fd) = file else { this.invalid_handle("SetFilePointerEx")? };
770
771 let Some(desc) = this.machine.fds.get(fd) else {
772 throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
773 };
774
775 let file_begin = this.eval_windows_u32("c", "FILE_BEGIN");
776 let file_current = this.eval_windows_u32("c", "FILE_CURRENT");
777 let file_end = this.eval_windows_u32("c", "FILE_END");
778
779 let seek = if move_method == file_begin {
780 SeekFrom::Start(dist_to_move.try_into().unwrap())
781 } else if move_method == file_current {
782 SeekFrom::Current(dist_to_move)
783 } else if move_method == file_end {
784 SeekFrom::End(dist_to_move)
785 } else {
786 throw_unsup_format!("Invalid move method: {move_method}")
787 };
788
789 match desc.seek(this.machine.communicate(), seek)? {
790 Ok(n) => {
791 if !this.ptr_is_null(new_fp_ptr)? {
792 this.write_scalar(
793 Scalar::from_i64(n.try_into().unwrap()),
794 &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?,
795 )?;
796 }
797 interp_ok(this.eval_windows("c", "TRUE"))
798 }
799 Err(e) => {
800 this.set_last_error(e)?;
801 interp_ok(this.eval_windows("c", "FALSE"))
802 }
803 }
804 }
805}
806
807fn extract_windows_epoch<'tcx>(
809 ecx: &MiriInterpCx<'tcx>,
810 time: io::Result<SystemTime>,
811) -> InterpResult<'tcx, Option<(u32, u32)>> {
812 match time.ok() {
813 Some(time) => {
814 let duration = ecx.system_time_since_windows_epoch(&time)?;
815 let duration_ticks = ecx.windows_ticks_for(duration)?;
816 #[expect(clippy::as_conversions)]
817 interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
818 }
819 None => interp_ok(None),
820 }
821}
822
823fn write_filetime_field<'tcx>(
824 cx: &mut MiriInterpCx<'tcx>,
825 val: &MPlaceTy<'tcx>,
826 name: &str,
827 (low, high): (u32, u32),
828) -> InterpResult<'tcx> {
829 cx.write_int_fields_named(
830 &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
831 &cx.project_field_named(val, name)?,
832 )
833}