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,
17 CreateNew,
18 OpenAlways,
19 OpenExisting,
20 TruncateExisting,
21}
22
23impl CreationDisposition {
24 fn new<'tcx>(
25 value: u32,
26 ecx: &mut MiriInterpCx<'tcx>,
27 ) -> InterpResult<'tcx, CreationDisposition> {
28 let create_always = ecx.eval_windows_u32("c", "CREATE_ALWAYS");
29 let create_new = ecx.eval_windows_u32("c", "CREATE_NEW");
30 let open_always = ecx.eval_windows_u32("c", "OPEN_ALWAYS");
31 let open_existing = ecx.eval_windows_u32("c", "OPEN_EXISTING");
32 let truncate_existing = ecx.eval_windows_u32("c", "TRUNCATE_EXISTING");
33
34 let out = if value == create_always {
35 CreationDisposition::CreateAlways
36 } else if value == create_new {
37 CreationDisposition::CreateNew
38 } else if value == open_always {
39 CreationDisposition::OpenAlways
40 } else if value == open_existing {
41 CreationDisposition::OpenExisting
42 } else if value == truncate_existing {
43 CreationDisposition::TruncateExisting
44 } else {
45 throw_unsup_format!("CreateFileW: Unsupported creation disposition: {value}");
46 };
47 interp_ok(out)
48 }
49}
50
51bitflags! {
52 #[derive(PartialEq)]
53 struct FileAttributes: u32 {
54 const ZERO = 0;
55 const NORMAL = 1 << 0;
56 const BACKUP_SEMANTICS = 1 << 1;
59 const OPEN_REPARSE = 1 << 2;
63 }
64}
65
66impl FileAttributes {
67 fn new<'tcx>(
68 mut value: u32,
69 ecx: &mut MiriInterpCx<'tcx>,
70 ) -> InterpResult<'tcx, FileAttributes> {
71 let file_attribute_normal = ecx.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL");
72 let file_flag_backup_semantics = ecx.eval_windows_u32("c", "FILE_FLAG_BACKUP_SEMANTICS");
73 let file_flag_open_reparse_point =
74 ecx.eval_windows_u32("c", "FILE_FLAG_OPEN_REPARSE_POINT");
75
76 let mut out = FileAttributes::ZERO;
77 if value & file_flag_backup_semantics != 0 {
78 value &= !file_flag_backup_semantics;
79 out |= FileAttributes::BACKUP_SEMANTICS;
80 }
81 if value & file_flag_open_reparse_point != 0 {
82 value &= !file_flag_open_reparse_point;
83 out |= FileAttributes::OPEN_REPARSE;
84 }
85 if value & file_attribute_normal != 0 {
86 value &= !file_attribute_normal;
87 out |= FileAttributes::NORMAL;
88 }
89
90 if value != 0 {
91 throw_unsup_format!("CreateFileW: Unsupported flags_and_attributes: {value}");
92 }
93
94 if out == FileAttributes::ZERO {
95 out = FileAttributes::NORMAL;
97 }
98 interp_ok(out)
99 }
100}
101
102impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
103#[allow(non_snake_case)]
104pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
105 fn CreateFileW(
106 &mut self,
107 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> {
115 use CreationDisposition::*;
117
118 let this = self.eval_context_mut();
119 this.assert_target_os(Os::Windows, "CreateFileW");
120 this.check_no_isolation("`CreateFileW`")?;
121
122 this.set_last_error(IoError::Raw(Scalar::from_i32(0)))?;
125
126 let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
127 let mut desired_access = this.read_scalar(desired_access)?.to_u32()?;
128 let share_mode = this.read_scalar(share_mode)?.to_u32()?;
129 let security_attributes = this.read_pointer(security_attributes)?;
130 let creation_disposition = this.read_scalar(creation_disposition)?.to_u32()?;
131 let flags_and_attributes = this.read_scalar(flags_and_attributes)?.to_u32()?;
132 let template_file = this.read_target_usize(template_file)?;
133
134 let generic_read = this.eval_windows_u32("c", "GENERIC_READ");
135 let generic_write = this.eval_windows_u32("c", "GENERIC_WRITE");
136
137 let file_share_delete = this.eval_windows_u32("c", "FILE_SHARE_DELETE");
138 let file_share_read = this.eval_windows_u32("c", "FILE_SHARE_READ");
139 let file_share_write = this.eval_windows_u32("c", "FILE_SHARE_WRITE");
140
141 let creation_disposition = CreationDisposition::new(creation_disposition, this)?;
142 let attributes = FileAttributes::new(flags_and_attributes, this)?;
143
144 if share_mode != (file_share_delete | file_share_read | file_share_write) {
145 throw_unsup_format!("CreateFileW: Unsupported share mode: {share_mode}");
146 }
147 if !this.ptr_is_null(security_attributes)? {
148 throw_unsup_format!("CreateFileW: Security attributes are not supported");
149 }
150
151 if attributes.contains(FileAttributes::OPEN_REPARSE) && creation_disposition == CreateAlways
152 {
153 throw_machine_stop!(TerminationInfo::Abort("Invalid CreateFileW argument combination: FILE_FLAG_OPEN_REPARSE_POINT with CREATE_ALWAYS".to_string()));
154 }
155
156 if template_file != 0 {
157 throw_unsup_format!("CreateFileW: Template files are not supported");
158 }
159
160 let mut desired_read = false;
162 if desired_access & generic_read != 0 {
163 desired_read = true;
164 desired_access &= !generic_read;
165 }
166 let mut desired_write = false;
167 if desired_access & generic_write != 0 {
168 desired_write = true;
169 desired_access &= !generic_write;
170 }
171
172 if desired_access != 0 {
173 throw_unsup_format!(
174 "CreateFileW: Unsupported bits set for access mode: {desired_access:#x}"
175 );
176 }
177
178 let mut counter = 0u32;
181 loop {
182 if counter >= 100 {
183 panic!(
184 "CreateFileW seems stuck in an infinite retry loop. \
185 If you can reproduce this, please file a bug."
186 );
187 }
188 counter = counter.strict_add(1);
189
190 let is_dir = file_name.is_dir();
194
195 if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) && is_dir {
197 this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
198 return interp_ok(Handle::Invalid);
199 }
200
201 if is_dir {
202 let dir = match Dir::open(&file_name) {
206 Ok(dir) => dir,
207 Err(e) => {
208 if e.kind() == io::ErrorKind::NotADirectory {
209 continue;
211 }
212 this.set_last_error(e)?;
213 return interp_ok(Handle::Invalid);
214 }
215 };
216 if !dir.metadata().unwrap().is_dir() {
217 continue;
219 }
220
221 if let CreateAlways | OpenAlways = creation_disposition {
223 this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
224 }
225
226 let fd_num = this.machine.fds.insert_new(DirHandle { dir });
227 return interp_ok(Handle::File(fd_num));
228 } else {
229 let exists_already = file_name.exists();
241
242 let mut options = fs::OpenOptions::new();
244 options.read(desired_read);
245 options.write(desired_write);
246 match creation_disposition {
247 CreateAlways | OpenAlways => {
248 if !exists_already {
252 options.create_new(true);
253 }
254 if creation_disposition == CreateAlways {
255 options.truncate(true);
256 }
257 }
258 CreateNew => {
259 options.create_new(true);
260 if !desired_write {
264 options.append(true);
265 }
266 }
267 OpenExisting => {
268 if !desired_read && !desired_write {
269 options.read(true);
277 }
278 }
279 TruncateExisting => {
280 options.truncate(true);
281 }
282 }
283
284 let file = match options.open(&file_name) {
285 Ok(file) => file,
286 Err(e) => {
287 let kind = e.kind();
288 if kind == io::ErrorKind::IsADirectory {
289 continue;
291 }
292 if exists_already && kind == io::ErrorKind::NotFound {
293 continue;
295 }
296 if !exists_already && kind == io::ErrorKind::AlreadyExists {
297 continue;
299 }
300 this.set_last_error(e)?;
301 return interp_ok(Handle::Invalid);
302 }
303 };
304 if file.metadata().unwrap().is_dir() {
305 continue;
307 }
308
309 if let CreateAlways | OpenAlways = creation_disposition
311 && exists_already
312 {
313 this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
314 }
315 let fd_num = this.machine.fds.insert_new(FileHandle {
316 file,
317 writable: desired_write,
318 readable: desired_read,
319 });
320 return interp_ok(Handle::File(fd_num));
321 }
322 }
323 }
324
325 fn GetFileInformationByHandle(
326 &mut self,
327 file: &OpTy<'tcx>, file_information: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
330 let this = self.eval_context_mut();
332 this.assert_target_os(Os::Windows, "GetFileInformationByHandle");
333 this.check_no_isolation("`GetFileInformationByHandle`")?;
334
335 let file = this.read_handle(file, "GetFileInformationByHandle")?;
336 let file_information = this.deref_pointer_as(
337 file_information,
338 this.windows_ty_layout("BY_HANDLE_FILE_INFORMATION"),
339 )?;
340
341 let Handle::File(fd_num) = file else { this.invalid_handle("GetFileInformationByHandle")? };
342
343 let Some(desc) = this.machine.fds.get(fd_num) else {
344 this.invalid_handle("GetFileInformationByHandle")?
345 };
346
347 let metadata = match desc.metadata()? {
348 Either::Left(Ok(meta)) => meta,
349 Either::Left(Err(e)) => {
350 this.set_last_error(e)?;
351 return interp_ok(this.eval_windows("c", "FALSE"));
352 }
353 Either::Right(_mode) =>
354 throw_unsup_format!(
355 "`GetFileInformationByHandle` is not supported on non-file-backed handles"
356 ),
357 };
358
359 let size = metadata.len();
360
361 let file_type = metadata.file_type();
362 let attributes = if file_type.is_dir() {
363 this.eval_windows_u32("c", "FILE_ATTRIBUTE_DIRECTORY")
364 } else if file_type.is_file() {
365 this.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL")
366 } else {
367 this.eval_windows_u32("c", "FILE_ATTRIBUTE_DEVICE")
368 };
369
370 let created = extract_windows_epoch(this, metadata.created())?.unwrap_or((0, 0));
374 let accessed = extract_windows_epoch(this, metadata.accessed())?.unwrap_or((0, 0));
375 let written = extract_windows_epoch(this, metadata.modified())?.unwrap_or((0, 0));
376
377 this.write_int_fields_named(&[("dwFileAttributes", attributes.into())], &file_information)?;
378 write_filetime_field(this, &file_information, "ftCreationTime", created)?;
379 write_filetime_field(this, &file_information, "ftLastAccessTime", accessed)?;
380 write_filetime_field(this, &file_information, "ftLastWriteTime", written)?;
381 this.write_int_fields_named(
382 &[
383 ("dwVolumeSerialNumber", 0),
384 ("nFileSizeHigh", (size >> 32).into()),
385 ("nFileSizeLow", (size & 0xFFFFFFFF).into()),
386 ("nNumberOfLinks", 1),
387 ("nFileIndexHigh", 0),
388 ("nFileIndexLow", 0),
389 ],
390 &file_information,
391 )?;
392
393 interp_ok(this.eval_windows("c", "TRUE"))
394 }
395
396 fn SetFileInformationByHandle(
397 &mut self,
398 file: &OpTy<'tcx>, class: &OpTy<'tcx>, file_information: &OpTy<'tcx>, buffer_size: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
403 let this = self.eval_context_mut();
405 this.assert_target_os(Os::Windows, "SetFileInformationByHandle");
406 this.check_no_isolation("`SetFileInformationByHandle`")?;
407
408 let class = this.read_scalar(class)?.to_u32()?;
409 let buffer_size = this.read_scalar(buffer_size)?.to_u32()?;
410 let file_information = this.read_pointer(file_information)?;
411 this.check_ptr_access(
412 file_information,
413 Size::from_bytes(buffer_size),
414 CheckInAllocMsg::MemoryAccess,
415 )?;
416
417 let file = this.read_handle(file, "SetFileInformationByHandle")?;
418 let Handle::File(fd_num) = file else { this.invalid_handle("SetFileInformationByHandle")? };
419 let Some(desc) = this.machine.fds.get(fd_num) else {
420 this.invalid_handle("SetFileInformationByHandle")?
421 };
422 let file = desc.downcast::<FileHandle>().ok_or_else(|| {
423 err_unsup_format!(
424 "`SetFileInformationByHandle` is only supported on file-backed file descriptors"
425 )
426 })?;
427
428 if class == this.eval_windows_u32("c", "FileEndOfFileInfo") {
429 let place = this
430 .ptr_to_mplace(file_information, this.windows_ty_layout("FILE_END_OF_FILE_INFO"));
431 let new_len =
432 this.read_scalar(&this.project_field_named(&place, "EndOfFile")?)?.to_i64()?;
433 match file.file.set_len(new_len.try_into().unwrap()) {
434 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
435 Err(e) => {
436 this.set_last_error(e)?;
437 interp_ok(this.eval_windows("c", "FALSE"))
438 }
439 }
440 } else if class == this.eval_windows_u32("c", "FileAllocationInfo") {
441 let place = this
448 .ptr_to_mplace(file_information, this.windows_ty_layout("FILE_ALLOCATION_INFO"));
449 let new_alloc_size: u64 = this
450 .read_scalar(&this.project_field_named(&place, "AllocationSize")?)?
451 .to_i64()?
452 .try_into()
453 .unwrap();
454 let old_len = match file.file.metadata() {
455 Ok(m) => m.len(),
456 Err(e) => {
457 this.set_last_error(e)?;
458 return interp_ok(this.eval_windows("c", "FALSE"));
459 }
460 };
461 if new_alloc_size < old_len {
462 match file.file.set_len(new_alloc_size) {
463 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
464 Err(e) => {
465 this.set_last_error(e)?;
466 interp_ok(this.eval_windows("c", "FALSE"))
467 }
468 }
469 } else {
470 interp_ok(this.eval_windows("c", "TRUE"))
471 }
472 } else {
473 throw_unsup_format!(
474 "SetFileInformationByHandle: Unsupported `FileInformationClass` value {}",
475 class
476 )
477 }
478 }
479
480 fn FlushFileBuffers(
481 &mut self,
482 file: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
484 let this = self.eval_context_mut();
486 this.assert_target_os(Os::Windows, "FlushFileBuffers");
487
488 let file = this.read_handle(file, "FlushFileBuffers")?;
489 let Handle::File(fd_num) = file else { this.invalid_handle("FlushFileBuffers")? };
490 let Some(desc) = this.machine.fds.get(fd_num) else {
491 this.invalid_handle("FlushFileBuffers")?
492 };
493 let file = desc.downcast::<FileHandle>().ok_or_else(|| {
494 err_unsup_format!(
495 "`FlushFileBuffers` is only supported on file-backed file descriptors"
496 )
497 })?;
498
499 if !file.writable {
500 this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
501 return interp_ok(this.eval_windows("c", "FALSE"));
502 }
503
504 match file.file.sync_all() {
505 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
506 Err(e) => {
507 this.set_last_error(e)?;
508 interp_ok(this.eval_windows("c", "FALSE"))
509 }
510 }
511 }
512
513 fn MoveFileExW(
514 &mut self,
515 existing_name: &OpTy<'tcx>,
516 new_name: &OpTy<'tcx>,
517 flags: &OpTy<'tcx>,
518 ) -> InterpResult<'tcx, Scalar> {
519 let this = self.eval_context_mut();
520
521 let existing_name = this.read_path_from_wide_str(this.read_pointer(existing_name)?)?;
522 let new_name = this.read_path_from_wide_str(this.read_pointer(new_name)?)?;
523
524 let flags = this.read_scalar(flags)?.to_u32()?;
525
526 let movefile_replace_existing = this.eval_windows_u32("c", "MOVEFILE_REPLACE_EXISTING");
529
530 if flags != movefile_replace_existing {
531 throw_unsup_format!("MoveFileExW: Unsupported `dwFlags` value {}", flags);
532 }
533
534 match std::fs::rename(existing_name, new_name) {
535 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
536 Err(e) => {
537 this.set_last_error(e)?;
538 interp_ok(this.eval_windows("c", "FALSE"))
539 }
540 }
541 }
542
543 fn DeleteFileW(
544 &mut self,
545 file_name: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
547 let this = self.eval_context_mut();
549 this.assert_target_os(Os::Windows, "DeleteFileW");
550 this.check_no_isolation("`DeleteFileW`")?;
551
552 let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
553 match std::fs::remove_file(file_name) {
554 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
555 Err(e) => {
556 this.set_last_error(e)?;
557 interp_ok(this.eval_windows("c", "FALSE"))
558 }
559 }
560 }
561
562 fn NtWriteFile(
563 &mut self,
564 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, ()> {
575 let this = self.eval_context_mut();
576 let handle = this.read_handle(handle, "NtWriteFile")?;
577 let event = this.read_handle(event, "NtWriteFile")?;
578 let apc_routine = this.read_pointer(apc_routine)?;
579 let apc_ctx = this.read_pointer(apc_ctx)?;
580 let buf = this.read_pointer(buf)?;
581 let count = this.read_scalar(n)?.to_u32()?;
582 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
584 let io_status_block =
585 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
586
587 if event != Handle::Null {
588 throw_unsup_format!(
589 "`NtWriteFile` `Event` parameter is non-null, which is unsupported"
590 );
591 }
592
593 if !this.ptr_is_null(apc_routine)? {
594 throw_unsup_format!(
595 "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported"
596 );
597 }
598
599 if !this.ptr_is_null(apc_ctx)? {
600 throw_unsup_format!(
601 "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported"
602 );
603 }
604
605 if byte_offset != 0 {
606 throw_unsup_format!(
607 "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
608 );
609 }
610
611 if !this.ptr_is_null(key)? {
612 throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported");
613 }
614
615 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
616
617 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? };
618
619 let io_status = {
624 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
625 this.project_field_named(&anon, "Status")?
626 };
627 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
628
629 let finish = {
633 let io_status = io_status.clone();
634 let io_status_info = io_status_info.clone();
635 let dest = dest.clone();
636 callback!(
637 @capture<'tcx> {
638 count: u32,
639 io_status: MPlaceTy<'tcx>,
640 io_status_info: MPlaceTy<'tcx>,
641 dest: MPlaceTy<'tcx>,
642 }
643 |this, result: Result<usize, IoError>| {
644 match result {
645 Ok(read_size) => {
646 assert!(read_size <= count.try_into().unwrap());
647 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
649 this.write_int(0, &io_status)?;
650 this.write_int(0, &dest)
651 }
652 Err(e) => {
653 this.write_int(0, &io_status_info)?;
654 let status = e.into_ntstatus();
655 this.write_int(status, &io_status)?;
656 this.write_int(status, &dest)
657 }
658 }}
659 )
660 };
661 desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
662
663 interp_ok(())
665 }
666
667 fn NtReadFile(
668 &mut self,
669 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, ()> {
680 let this = self.eval_context_mut();
681 let handle = this.read_handle(handle, "NtReadFile")?;
682 let event = this.read_handle(event, "NtReadFile")?;
683 let apc_routine = this.read_pointer(apc_routine)?;
684 let apc_ctx = this.read_pointer(apc_ctx)?;
685 let buf = this.read_pointer(buf)?;
686 let count = this.read_scalar(n)?.to_u32()?;
687 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
689 let io_status_block =
690 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
691
692 if event != Handle::Null {
693 throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
694 }
695
696 if !this.ptr_is_null(apc_routine)? {
697 throw_unsup_format!(
698 "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
699 );
700 }
701
702 if !this.ptr_is_null(apc_ctx)? {
703 throw_unsup_format!(
704 "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
705 );
706 }
707
708 if byte_offset != 0 {
709 throw_unsup_format!(
710 "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
711 );
712 }
713
714 if !this.ptr_is_null(key)? {
715 throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
716 }
717
718 let io_status = {
720 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
721 this.project_field_named(&anon, "Status")?
722 };
723 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
724
725 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
726
727 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
728
729 let finish = {
733 let io_status = io_status.clone();
734 let io_status_info = io_status_info.clone();
735 let dest = dest.clone();
736 callback!(
737 @capture<'tcx> {
738 count: u32,
739 io_status: MPlaceTy<'tcx>,
740 io_status_info: MPlaceTy<'tcx>,
741 dest: MPlaceTy<'tcx>,
742 }
743 |this, result: Result<usize, IoError>| {
744 match result {
745 Ok(read_size) => {
746 assert!(read_size <= count.try_into().unwrap());
747 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
749 this.write_int(0, &io_status)?;
750 this.write_int(0, &dest)
751 }
752 Err(e) => {
753 this.write_int(0, &io_status_info)?;
754 let status = e.into_ntstatus();
755 this.write_int(status, &io_status)?;
756 this.write_int(status, &dest)
757 }
758 }}
759 )
760 };
761 desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
762
763 interp_ok(())
765 }
766
767 fn SetFilePointerEx(
768 &mut self,
769 file: &OpTy<'tcx>, dist_to_move: &OpTy<'tcx>, new_fp: &OpTy<'tcx>, move_method: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
774 let this = self.eval_context_mut();
776 let file = this.read_handle(file, "SetFilePointerEx")?;
777 let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?;
778 let new_fp_ptr = this.read_pointer(new_fp)?;
779 let move_method = this.read_scalar(move_method)?.to_u32()?;
780
781 let Handle::File(fd) = file else { this.invalid_handle("SetFilePointerEx")? };
782
783 let Some(desc) = this.machine.fds.get(fd) else {
784 throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
785 };
786
787 let file_begin = this.eval_windows_u32("c", "FILE_BEGIN");
788 let file_current = this.eval_windows_u32("c", "FILE_CURRENT");
789 let file_end = this.eval_windows_u32("c", "FILE_END");
790
791 let seek = if move_method == file_begin {
792 SeekFrom::Start(dist_to_move.try_into().unwrap())
793 } else if move_method == file_current {
794 SeekFrom::Current(dist_to_move)
795 } else if move_method == file_end {
796 SeekFrom::End(dist_to_move)
797 } else {
798 throw_unsup_format!("Invalid move method: {move_method}")
799 };
800
801 match desc.seek(this.machine.communicate(), seek)? {
802 Ok(n) => {
803 if !this.ptr_is_null(new_fp_ptr)? {
804 this.write_scalar(
805 Scalar::from_i64(n.try_into().unwrap()),
806 &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?,
807 )?;
808 }
809 interp_ok(this.eval_windows("c", "TRUE"))
810 }
811 Err(e) => {
812 this.set_last_error(e)?;
813 interp_ok(this.eval_windows("c", "FALSE"))
814 }
815 }
816 }
817}
818
819fn extract_windows_epoch<'tcx>(
821 ecx: &MiriInterpCx<'tcx>,
822 time: io::Result<SystemTime>,
823) -> InterpResult<'tcx, Option<(u32, u32)>> {
824 match time.ok() {
825 Some(time) => {
826 let duration = ecx.system_time_since_windows_epoch(&time)?;
827 let duration_ticks = ecx.windows_ticks_for(duration)?;
828 #[expect(clippy::as_conversions)]
829 interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
830 }
831 None => interp_ok(None),
832 }
833}
834
835fn write_filetime_field<'tcx>(
836 cx: &mut MiriInterpCx<'tcx>,
837 val: &MPlaceTy<'tcx>,
838 name: &str,
839 (low, high): (u32, u32),
840) -> InterpResult<'tcx> {
841 cx.write_int_fields_named(
842 &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
843 &cx.project_field_named(val, name)?,
844 )
845}