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