1use std::fs::{self, Dir};
2use std::io;
3use std::io::SeekFrom;
4use std::time::SystemTime;
5
6use bitflags::bitflags;
7use rustc_abi::Size;
8use rustc_target::spec::Os;
9
10use crate::shims::files::{DirHandle, FileHandle};
11use crate::shims::windows::handle::{EvalContextExt as _, Handle};
12use crate::*;
13
14#[derive(Copy, Clone, Debug, PartialEq)]
15enum CreationDisposition {
16 CreateAlways,
18 CreateNew,
20 OpenAlways,
22 OpenExisting,
24 TruncateExisting,
26}
27
28impl CreationDisposition {
29 fn new<'tcx>(
30 value: u32,
31 ecx: &mut MiriInterpCx<'tcx>,
32 ) -> InterpResult<'tcx, CreationDisposition> {
33 let create_always = ecx.eval_windows_u32("c", "CREATE_ALWAYS");
34 let create_new = ecx.eval_windows_u32("c", "CREATE_NEW");
35 let open_always = ecx.eval_windows_u32("c", "OPEN_ALWAYS");
36 let open_existing = ecx.eval_windows_u32("c", "OPEN_EXISTING");
37 let truncate_existing = ecx.eval_windows_u32("c", "TRUNCATE_EXISTING");
38
39 let out = if value == create_always {
40 CreationDisposition::CreateAlways
41 } else if value == create_new {
42 CreationDisposition::CreateNew
43 } else if value == open_always {
44 CreationDisposition::OpenAlways
45 } else if value == open_existing {
46 CreationDisposition::OpenExisting
47 } else if value == truncate_existing {
48 CreationDisposition::TruncateExisting
49 } else {
50 throw_unsup_format!("CreateFileW: Unsupported creation disposition: {value}");
51 };
52 interp_ok(out)
53 }
54}
55
56bitflags! {
57 #[derive(PartialEq)]
58 struct FileAttributes: u32 {
59 const ZERO = 0;
60 const NORMAL = 1 << 0;
61 const BACKUP_SEMANTICS = 1 << 1;
64 const OPEN_REPARSE = 1 << 2;
68 }
69}
70
71impl FileAttributes {
72 fn new<'tcx>(
73 mut value: u32,
74 ecx: &mut MiriInterpCx<'tcx>,
75 ) -> InterpResult<'tcx, FileAttributes> {
76 let file_attribute_normal = ecx.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL");
77 let file_flag_backup_semantics = ecx.eval_windows_u32("c", "FILE_FLAG_BACKUP_SEMANTICS");
78 let file_flag_open_reparse_point =
79 ecx.eval_windows_u32("c", "FILE_FLAG_OPEN_REPARSE_POINT");
80
81 let mut out = FileAttributes::ZERO;
82 if value & file_flag_backup_semantics != 0 {
83 value &= !file_flag_backup_semantics;
84 out |= FileAttributes::BACKUP_SEMANTICS;
85 }
86 if value & file_flag_open_reparse_point != 0 {
87 value &= !file_flag_open_reparse_point;
88 out |= FileAttributes::OPEN_REPARSE;
89 }
90 if value & file_attribute_normal != 0 {
91 value &= !file_attribute_normal;
92 out |= FileAttributes::NORMAL;
93 }
94
95 if value != 0 {
96 throw_unsup_format!("CreateFileW: Unsupported flags_and_attributes: {value}");
97 }
98
99 if out == FileAttributes::ZERO {
100 out = FileAttributes::NORMAL;
102 }
103 interp_ok(out)
104 }
105}
106
107impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
108#[allow(non_snake_case)]
109pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
110 fn CreateFileW(
111 &mut self,
112 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> {
120 use CreationDisposition::*;
122
123 let this = self.eval_context_mut();
124 this.assert_target_os(Os::Windows, "CreateFileW");
125 this.check_no_isolation("`CreateFileW`")?;
126
127 this.set_last_error(IoError::Raw(Scalar::from_i32(0)))?;
130
131 let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
132 let mut desired_access = this.read_scalar(desired_access)?.to_u32()?;
133 let share_mode = this.read_scalar(share_mode)?.to_u32()?;
134 let security_attributes = this.read_pointer(security_attributes)?;
135 let creation_disposition = this.read_scalar(creation_disposition)?.to_u32()?;
136 let flags_and_attributes = this.read_scalar(flags_and_attributes)?.to_u32()?;
137 let template_file = this.read_target_usize(template_file)?;
138
139 let generic_read = this.eval_windows_u32("c", "GENERIC_READ");
140 let generic_write = this.eval_windows_u32("c", "GENERIC_WRITE");
141
142 let file_share_delete = this.eval_windows_u32("c", "FILE_SHARE_DELETE");
143 let file_share_read = this.eval_windows_u32("c", "FILE_SHARE_READ");
144 let file_share_write = this.eval_windows_u32("c", "FILE_SHARE_WRITE");
145
146 let creation_disposition = CreationDisposition::new(creation_disposition, this)?;
147 let attributes = FileAttributes::new(flags_and_attributes, this)?;
148
149 if share_mode != (file_share_delete | file_share_read | file_share_write) {
150 throw_unsup_format!("CreateFileW: Unsupported share mode: {share_mode}");
151 }
152 if !this.ptr_is_null(security_attributes)? {
153 throw_unsup_format!("CreateFileW: Security attributes are not supported");
154 }
155
156 if attributes.contains(FileAttributes::OPEN_REPARSE) && creation_disposition == CreateAlways
157 {
158 throw_machine_stop!(TerminationInfo::Abort("Invalid CreateFileW argument combination: FILE_FLAG_OPEN_REPARSE_POINT with CREATE_ALWAYS".to_string()));
159 }
160 if attributes.contains(FileAttributes::OPEN_REPARSE) && creation_disposition != CreateNew {
161 throw_unsup_format!(
164 "CreateFileW: FILE_FLAG_OPEN_REPARSE_POINT is only supported with CREATE_NEW"
165 );
166 }
167
168 if template_file != 0 {
169 throw_unsup_format!("CreateFileW: Template files are not supported");
170 }
171
172 let mut desired_read = false;
174 if desired_access & generic_read != 0 {
175 desired_read = true;
176 desired_access &= !generic_read;
177 }
178 let mut desired_write = false;
179 if desired_access & generic_write != 0 {
180 desired_write = true;
181 desired_access &= !generic_write;
182 }
183
184 if desired_access != 0 {
185 throw_unsup_format!(
186 "CreateFileW: Unsupported bits set for access mode: {desired_access:#x}"
187 );
188 }
189
190 let mut counter = 0u32;
193 loop {
194 if counter >= 100 {
195 panic!(
196 "CreateFileW seems stuck in an infinite retry loop. \
197 If you can reproduce this, please file a bug."
198 );
199 }
200 counter = counter.strict_add(1);
201
202 let is_dir = file_name.is_dir();
206
207 if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) && is_dir {
209 this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
210 return interp_ok(Handle::Invalid);
211 }
212
213 if is_dir {
214 let dir = match Dir::open(&file_name) {
218 Ok(dir) => dir,
219 Err(e) => {
220 if e.kind() == io::ErrorKind::NotADirectory {
221 continue;
223 }
224 this.set_last_error(e)?;
225 return interp_ok(Handle::Invalid);
226 }
227 };
228 if !dir.metadata().unwrap().is_dir() {
229 continue;
231 }
232
233 if let CreateAlways | OpenAlways = creation_disposition {
235 this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
236 }
237
238 let fd_num = this.machine.fds.insert_new(DirHandle { dir });
239 return interp_ok(Handle::File(fd_num));
240 } else {
241 let exists_already = file_name.exists();
253
254 let mut options = fs::OpenOptions::new();
256 options.read(desired_read);
257 options.write(desired_write);
258 match creation_disposition {
259 CreateAlways | OpenAlways => {
260 if !exists_already {
267 options.create_new(true);
268 }
269 if creation_disposition == CreateAlways {
270 options.truncate(true);
271 }
272 }
273 CreateNew => {
274 options.create_new(true);
275 if !desired_write {
279 options.append(true);
280 }
281 }
282 OpenExisting => {
283 if !desired_read && !desired_write {
284 options.read(true);
292 }
293 }
294 TruncateExisting => {
295 options.truncate(true);
296 }
297 }
298
299 let file = match options.open(&file_name) {
300 Ok(file) => file,
301 Err(e) => {
302 let kind = e.kind();
303 if kind == io::ErrorKind::IsADirectory {
304 continue;
306 }
307 if exists_already && kind == io::ErrorKind::NotFound {
308 continue;
310 }
311 if !exists_already && kind == io::ErrorKind::AlreadyExists {
312 continue;
314 }
315 this.set_last_error(e)?;
316 return interp_ok(Handle::Invalid);
317 }
318 };
319 if file.metadata().unwrap().is_dir() {
320 continue;
322 }
323
324 if let CreateAlways | OpenAlways = creation_disposition
326 && exists_already
327 {
328 this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
329 }
330 let fd_num = this.machine.fds.insert_new(FileHandle {
331 file,
332 writable: desired_write,
333 readable: desired_read,
334 });
335 return interp_ok(Handle::File(fd_num));
336 }
337 }
338 }
339
340 fn GetFileInformationByHandle(
341 &mut self,
342 file: &OpTy<'tcx>, file_information: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
345 let this = self.eval_context_mut();
347 this.assert_target_os(Os::Windows, "GetFileInformationByHandle");
348 this.check_no_isolation("`GetFileInformationByHandle`")?;
349
350 let file = this.read_handle(file, "GetFileInformationByHandle")?;
351 let file_information = this.deref_pointer_as(
352 file_information,
353 this.windows_ty_layout("BY_HANDLE_FILE_INFORMATION"),
354 )?;
355
356 let Handle::File(fd_num) = file else { this.invalid_handle("GetFileInformationByHandle")? };
357
358 let Some(desc) = this.machine.fds.get(fd_num) else {
359 this.invalid_handle("GetFileInformationByHandle")?
360 };
361
362 let metadata = match desc.metadata()? {
363 Either::Left(Ok(meta)) => meta,
364 Either::Left(Err(e)) => {
365 this.set_last_error(e)?;
366 return interp_ok(this.eval_windows("c", "FALSE"));
367 }
368 Either::Right(_mode) =>
369 throw_unsup_format!(
370 "`GetFileInformationByHandle` is not supported on non-file-backed handles"
371 ),
372 };
373
374 let size = metadata.len();
375
376 let file_type = metadata.file_type();
377 let attributes = if file_type.is_dir() {
378 this.eval_windows_u32("c", "FILE_ATTRIBUTE_DIRECTORY")
379 } else if file_type.is_file() {
380 this.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL")
381 } else {
382 this.eval_windows_u32("c", "FILE_ATTRIBUTE_DEVICE")
383 };
384
385 let created = extract_windows_epoch(this, metadata.created())?.unwrap_or((0, 0));
389 let accessed = extract_windows_epoch(this, metadata.accessed())?.unwrap_or((0, 0));
390 let written = extract_windows_epoch(this, metadata.modified())?.unwrap_or((0, 0));
391
392 this.write_int_fields_named(&[("dwFileAttributes", attributes.into())], &file_information)?;
393 write_filetime_field(this, &file_information, "ftCreationTime", created)?;
394 write_filetime_field(this, &file_information, "ftLastAccessTime", accessed)?;
395 write_filetime_field(this, &file_information, "ftLastWriteTime", written)?;
396 this.write_int_fields_named(
397 &[
398 ("dwVolumeSerialNumber", 0),
399 ("nFileSizeHigh", (size >> 32).into()),
400 ("nFileSizeLow", (size & 0xFFFFFFFF).into()),
401 ("nNumberOfLinks", 1),
402 ("nFileIndexHigh", 0),
403 ("nFileIndexLow", 0),
404 ],
405 &file_information,
406 )?;
407
408 interp_ok(this.eval_windows("c", "TRUE"))
409 }
410
411 fn SetFileInformationByHandle(
412 &mut self,
413 file: &OpTy<'tcx>, class: &OpTy<'tcx>, file_information: &OpTy<'tcx>, buffer_size: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
418 let this = self.eval_context_mut();
420 this.assert_target_os(Os::Windows, "SetFileInformationByHandle");
421 this.check_no_isolation("`SetFileInformationByHandle`")?;
422
423 let class = this.read_scalar(class)?.to_u32()?;
424 let buffer_size = this.read_scalar(buffer_size)?.to_u32()?;
425 let file_information = this.read_pointer(file_information)?;
426 this.check_ptr_access(
427 file_information,
428 Size::from_bytes(buffer_size),
429 CheckInAllocMsg::MemoryAccess,
430 )?;
431
432 let file = this.read_handle(file, "SetFileInformationByHandle")?;
433 let Handle::File(fd_num) = file else { this.invalid_handle("SetFileInformationByHandle")? };
434 let Some(desc) = this.machine.fds.get(fd_num) else {
435 this.invalid_handle("SetFileInformationByHandle")?
436 };
437 let file = desc.downcast::<FileHandle>().ok_or_else(|| {
438 err_unsup_format!(
439 "`SetFileInformationByHandle` is only supported on file-backed file descriptors"
440 )
441 })?;
442
443 if class == this.eval_windows_u32("c", "FileEndOfFileInfo") {
444 let place = this
445 .ptr_to_mplace(file_information, this.windows_ty_layout("FILE_END_OF_FILE_INFO"));
446 let new_len =
447 this.read_scalar(&this.project_field_named(&place, "EndOfFile")?)?.to_i64()?;
448 match file.file.set_len(new_len.try_into().unwrap()) {
449 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
450 Err(e) => {
451 this.set_last_error(e)?;
452 interp_ok(this.eval_windows("c", "FALSE"))
453 }
454 }
455 } else if class == this.eval_windows_u32("c", "FileAllocationInfo") {
456 let place = this
463 .ptr_to_mplace(file_information, this.windows_ty_layout("FILE_ALLOCATION_INFO"));
464 let new_alloc_size: u64 = this
465 .read_scalar(&this.project_field_named(&place, "AllocationSize")?)?
466 .to_i64()?
467 .try_into()
468 .unwrap();
469 let old_len = match file.file.metadata() {
470 Ok(m) => m.len(),
471 Err(e) => {
472 this.set_last_error(e)?;
473 return interp_ok(this.eval_windows("c", "FALSE"));
474 }
475 };
476 if new_alloc_size < old_len {
477 match file.file.set_len(new_alloc_size) {
478 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
479 Err(e) => {
480 this.set_last_error(e)?;
481 interp_ok(this.eval_windows("c", "FALSE"))
482 }
483 }
484 } else {
485 interp_ok(this.eval_windows("c", "TRUE"))
486 }
487 } else {
488 throw_unsup_format!(
489 "SetFileInformationByHandle: Unsupported `FileInformationClass` value {}",
490 class
491 )
492 }
493 }
494
495 fn FlushFileBuffers(
496 &mut self,
497 file: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
499 let this = self.eval_context_mut();
501 this.assert_target_os(Os::Windows, "FlushFileBuffers");
502
503 let file = this.read_handle(file, "FlushFileBuffers")?;
504 let Handle::File(fd_num) = file else { this.invalid_handle("FlushFileBuffers")? };
505 let Some(desc) = this.machine.fds.get(fd_num) else {
506 this.invalid_handle("FlushFileBuffers")?
507 };
508 let file = desc.downcast::<FileHandle>().ok_or_else(|| {
509 err_unsup_format!(
510 "`FlushFileBuffers` is only supported on file-backed file descriptors"
511 )
512 })?;
513
514 if !file.writable {
515 this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
516 return interp_ok(this.eval_windows("c", "FALSE"));
517 }
518
519 match file.file.sync_all() {
520 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
521 Err(e) => {
522 this.set_last_error(e)?;
523 interp_ok(this.eval_windows("c", "FALSE"))
524 }
525 }
526 }
527
528 fn MoveFileExW(
529 &mut self,
530 existing_name: &OpTy<'tcx>,
531 new_name: &OpTy<'tcx>,
532 flags: &OpTy<'tcx>,
533 ) -> InterpResult<'tcx, Scalar> {
534 let this = self.eval_context_mut();
535
536 let existing_name = this.read_path_from_wide_str(this.read_pointer(existing_name)?)?;
537 let new_name = this.read_path_from_wide_str(this.read_pointer(new_name)?)?;
538
539 let flags = this.read_scalar(flags)?.to_u32()?;
540
541 let movefile_replace_existing = this.eval_windows_u32("c", "MOVEFILE_REPLACE_EXISTING");
544
545 if flags != movefile_replace_existing {
546 throw_unsup_format!("MoveFileExW: Unsupported `dwFlags` value {}", flags);
547 }
548
549 match std::fs::rename(existing_name, new_name) {
550 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
551 Err(e) => {
552 this.set_last_error(e)?;
553 interp_ok(this.eval_windows("c", "FALSE"))
554 }
555 }
556 }
557
558 fn DeleteFileW(
559 &mut self,
560 file_name: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
562 let this = self.eval_context_mut();
564 this.assert_target_os(Os::Windows, "DeleteFileW");
565 this.check_no_isolation("`DeleteFileW`")?;
566
567 let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
568 match std::fs::remove_file(file_name) {
569 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
570 Err(e) => {
571 this.set_last_error(e)?;
572 interp_ok(this.eval_windows("c", "FALSE"))
573 }
574 }
575 }
576
577 fn NtWriteFile(
578 &mut self,
579 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, ()> {
590 let this = self.eval_context_mut();
591 let handle = this.read_handle(handle, "NtWriteFile")?;
592 let event = this.read_handle(event, "NtWriteFile")?;
593 let apc_routine = this.read_pointer(apc_routine)?;
594 let apc_ctx = this.read_pointer(apc_ctx)?;
595 let buf = this.read_pointer(buf)?;
596 let count = this.read_scalar(n)?.to_u32()?;
597 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
599 let io_status_block =
600 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
601
602 if event != Handle::Null {
603 throw_unsup_format!(
604 "`NtWriteFile` `Event` parameter is non-null, which is unsupported"
605 );
606 }
607
608 if !this.ptr_is_null(apc_routine)? {
609 throw_unsup_format!(
610 "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported"
611 );
612 }
613
614 if !this.ptr_is_null(apc_ctx)? {
615 throw_unsup_format!(
616 "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported"
617 );
618 }
619
620 if byte_offset != 0 {
621 throw_unsup_format!(
622 "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
623 );
624 }
625
626 if !this.ptr_is_null(key)? {
627 throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported");
628 }
629
630 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
631
632 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? };
633
634 let io_status = {
639 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
640 this.project_field_named(&anon, "Status")?
641 };
642 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
643
644 let finish = {
648 let io_status = io_status.clone();
649 let io_status_info = io_status_info.clone();
650 let dest = dest.clone();
651 callback!(
652 @capture<'tcx> {
653 count: u32,
654 io_status: MPlaceTy<'tcx>,
655 io_status_info: MPlaceTy<'tcx>,
656 dest: MPlaceTy<'tcx>,
657 }
658 |this, result: Result<usize, IoError>| {
659 match result {
660 Ok(read_size) => {
661 assert!(read_size <= count.try_into().unwrap());
662 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
664 this.write_int(0, &io_status)?;
665 this.write_int(0, &dest)
666 }
667 Err(e) => {
668 this.write_int(0, &io_status_info)?;
669 let status = e.into_ntstatus();
670 this.write_int(status, &io_status)?;
671 this.write_int(status, &dest)
672 }
673 }}
674 )
675 };
676 desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
677
678 interp_ok(())
680 }
681
682 fn NtReadFile(
683 &mut self,
684 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, ()> {
695 let this = self.eval_context_mut();
696 let handle = this.read_handle(handle, "NtReadFile")?;
697 let event = this.read_handle(event, "NtReadFile")?;
698 let apc_routine = this.read_pointer(apc_routine)?;
699 let apc_ctx = this.read_pointer(apc_ctx)?;
700 let buf = this.read_pointer(buf)?;
701 let count = this.read_scalar(n)?.to_u32()?;
702 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
704 let io_status_block =
705 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
706
707 if event != Handle::Null {
708 throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
709 }
710
711 if !this.ptr_is_null(apc_routine)? {
712 throw_unsup_format!(
713 "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
714 );
715 }
716
717 if !this.ptr_is_null(apc_ctx)? {
718 throw_unsup_format!(
719 "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
720 );
721 }
722
723 if byte_offset != 0 {
724 throw_unsup_format!(
725 "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
726 );
727 }
728
729 if !this.ptr_is_null(key)? {
730 throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
731 }
732
733 let io_status = {
735 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
736 this.project_field_named(&anon, "Status")?
737 };
738 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
739
740 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
741
742 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
743
744 let finish = {
748 let io_status = io_status.clone();
749 let io_status_info = io_status_info.clone();
750 let dest = dest.clone();
751 callback!(
752 @capture<'tcx> {
753 count: u32,
754 io_status: MPlaceTy<'tcx>,
755 io_status_info: MPlaceTy<'tcx>,
756 dest: MPlaceTy<'tcx>,
757 }
758 |this, result: Result<usize, IoError>| {
759 match result {
760 Ok(read_size) => {
761 assert!(read_size <= count.try_into().unwrap());
762 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
764 this.write_int(0, &io_status)?;
765 this.write_int(0, &dest)
766 }
767 Err(e) => {
768 this.write_int(0, &io_status_info)?;
769 let status = e.into_ntstatus();
770 this.write_int(status, &io_status)?;
771 this.write_int(status, &dest)
772 }
773 }}
774 )
775 };
776 desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
777
778 interp_ok(())
780 }
781
782 fn SetFilePointerEx(
783 &mut self,
784 file: &OpTy<'tcx>, dist_to_move: &OpTy<'tcx>, new_fp: &OpTy<'tcx>, move_method: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
789 let this = self.eval_context_mut();
791 let file = this.read_handle(file, "SetFilePointerEx")?;
792 let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?;
793 let new_fp_ptr = this.read_pointer(new_fp)?;
794 let move_method = this.read_scalar(move_method)?.to_u32()?;
795
796 let Handle::File(fd) = file else { this.invalid_handle("SetFilePointerEx")? };
797
798 let Some(desc) = this.machine.fds.get(fd) else {
799 throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
800 };
801
802 let file_begin = this.eval_windows_u32("c", "FILE_BEGIN");
803 let file_current = this.eval_windows_u32("c", "FILE_CURRENT");
804 let file_end = this.eval_windows_u32("c", "FILE_END");
805
806 let seek = if move_method == file_begin {
807 SeekFrom::Start(dist_to_move.try_into().unwrap())
808 } else if move_method == file_current {
809 SeekFrom::Current(dist_to_move)
810 } else if move_method == file_end {
811 SeekFrom::End(dist_to_move)
812 } else {
813 throw_unsup_format!("Invalid move method: {move_method}")
814 };
815
816 match desc.seek(this.machine.communicate(), seek)? {
817 Ok(n) => {
818 if !this.ptr_is_null(new_fp_ptr)? {
819 this.write_scalar(
820 Scalar::from_i64(n.try_into().unwrap()),
821 &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?,
822 )?;
823 }
824 interp_ok(this.eval_windows("c", "TRUE"))
825 }
826 Err(e) => {
827 this.set_last_error(e)?;
828 interp_ok(this.eval_windows("c", "FALSE"))
829 }
830 }
831 }
832}
833
834fn extract_windows_epoch<'tcx>(
836 ecx: &MiriInterpCx<'tcx>,
837 time: io::Result<SystemTime>,
838) -> InterpResult<'tcx, Option<(u32, u32)>> {
839 match time.ok() {
840 Some(time) => {
841 let duration = ecx.system_time_since_windows_epoch(&time)?;
842 let duration_ticks = ecx.windows_ticks_for(duration)?;
843 #[expect(clippy::as_conversions)]
844 interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
845 }
846 None => interp_ok(None),
847 }
848}
849
850fn write_filetime_field<'tcx>(
851 cx: &mut MiriInterpCx<'tcx>,
852 val: &MPlaceTy<'tcx>,
853 name: &str,
854 (low, high): (u32, u32),
855) -> InterpResult<'tcx> {
856 cx.write_int_fields_named(
857 &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
858 &cx.project_field_named(val, name)?,
859 )
860}