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 DeleteFileW(
494 &mut self,
495 file_name: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
497 let this = self.eval_context_mut();
499 this.assert_target_os(Os::Windows, "DeleteFileW");
500 this.check_no_isolation("`DeleteFileW`")?;
501
502 let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
503 match std::fs::remove_file(file_name) {
504 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
505 Err(e) => {
506 this.set_last_error(e)?;
507 interp_ok(this.eval_windows("c", "FALSE"))
508 }
509 }
510 }
511
512 fn NtWriteFile(
513 &mut self,
514 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, ()> {
525 let this = self.eval_context_mut();
526 let handle = this.read_handle(handle, "NtWriteFile")?;
527 let event = this.read_handle(event, "NtWriteFile")?;
528 let apc_routine = this.read_pointer(apc_routine)?;
529 let apc_ctx = this.read_pointer(apc_ctx)?;
530 let buf = this.read_pointer(buf)?;
531 let count = this.read_scalar(n)?.to_u32()?;
532 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
534 let io_status_block =
535 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
536
537 if event != Handle::Null {
538 throw_unsup_format!(
539 "`NtWriteFile` `Event` parameter is non-null, which is unsupported"
540 );
541 }
542
543 if !this.ptr_is_null(apc_routine)? {
544 throw_unsup_format!(
545 "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported"
546 );
547 }
548
549 if !this.ptr_is_null(apc_ctx)? {
550 throw_unsup_format!(
551 "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported"
552 );
553 }
554
555 if byte_offset != 0 {
556 throw_unsup_format!(
557 "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
558 );
559 }
560
561 if !this.ptr_is_null(key)? {
562 throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported");
563 }
564
565 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
566
567 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? };
568
569 let io_status = {
574 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
575 this.project_field_named(&anon, "Status")?
576 };
577 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
578
579 let finish = {
583 let io_status = io_status.clone();
584 let io_status_info = io_status_info.clone();
585 let dest = dest.clone();
586 callback!(
587 @capture<'tcx> {
588 count: u32,
589 io_status: MPlaceTy<'tcx>,
590 io_status_info: MPlaceTy<'tcx>,
591 dest: MPlaceTy<'tcx>,
592 }
593 |this, result: Result<usize, IoError>| {
594 match result {
595 Ok(read_size) => {
596 assert!(read_size <= count.try_into().unwrap());
597 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
599 this.write_int(0, &io_status)?;
600 this.write_int(0, &dest)
601 }
602 Err(e) => {
603 this.write_int(0, &io_status_info)?;
604 let status = e.into_ntstatus();
605 this.write_int(status, &io_status)?;
606 this.write_int(status, &dest)
607 }
608 }}
609 )
610 };
611 desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
612
613 interp_ok(())
615 }
616
617 fn NtReadFile(
618 &mut self,
619 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, ()> {
630 let this = self.eval_context_mut();
631 let handle = this.read_handle(handle, "NtReadFile")?;
632 let event = this.read_handle(event, "NtReadFile")?;
633 let apc_routine = this.read_pointer(apc_routine)?;
634 let apc_ctx = this.read_pointer(apc_ctx)?;
635 let buf = this.read_pointer(buf)?;
636 let count = this.read_scalar(n)?.to_u32()?;
637 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
639 let io_status_block =
640 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
641
642 if event != Handle::Null {
643 throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
644 }
645
646 if !this.ptr_is_null(apc_routine)? {
647 throw_unsup_format!(
648 "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
649 );
650 }
651
652 if !this.ptr_is_null(apc_ctx)? {
653 throw_unsup_format!(
654 "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
655 );
656 }
657
658 if byte_offset != 0 {
659 throw_unsup_format!(
660 "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
661 );
662 }
663
664 if !this.ptr_is_null(key)? {
665 throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
666 }
667
668 let io_status = {
670 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
671 this.project_field_named(&anon, "Status")?
672 };
673 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
674
675 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
676
677 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
678
679 let finish = {
683 let io_status = io_status.clone();
684 let io_status_info = io_status_info.clone();
685 let dest = dest.clone();
686 callback!(
687 @capture<'tcx> {
688 count: u32,
689 io_status: MPlaceTy<'tcx>,
690 io_status_info: MPlaceTy<'tcx>,
691 dest: MPlaceTy<'tcx>,
692 }
693 |this, result: Result<usize, IoError>| {
694 match result {
695 Ok(read_size) => {
696 assert!(read_size <= count.try_into().unwrap());
697 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
699 this.write_int(0, &io_status)?;
700 this.write_int(0, &dest)
701 }
702 Err(e) => {
703 this.write_int(0, &io_status_info)?;
704 let status = e.into_ntstatus();
705 this.write_int(status, &io_status)?;
706 this.write_int(status, &dest)
707 }
708 }}
709 )
710 };
711 desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
712
713 interp_ok(())
715 }
716
717 fn SetFilePointerEx(
718 &mut self,
719 file: &OpTy<'tcx>, dist_to_move: &OpTy<'tcx>, new_fp: &OpTy<'tcx>, move_method: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
724 let this = self.eval_context_mut();
726 let file = this.read_handle(file, "SetFilePointerEx")?;
727 let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?;
728 let new_fp_ptr = this.read_pointer(new_fp)?;
729 let move_method = this.read_scalar(move_method)?.to_u32()?;
730
731 let Handle::File(fd) = file else { this.invalid_handle("SetFilePointerEx")? };
732
733 let Some(desc) = this.machine.fds.get(fd) else {
734 throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
735 };
736
737 let file_begin = this.eval_windows_u32("c", "FILE_BEGIN");
738 let file_current = this.eval_windows_u32("c", "FILE_CURRENT");
739 let file_end = this.eval_windows_u32("c", "FILE_END");
740
741 let seek = if move_method == file_begin {
742 SeekFrom::Start(dist_to_move.try_into().unwrap())
743 } else if move_method == file_current {
744 SeekFrom::Current(dist_to_move)
745 } else if move_method == file_end {
746 SeekFrom::End(dist_to_move)
747 } else {
748 throw_unsup_format!("Invalid move method: {move_method}")
749 };
750
751 match desc.seek(this.machine.communicate(), seek)? {
752 Ok(n) => {
753 if !this.ptr_is_null(new_fp_ptr)? {
754 this.write_scalar(
755 Scalar::from_i64(n.try_into().unwrap()),
756 &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?,
757 )?;
758 }
759 interp_ok(this.eval_windows("c", "TRUE"))
760 }
761 Err(e) => {
762 this.set_last_error(e)?;
763 interp_ok(this.eval_windows("c", "FALSE"))
764 }
765 }
766 }
767}
768
769fn extract_windows_epoch<'tcx>(
771 ecx: &MiriInterpCx<'tcx>,
772 time: io::Result<SystemTime>,
773) -> InterpResult<'tcx, Option<(u32, u32)>> {
774 match time.ok() {
775 Some(time) => {
776 let duration = ecx.system_time_since_windows_epoch(&time)?;
777 let duration_ticks = ecx.windows_ticks_for(duration)?;
778 #[expect(clippy::as_conversions)]
779 interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
780 }
781 None => interp_ok(None),
782 }
783}
784
785fn write_filetime_field<'tcx>(
786 cx: &mut MiriInterpCx<'tcx>,
787 val: &MPlaceTy<'tcx>,
788 name: &str,
789 (low, high): (u32, u32),
790) -> InterpResult<'tcx> {
791 cx.write_int_fields_named(
792 &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
793 &cx.project_field_named(val, name)?,
794 )
795}