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 ecx = self.eval_context_mut();
123 ecx.assert_target_os(Os::Windows, "CreateFileW");
124 ecx.check_no_isolation("`CreateFileW`")?;
125
126 ecx.set_last_error(IoError::Raw(Scalar::from_i32(0)))?;
129
130 let file_name = ecx.read_path_from_wide_str(ecx.read_pointer(file_name)?)?;
131 let mut desired_access = ecx.read_scalar(desired_access)?.to_u32()?;
132 let share_mode = ecx.read_scalar(share_mode)?.to_u32()?;
133 let security_attributes = ecx.read_pointer(security_attributes)?;
134 let creation_disposition = ecx.read_scalar(creation_disposition)?.to_u32()?;
135 let flags_and_attributes = ecx.read_scalar(flags_and_attributes)?.to_u32()?;
136 let template_file = ecx.read_target_usize(template_file)?;
137
138 let generic_read = ecx.eval_windows_u32("c", "GENERIC_READ");
139 let generic_write = ecx.eval_windows_u32("c", "GENERIC_WRITE");
140
141 let file_share_delete = ecx.eval_windows_u32("c", "FILE_SHARE_DELETE");
142 let file_share_read = ecx.eval_windows_u32("c", "FILE_SHARE_READ");
143 let file_share_write = ecx.eval_windows_u32("c", "FILE_SHARE_WRITE");
144
145 let creation_disposition = CreationDisposition::new(creation_disposition, ecx)?;
146 let attributes = FileAttributes::new(flags_and_attributes, ecx)?;
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 !ecx.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 let mut append = false;
230 match creation_disposition {
231 CreateAlways | OpenAlways => {
232 if !exists_already {
239 options.create_new(true);
240 }
241 if creation_disposition == CreateAlways {
242 options.truncate(true);
243 }
244 }
245 CreateNew => {
246 options.create_new(true);
247 if !desired_write {
251 options.append(true);
252 append = true;
253 }
254 }
255 OpenExisting => {
256 if !desired_read && !desired_write {
257 options.read(true);
265 }
266 }
267 TruncateExisting => {
268 options.truncate(true);
269 }
270 }
271
272 return match open_file_or_dir(None, &file_name, &options) {
274 Err(err) => {
275 let kind = err.kind();
276 if exists_already && kind == io::ErrorKind::NotFound {
277 continue;
279 }
280 if !exists_already && kind == io::ErrorKind::AlreadyExists {
281 continue;
283 }
284
285 if kind == io::ErrorKind::IsADirectory && desired_write {
286 ecx.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
289 } else {
290 ecx.set_last_error(err)?;
291 }
292 return interp_ok(Handle::Invalid);
293 }
294 Ok(Either::Right(dir)) => {
295 if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) {
297 ecx.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
298 return interp_ok(Handle::Invalid);
299 }
300 if matches!(creation_disposition, CreateAlways | OpenAlways) {
302 unreachable!()
303 }
304
305 let Some(fd_num) = ecx.machine.fds.insert_new(DirHandle {
306 dir,
307 #[cfg(bootstrap)]
308 fallback: file_name.canonicalize().unwrap(),
309 }) else {
310 throw_unsup_format!("we ran out of FD numbers")
311 };
312 interp_ok(Handle::File(fd_num))
313 }
314 Ok(Either::Left(file)) => {
315 if matches!(creation_disposition, CreateAlways | OpenAlways) && exists_already {
317 ecx.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
318 }
319
320 let Some(fd_num) = ecx.machine.fds.insert_new(FileHandle {
321 file,
322 writable: desired_write,
323 readable: desired_read,
324 append,
325 }) else {
326 throw_unsup_format!("we ran out of FD numbers")
327 };
328 interp_ok(Handle::File(fd_num))
329 }
330 };
331 }
332 }
333
334 fn GetFileInformationByHandle(
335 &mut self,
336 file: &OpTy<'tcx>, file_information: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
339 let ecx = self.eval_context_mut();
341 ecx.assert_target_os(Os::Windows, "GetFileInformationByHandle");
342 ecx.check_no_isolation("`GetFileInformationByHandle`")?;
343
344 let file = ecx.read_handle(file, "GetFileInformationByHandle")?;
345 let file_information = ecx.deref_pointer(file_information)?;
346
347 let Handle::File(fd_num) = file else { ecx.invalid_handle("GetFileInformationByHandle")? };
348
349 let Some(desc) = ecx.machine.fds.get(fd_num) else {
350 ecx.invalid_handle("GetFileInformationByHandle")?
351 };
352
353 let metadata = match desc.metadata(ecx.machine.communicate())? {
354 Either::Left(Ok(meta)) => meta,
355 Either::Left(Err(e)) => {
356 ecx.set_last_error(e)?;
357 return interp_ok(ecx.eval_windows("c", "FALSE"));
358 }
359 Either::Right(_mode) =>
360 throw_unsup_format!(
361 "`GetFileInformationByHandle` is not supported on non-file-backed handles"
362 ),
363 };
364
365 let size = metadata.len();
366
367 let file_type = metadata.file_type();
368 let attributes = if file_type.is_dir() {
369 ecx.eval_windows_u32("c", "FILE_ATTRIBUTE_DIRECTORY")
370 } else if file_type.is_file() {
371 ecx.eval_windows_u32("c", "FILE_ATTRIBUTE_ARCHIVE")
375 } else {
376 ecx.eval_windows_u32("c", "FILE_ATTRIBUTE_DEVICE")
377 };
378
379 let created = extract_windows_epoch(ecx, metadata.created())?.unwrap_or((0, 0));
383 let accessed = extract_windows_epoch(ecx, metadata.accessed())?.unwrap_or((0, 0));
384 let written = extract_windows_epoch(ecx, metadata.modified())?.unwrap_or((0, 0));
385
386 ecx.write_int_fields_named(&[("dwFileAttributes", attributes.into())], &file_information)?;
387 write_filetime_field(ecx, &file_information, "ftCreationTime", created)?;
388 write_filetime_field(ecx, &file_information, "ftLastAccessTime", accessed)?;
389 write_filetime_field(ecx, &file_information, "ftLastWriteTime", written)?;
390 ecx.write_int_fields_named(
391 &[
392 ("dwVolumeSerialNumber", 0),
393 ("nFileSizeHigh", (size >> 32).into()),
394 ("nFileSizeLow", (size & 0xFFFFFFFF).into()),
395 ("nNumberOfLinks", 1),
396 ("nFileIndexHigh", 0),
397 ("nFileIndexLow", 0),
398 ],
399 &file_information,
400 )?;
401
402 interp_ok(ecx.eval_windows("c", "TRUE"))
403 }
404
405 fn SetFileInformationByHandle(
406 &mut self,
407 file: &OpTy<'tcx>, class: &OpTy<'tcx>, file_information: &OpTy<'tcx>, buffer_size: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
412 let ecx = self.eval_context_mut();
414 ecx.assert_target_os(Os::Windows, "SetFileInformationByHandle");
415 ecx.check_no_isolation("`SetFileInformationByHandle`")?;
416
417 let class = ecx.read_scalar(class)?.to_u32()?;
418 let buffer_size = ecx.read_scalar(buffer_size)?.to_u32()?;
419 let file_information = ecx.read_pointer(file_information)?;
420 ecx.check_ptr_access(
421 file_information,
422 Size::from_bytes(buffer_size),
423 CheckInAllocMsg::MemoryAccess,
424 )?;
425
426 let file = ecx.read_handle(file, "SetFileInformationByHandle")?;
427 let Handle::File(fd_num) = file else { ecx.invalid_handle("SetFileInformationByHandle")? };
428 let Some(desc) = ecx.machine.fds.get(fd_num) else {
429 ecx.invalid_handle("SetFileInformationByHandle")?
430 };
431 let file = desc.downcast::<FileHandle>().ok_or_else(|| {
432 err_unsup_format!(
433 "`SetFileInformationByHandle` is only supported on file-backed file descriptors"
434 )
435 })?;
436
437 if class == ecx.eval_windows_u32("c", "FileEndOfFileInfo") {
438 let place =
439 ecx.ptr_to_mplace(file_information, ecx.windows_ty_layout("FILE_END_OF_FILE_INFO"));
440 let new_len =
441 ecx.read_scalar(&ecx.project_field_named(&place, "EndOfFile")?)?.to_i64()?;
442 match file.file.set_len(new_len.try_into().unwrap()) {
443 Ok(_) => interp_ok(ecx.eval_windows("c", "TRUE")),
444 Err(e) => {
445 ecx.set_last_error(e)?;
446 interp_ok(ecx.eval_windows("c", "FALSE"))
447 }
448 }
449 } else if class == ecx.eval_windows_u32("c", "FileAllocationInfo") {
450 let place =
457 ecx.ptr_to_mplace(file_information, ecx.windows_ty_layout("FILE_ALLOCATION_INFO"));
458 let new_alloc_size: u64 = ecx
459 .read_scalar(&ecx.project_field_named(&place, "AllocationSize")?)?
460 .to_i64()?
461 .try_into()
462 .unwrap();
463 let old_len = match file.file.metadata() {
464 Ok(m) => m.len(),
465 Err(e) => {
466 ecx.set_last_error(e)?;
467 return interp_ok(ecx.eval_windows("c", "FALSE"));
468 }
469 };
470 if new_alloc_size < old_len {
471 match file.file.set_len(new_alloc_size) {
472 Ok(_) => interp_ok(ecx.eval_windows("c", "TRUE")),
473 Err(e) => {
474 ecx.set_last_error(e)?;
475 interp_ok(ecx.eval_windows("c", "FALSE"))
476 }
477 }
478 } else {
479 interp_ok(ecx.eval_windows("c", "TRUE"))
480 }
481 } else {
482 throw_unsup_format!(
483 "SetFileInformationByHandle: Unsupported `FileInformationClass` value {}",
484 class
485 )
486 }
487 }
488
489 fn FlushFileBuffers(
490 &mut self,
491 file: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
493 let ecx = self.eval_context_mut();
495 ecx.assert_target_os(Os::Windows, "FlushFileBuffers");
496
497 let file = ecx.read_handle(file, "FlushFileBuffers")?;
498 let Handle::File(fd_num) = file else { ecx.invalid_handle("FlushFileBuffers")? };
499 let Some(desc) = ecx.machine.fds.get(fd_num) else {
500 ecx.invalid_handle("FlushFileBuffers")?
501 };
502 let file = desc.downcast::<FileHandle>().ok_or_else(|| {
503 err_unsup_format!(
504 "`FlushFileBuffers` is only supported on file-backed file descriptors"
505 )
506 })?;
507
508 if !file.writable {
509 ecx.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
510 return interp_ok(ecx.eval_windows("c", "FALSE"));
511 }
512
513 match file.file.sync_all() {
514 Ok(_) => interp_ok(ecx.eval_windows("c", "TRUE")),
515 Err(e) => {
516 ecx.set_last_error(e)?;
517 interp_ok(ecx.eval_windows("c", "FALSE"))
518 }
519 }
520 }
521
522 fn MoveFileExW(
523 &mut self,
524 existing_name: &OpTy<'tcx>,
525 new_name: &OpTy<'tcx>,
526 flags: &OpTy<'tcx>,
527 ) -> InterpResult<'tcx, Scalar> {
528 let ecx = self.eval_context_mut();
529
530 let existing_name = ecx.read_path_from_wide_str(ecx.read_pointer(existing_name)?)?;
531 let new_name = ecx.read_path_from_wide_str(ecx.read_pointer(new_name)?)?;
532
533 let flags = ecx.read_scalar(flags)?.to_u32()?;
534
535 let movefile_replace_existing = ecx.eval_windows_u32("c", "MOVEFILE_REPLACE_EXISTING");
538
539 if flags != movefile_replace_existing {
540 throw_unsup_format!("MoveFileExW: Unsupported `dwFlags` value {}", flags);
541 }
542
543 match std::fs::rename(existing_name, new_name) {
544 Ok(_) => interp_ok(ecx.eval_windows("c", "TRUE")),
545 Err(e) => {
546 ecx.set_last_error(e)?;
547 interp_ok(ecx.eval_windows("c", "FALSE"))
548 }
549 }
550 }
551
552 fn DeleteFileW(
553 &mut self,
554 file_name: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
556 let ecx = self.eval_context_mut();
558 ecx.assert_target_os(Os::Windows, "DeleteFileW");
559 ecx.check_no_isolation("`DeleteFileW`")?;
560
561 let file_name = ecx.read_path_from_wide_str(ecx.read_pointer(file_name)?)?;
562 match std::fs::remove_file(file_name) {
563 Ok(_) => interp_ok(ecx.eval_windows("c", "TRUE")),
564 Err(e) => {
565 ecx.set_last_error(e)?;
566 interp_ok(ecx.eval_windows("c", "FALSE"))
567 }
568 }
569 }
570
571 fn NtWriteFile(
572 &mut self,
573 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, ()> {
584 let ecx = self.eval_context_mut();
585
586 let handle = ecx.read_handle(handle, "NtWriteFile")?;
587 let event = ecx.read_handle(event, "NtWriteFile")?;
588 let apc_routine = ecx.read_pointer(apc_routine)?;
589 let apc_ctx = ecx.read_pointer(apc_ctx)?;
590 let buf = ecx.read_pointer(buf)?;
591 let count = ecx.read_scalar(n)?.to_u32()?;
592 let byte_offset = ecx.read_target_usize(byte_offset)?; let key = ecx.read_pointer(key)?;
594 let io_status_block = ecx.deref_pointer(io_status_block)?;
595
596 if event != Handle::Null {
597 throw_unsup_format!(
598 "`NtWriteFile` `Event` parameter is non-null, which is unsupported"
599 );
600 }
601
602 if !ecx.ptr_is_null(apc_routine)? {
603 throw_unsup_format!(
604 "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported"
605 );
606 }
607
608 if !ecx.ptr_is_null(apc_ctx)? {
609 throw_unsup_format!(
610 "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported"
611 );
612 }
613
614 if byte_offset != 0 {
615 throw_unsup_format!(
616 "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
617 );
618 }
619
620 if !ecx.ptr_is_null(key)? {
621 throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported");
622 }
623
624 let Handle::File(fd) = handle else { ecx.invalid_handle("NtWriteFile")? };
625
626 let Some(desc) = ecx.machine.fds.get(fd) else { ecx.invalid_handle("NtWriteFile")? };
627
628 let io_status = {
633 let anon = ecx.project_field_named(&io_status_block, "Anonymous")?;
634 ecx.project_field_named(&anon, "Status")?
635 };
636 let io_status_info = ecx.project_field_named(&io_status_block, "Information")?;
637
638 let finish = {
642 let io_status = io_status.clone();
643 let io_status_info = io_status_info.clone();
644 let dest = dest.clone();
645 callback!(
646 @capture<'tcx> {
647 count: u32,
648 io_status: MPlaceTy<'tcx>,
649 io_status_info: MPlaceTy<'tcx>,
650 dest: MPlaceTy<'tcx>,
651 }
652 |ecx, result: Result<u64, IoError>| {
653 match result {
654 Ok(read_size) => {
655 assert!(read_size <= count.into());
656 ecx.write_int(read_size, &io_status_info)?;
658 ecx.write_int(0, &io_status)?;
659 ecx.write_int(0, &dest)
660 }
661 Err(e) => {
662 ecx.write_int(0, &io_status_info)?;
663 let status = e.into_ntstatus();
664 ecx.write_int(status, &io_status)?;
665 ecx.write_int(status, &dest)
666 }
667 }}
668 )
669 };
670 desc.write(ecx.machine.communicate(), buf, count.into(), ecx, finish)?;
671
672 interp_ok(())
674 }
675
676 fn NtReadFile(
677 &mut self,
678 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, ()> {
689 let ecx = self.eval_context_mut();
690
691 let handle = ecx.read_handle(handle, "NtReadFile")?;
692 let event = ecx.read_handle(event, "NtReadFile")?;
693 let apc_routine = ecx.read_pointer(apc_routine)?;
694 let apc_ctx = ecx.read_pointer(apc_ctx)?;
695 let buf = ecx.read_pointer(buf)?;
696 let count = ecx.read_scalar(n)?.to_u32()?;
697 let byte_offset = ecx.read_target_usize(byte_offset)?; let key = ecx.read_pointer(key)?;
699 let io_status_block = ecx.deref_pointer(io_status_block)?;
700
701 if event != Handle::Null {
702 throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
703 }
704
705 if !ecx.ptr_is_null(apc_routine)? {
706 throw_unsup_format!(
707 "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
708 );
709 }
710
711 if !ecx.ptr_is_null(apc_ctx)? {
712 throw_unsup_format!(
713 "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
714 );
715 }
716
717 if byte_offset != 0 {
718 throw_unsup_format!(
719 "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
720 );
721 }
722
723 if !ecx.ptr_is_null(key)? {
724 throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
725 }
726
727 let io_status = {
729 let anon = ecx.project_field_named(&io_status_block, "Anonymous")?;
730 ecx.project_field_named(&anon, "Status")?
731 };
732 let io_status_info = ecx.project_field_named(&io_status_block, "Information")?;
733
734 let Handle::File(fd) = handle else { ecx.invalid_handle("NtWriteFile")? };
735
736 let Some(desc) = ecx.machine.fds.get(fd) else { ecx.invalid_handle("NtReadFile")? };
737
738 let finish = {
742 let io_status = io_status.clone();
743 let io_status_info = io_status_info.clone();
744 let dest = dest.clone();
745 callback!(
746 @capture<'tcx> {
747 count: u32,
748 io_status: MPlaceTy<'tcx>,
749 io_status_info: MPlaceTy<'tcx>,
750 dest: MPlaceTy<'tcx>,
751 }
752 |ecx, result: Result<u64, IoError>| {
753 match result {
754 Ok(read_size) => {
755 assert!(read_size <= count.into());
756 ecx.write_int(read_size, &io_status_info)?;
758 ecx.write_int(0, &io_status)?;
759 ecx.write_int(0, &dest)
760 }
761 Err(e) => {
762 ecx.write_int(0, &io_status_info)?;
763 let status = e.into_ntstatus();
764 ecx.write_int(status, &io_status)?;
765 ecx.write_int(status, &dest)
766 }
767 }}
768 )
769 };
770 desc.read(ecx.machine.communicate(), buf, count.into(), ecx, finish)?;
771
772 interp_ok(())
774 }
775
776 fn SetFilePointerEx(
777 &mut self,
778 file: &OpTy<'tcx>, dist_to_move: &OpTy<'tcx>, new_fp: &OpTy<'tcx>, move_method: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
783 let ecx = self.eval_context_mut();
785
786 let file = ecx.read_handle(file, "SetFilePointerEx")?;
787 let dist_to_move = ecx.read_scalar(dist_to_move)?.to_i64()?;
788 let new_fp_ptr = ecx.read_pointer(new_fp)?;
789 let move_method = ecx.read_scalar(move_method)?.to_u32()?;
790
791 let Handle::File(fd) = file else { ecx.invalid_handle("SetFilePointerEx")? };
792
793 let Some(desc) = ecx.machine.fds.get(fd) else {
794 throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
795 };
796
797 let file_begin = ecx.eval_windows_u32("c", "FILE_BEGIN");
798 let file_current = ecx.eval_windows_u32("c", "FILE_CURRENT");
799 let file_end = ecx.eval_windows_u32("c", "FILE_END");
800
801 let seek = if move_method == file_begin {
802 SeekFrom::Start(dist_to_move.try_into().unwrap())
803 } else if move_method == file_current {
804 SeekFrom::Current(dist_to_move)
805 } else if move_method == file_end {
806 SeekFrom::End(dist_to_move)
807 } else {
808 throw_unsup_format!("Invalid move method: {move_method}")
809 };
810
811 match desc.seek(ecx.machine.communicate(), seek)? {
812 Ok(n) => {
813 if !ecx.ptr_is_null(new_fp_ptr)? {
814 ecx.write_scalar(
815 Scalar::from_i64(n.try_into().unwrap()),
816 &ecx.deref_pointer(new_fp)?,
817 )?;
818 }
819 interp_ok(ecx.eval_windows("c", "TRUE"))
820 }
821 Err(e) => {
822 ecx.set_last_error(e)?;
823 interp_ok(ecx.eval_windows("c", "FALSE"))
824 }
825 }
826 }
827}
828
829fn extract_windows_epoch<'tcx>(
831 ecx: &MiriInterpCx<'tcx>,
832 time: io::Result<SystemTime>,
833) -> InterpResult<'tcx, Option<(u32, u32)>> {
834 match time.ok() {
835 Some(time) => {
836 let duration = ecx.system_time_since_windows_epoch(&time)?;
837 let duration_ticks = ecx.windows_ticks_for(duration)?;
838 #[expect(clippy::as_conversions)]
839 interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
840 }
841 None => interp_ok(None),
842 }
843}
844
845fn write_filetime_field<'tcx>(
846 cx: &mut MiriInterpCx<'tcx>,
847 val: &MPlaceTy<'tcx>,
848 name: &str,
849 (low, high): (u32, u32),
850) -> InterpResult<'tcx> {
851 cx.write_int_fields_named(
852 &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
853 &cx.project_field_named(val, name)?,
854 )
855}