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