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