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 #[cfg(not(bootstrap))]
217 if !dir.metadata().unwrap().is_dir() {
218 continue;
220 }
221
222 if let CreateAlways | OpenAlways = creation_disposition {
224 this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
225 }
226
227 let fd_num = this.machine.fds.insert_new(DirHandle { dir, path: file_name });
228 return interp_ok(Handle::File(fd_num));
229 } else {
230 let exists_already = file_name.exists();
242
243 let mut options = fs::OpenOptions::new();
245 options.read(desired_read);
246 options.write(desired_write);
247 match creation_disposition {
248 CreateAlways | OpenAlways => {
249 if !exists_already {
253 options.create_new(true);
254 }
255 if creation_disposition == CreateAlways {
256 options.truncate(true);
257 }
258 }
259 CreateNew => {
260 options.create_new(true);
261 if !desired_write {
265 options.append(true);
266 }
267 }
268 OpenExisting => {
269 if !desired_read && !desired_write {
270 options.read(true);
278 }
279 }
280 TruncateExisting => {
281 options.truncate(true);
282 }
283 }
284
285 let file = match options.open(&file_name) {
286 Ok(file) => file,
287 Err(e) => {
288 let kind = e.kind();
289 if kind == io::ErrorKind::IsADirectory {
290 continue;
292 }
293 if exists_already && kind == io::ErrorKind::NotFound {
294 continue;
296 }
297 if !exists_already && kind == io::ErrorKind::AlreadyExists {
298 continue;
300 }
301 this.set_last_error(e)?;
302 return interp_ok(Handle::Invalid);
303 }
304 };
305 if file.metadata().unwrap().is_dir() {
306 continue;
308 }
309
310 if let CreateAlways | OpenAlways = creation_disposition
312 && exists_already
313 {
314 this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
315 }
316 let fd_num = this.machine.fds.insert_new(FileHandle {
317 file,
318 writable: desired_write,
319 readable: desired_read,
320 });
321 return interp_ok(Handle::File(fd_num));
322 }
323 }
324 }
325
326 fn GetFileInformationByHandle(
327 &mut self,
328 file: &OpTy<'tcx>, file_information: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
331 let this = self.eval_context_mut();
333 this.assert_target_os(Os::Windows, "GetFileInformationByHandle");
334 this.check_no_isolation("`GetFileInformationByHandle`")?;
335
336 let file = this.read_handle(file, "GetFileInformationByHandle")?;
337 let file_information = this.deref_pointer_as(
338 file_information,
339 this.windows_ty_layout("BY_HANDLE_FILE_INFORMATION"),
340 )?;
341
342 let Handle::File(fd_num) = file else { this.invalid_handle("GetFileInformationByHandle")? };
343
344 let Some(desc) = this.machine.fds.get(fd_num) else {
345 this.invalid_handle("GetFileInformationByHandle")?
346 };
347
348 let metadata = match desc.metadata()? {
349 Either::Left(Ok(meta)) => meta,
350 Either::Left(Err(e)) => {
351 this.set_last_error(e)?;
352 return interp_ok(this.eval_windows("c", "FALSE"));
353 }
354 Either::Right(_mode) =>
355 throw_unsup_format!(
356 "`GetFileInformationByHandle` is not supported on non-file-backed handles"
357 ),
358 };
359
360 let size = metadata.len();
361
362 let file_type = metadata.file_type();
363 let attributes = if file_type.is_dir() {
364 this.eval_windows_u32("c", "FILE_ATTRIBUTE_DIRECTORY")
365 } else if file_type.is_file() {
366 this.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL")
367 } else {
368 this.eval_windows_u32("c", "FILE_ATTRIBUTE_DEVICE")
369 };
370
371 let created = extract_windows_epoch(this, metadata.created())?.unwrap_or((0, 0));
375 let accessed = extract_windows_epoch(this, metadata.accessed())?.unwrap_or((0, 0));
376 let written = extract_windows_epoch(this, metadata.modified())?.unwrap_or((0, 0));
377
378 this.write_int_fields_named(&[("dwFileAttributes", attributes.into())], &file_information)?;
379 write_filetime_field(this, &file_information, "ftCreationTime", created)?;
380 write_filetime_field(this, &file_information, "ftLastAccessTime", accessed)?;
381 write_filetime_field(this, &file_information, "ftLastWriteTime", written)?;
382 this.write_int_fields_named(
383 &[
384 ("dwVolumeSerialNumber", 0),
385 ("nFileSizeHigh", (size >> 32).into()),
386 ("nFileSizeLow", (size & 0xFFFFFFFF).into()),
387 ("nNumberOfLinks", 1),
388 ("nFileIndexHigh", 0),
389 ("nFileIndexLow", 0),
390 ],
391 &file_information,
392 )?;
393
394 interp_ok(this.eval_windows("c", "TRUE"))
395 }
396
397 fn SetFileInformationByHandle(
398 &mut self,
399 file: &OpTy<'tcx>, class: &OpTy<'tcx>, file_information: &OpTy<'tcx>, buffer_size: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
404 let this = self.eval_context_mut();
406 this.assert_target_os(Os::Windows, "SetFileInformationByHandle");
407 this.check_no_isolation("`SetFileInformationByHandle`")?;
408
409 let class = this.read_scalar(class)?.to_u32()?;
410 let buffer_size = this.read_scalar(buffer_size)?.to_u32()?;
411 let file_information = this.read_pointer(file_information)?;
412 this.check_ptr_access(
413 file_information,
414 Size::from_bytes(buffer_size),
415 CheckInAllocMsg::MemoryAccess,
416 )?;
417
418 let file = this.read_handle(file, "SetFileInformationByHandle")?;
419 let Handle::File(fd_num) = file else { this.invalid_handle("SetFileInformationByHandle")? };
420 let Some(desc) = this.machine.fds.get(fd_num) else {
421 this.invalid_handle("SetFileInformationByHandle")?
422 };
423 let file = desc.downcast::<FileHandle>().ok_or_else(|| {
424 err_unsup_format!(
425 "`SetFileInformationByHandle` is only supported on file-backed file descriptors"
426 )
427 })?;
428
429 if class == this.eval_windows_u32("c", "FileEndOfFileInfo") {
430 let place = this
431 .ptr_to_mplace(file_information, this.windows_ty_layout("FILE_END_OF_FILE_INFO"));
432 let new_len =
433 this.read_scalar(&this.project_field_named(&place, "EndOfFile")?)?.to_i64()?;
434 match file.file.set_len(new_len.try_into().unwrap()) {
435 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
436 Err(e) => {
437 this.set_last_error(e)?;
438 interp_ok(this.eval_windows("c", "FALSE"))
439 }
440 }
441 } else if class == this.eval_windows_u32("c", "FileAllocationInfo") {
442 let place = this
449 .ptr_to_mplace(file_information, this.windows_ty_layout("FILE_ALLOCATION_INFO"));
450 let new_alloc_size: u64 = this
451 .read_scalar(&this.project_field_named(&place, "AllocationSize")?)?
452 .to_i64()?
453 .try_into()
454 .unwrap();
455 let old_len = match file.file.metadata() {
456 Ok(m) => m.len(),
457 Err(e) => {
458 this.set_last_error(e)?;
459 return interp_ok(this.eval_windows("c", "FALSE"));
460 }
461 };
462 if new_alloc_size < old_len {
463 match file.file.set_len(new_alloc_size) {
464 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
465 Err(e) => {
466 this.set_last_error(e)?;
467 interp_ok(this.eval_windows("c", "FALSE"))
468 }
469 }
470 } else {
471 interp_ok(this.eval_windows("c", "TRUE"))
472 }
473 } else {
474 throw_unsup_format!(
475 "SetFileInformationByHandle: Unsupported `FileInformationClass` value {}",
476 class
477 )
478 }
479 }
480
481 fn FlushFileBuffers(
482 &mut self,
483 file: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
485 let this = self.eval_context_mut();
487 this.assert_target_os(Os::Windows, "FlushFileBuffers");
488
489 let file = this.read_handle(file, "FlushFileBuffers")?;
490 let Handle::File(fd_num) = file else { this.invalid_handle("FlushFileBuffers")? };
491 let Some(desc) = this.machine.fds.get(fd_num) else {
492 this.invalid_handle("FlushFileBuffers")?
493 };
494 let file = desc.downcast::<FileHandle>().ok_or_else(|| {
495 err_unsup_format!(
496 "`FlushFileBuffers` is only supported on file-backed file descriptors"
497 )
498 })?;
499
500 if !file.writable {
501 this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
502 return interp_ok(this.eval_windows("c", "FALSE"));
503 }
504
505 match file.file.sync_all() {
506 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
507 Err(e) => {
508 this.set_last_error(e)?;
509 interp_ok(this.eval_windows("c", "FALSE"))
510 }
511 }
512 }
513
514 fn MoveFileExW(
515 &mut self,
516 existing_name: &OpTy<'tcx>,
517 new_name: &OpTy<'tcx>,
518 flags: &OpTy<'tcx>,
519 ) -> InterpResult<'tcx, Scalar> {
520 let this = self.eval_context_mut();
521
522 let existing_name = this.read_path_from_wide_str(this.read_pointer(existing_name)?)?;
523 let new_name = this.read_path_from_wide_str(this.read_pointer(new_name)?)?;
524
525 let flags = this.read_scalar(flags)?.to_u32()?;
526
527 let movefile_replace_existing = this.eval_windows_u32("c", "MOVEFILE_REPLACE_EXISTING");
530
531 if flags != movefile_replace_existing {
532 throw_unsup_format!("MoveFileExW: Unsupported `dwFlags` value {}", flags);
533 }
534
535 match std::fs::rename(existing_name, new_name) {
536 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
537 Err(e) => {
538 this.set_last_error(e)?;
539 interp_ok(this.eval_windows("c", "FALSE"))
540 }
541 }
542 }
543
544 fn DeleteFileW(
545 &mut self,
546 file_name: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
548 let this = self.eval_context_mut();
550 this.assert_target_os(Os::Windows, "DeleteFileW");
551 this.check_no_isolation("`DeleteFileW`")?;
552
553 let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
554 match std::fs::remove_file(file_name) {
555 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
556 Err(e) => {
557 this.set_last_error(e)?;
558 interp_ok(this.eval_windows("c", "FALSE"))
559 }
560 }
561 }
562
563 fn NtWriteFile(
564 &mut self,
565 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, ()> {
576 let this = self.eval_context_mut();
577 let handle = this.read_handle(handle, "NtWriteFile")?;
578 let event = this.read_handle(event, "NtWriteFile")?;
579 let apc_routine = this.read_pointer(apc_routine)?;
580 let apc_ctx = this.read_pointer(apc_ctx)?;
581 let buf = this.read_pointer(buf)?;
582 let count = this.read_scalar(n)?.to_u32()?;
583 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
585 let io_status_block =
586 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
587
588 if event != Handle::Null {
589 throw_unsup_format!(
590 "`NtWriteFile` `Event` parameter is non-null, which is unsupported"
591 );
592 }
593
594 if !this.ptr_is_null(apc_routine)? {
595 throw_unsup_format!(
596 "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported"
597 );
598 }
599
600 if !this.ptr_is_null(apc_ctx)? {
601 throw_unsup_format!(
602 "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported"
603 );
604 }
605
606 if byte_offset != 0 {
607 throw_unsup_format!(
608 "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
609 );
610 }
611
612 if !this.ptr_is_null(key)? {
613 throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported");
614 }
615
616 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
617
618 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? };
619
620 let io_status = {
625 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
626 this.project_field_named(&anon, "Status")?
627 };
628 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
629
630 let finish = {
634 let io_status = io_status.clone();
635 let io_status_info = io_status_info.clone();
636 let dest = dest.clone();
637 callback!(
638 @capture<'tcx> {
639 count: u32,
640 io_status: MPlaceTy<'tcx>,
641 io_status_info: MPlaceTy<'tcx>,
642 dest: MPlaceTy<'tcx>,
643 }
644 |this, result: Result<usize, IoError>| {
645 match result {
646 Ok(read_size) => {
647 assert!(read_size <= count.try_into().unwrap());
648 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
650 this.write_int(0, &io_status)?;
651 this.write_int(0, &dest)
652 }
653 Err(e) => {
654 this.write_int(0, &io_status_info)?;
655 let status = e.into_ntstatus();
656 this.write_int(status, &io_status)?;
657 this.write_int(status, &dest)
658 }
659 }}
660 )
661 };
662 desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
663
664 interp_ok(())
666 }
667
668 fn NtReadFile(
669 &mut self,
670 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, ()> {
681 let this = self.eval_context_mut();
682 let handle = this.read_handle(handle, "NtReadFile")?;
683 let event = this.read_handle(event, "NtReadFile")?;
684 let apc_routine = this.read_pointer(apc_routine)?;
685 let apc_ctx = this.read_pointer(apc_ctx)?;
686 let buf = this.read_pointer(buf)?;
687 let count = this.read_scalar(n)?.to_u32()?;
688 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
690 let io_status_block =
691 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
692
693 if event != Handle::Null {
694 throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
695 }
696
697 if !this.ptr_is_null(apc_routine)? {
698 throw_unsup_format!(
699 "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
700 );
701 }
702
703 if !this.ptr_is_null(apc_ctx)? {
704 throw_unsup_format!(
705 "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
706 );
707 }
708
709 if byte_offset != 0 {
710 throw_unsup_format!(
711 "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
712 );
713 }
714
715 if !this.ptr_is_null(key)? {
716 throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
717 }
718
719 let io_status = {
721 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
722 this.project_field_named(&anon, "Status")?
723 };
724 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
725
726 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
727
728 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
729
730 let finish = {
734 let io_status = io_status.clone();
735 let io_status_info = io_status_info.clone();
736 let dest = dest.clone();
737 callback!(
738 @capture<'tcx> {
739 count: u32,
740 io_status: MPlaceTy<'tcx>,
741 io_status_info: MPlaceTy<'tcx>,
742 dest: MPlaceTy<'tcx>,
743 }
744 |this, result: Result<usize, IoError>| {
745 match result {
746 Ok(read_size) => {
747 assert!(read_size <= count.try_into().unwrap());
748 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
750 this.write_int(0, &io_status)?;
751 this.write_int(0, &dest)
752 }
753 Err(e) => {
754 this.write_int(0, &io_status_info)?;
755 let status = e.into_ntstatus();
756 this.write_int(status, &io_status)?;
757 this.write_int(status, &dest)
758 }
759 }}
760 )
761 };
762 desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
763
764 interp_ok(())
766 }
767
768 fn SetFilePointerEx(
769 &mut self,
770 file: &OpTy<'tcx>, dist_to_move: &OpTy<'tcx>, new_fp: &OpTy<'tcx>, move_method: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
775 let this = self.eval_context_mut();
777 let file = this.read_handle(file, "SetFilePointerEx")?;
778 let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?;
779 let new_fp_ptr = this.read_pointer(new_fp)?;
780 let move_method = this.read_scalar(move_method)?.to_u32()?;
781
782 let Handle::File(fd) = file else { this.invalid_handle("SetFilePointerEx")? };
783
784 let Some(desc) = this.machine.fds.get(fd) else {
785 throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
786 };
787
788 let file_begin = this.eval_windows_u32("c", "FILE_BEGIN");
789 let file_current = this.eval_windows_u32("c", "FILE_CURRENT");
790 let file_end = this.eval_windows_u32("c", "FILE_END");
791
792 let seek = if move_method == file_begin {
793 SeekFrom::Start(dist_to_move.try_into().unwrap())
794 } else if move_method == file_current {
795 SeekFrom::Current(dist_to_move)
796 } else if move_method == file_end {
797 SeekFrom::End(dist_to_move)
798 } else {
799 throw_unsup_format!("Invalid move method: {move_method}")
800 };
801
802 match desc.seek(this.machine.communicate(), seek)? {
803 Ok(n) => {
804 if !this.ptr_is_null(new_fp_ptr)? {
805 this.write_scalar(
806 Scalar::from_i64(n.try_into().unwrap()),
807 &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?,
808 )?;
809 }
810 interp_ok(this.eval_windows("c", "TRUE"))
811 }
812 Err(e) => {
813 this.set_last_error(e)?;
814 interp_ok(this.eval_windows("c", "FALSE"))
815 }
816 }
817 }
818}
819
820fn extract_windows_epoch<'tcx>(
822 ecx: &MiriInterpCx<'tcx>,
823 time: io::Result<SystemTime>,
824) -> InterpResult<'tcx, Option<(u32, u32)>> {
825 match time.ok() {
826 Some(time) => {
827 let duration = ecx.system_time_since_windows_epoch(&time)?;
828 let duration_ticks = ecx.windows_ticks_for(duration)?;
829 #[expect(clippy::as_conversions)]
830 interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
831 }
832 None => interp_ok(None),
833 }
834}
835
836fn write_filetime_field<'tcx>(
837 cx: &mut MiriInterpCx<'tcx>,
838 val: &MPlaceTy<'tcx>,
839 name: &str,
840 (low, high): (u32, u32),
841) -> InterpResult<'tcx> {
842 cx.write_int_fields_named(
843 &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
844 &cx.project_field_named(val, name)?,
845 )
846}