miri/shims/windows/
fs.rs

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_target::spec::Os;
9
10use crate::shims::files::{FdId, FileDescription, FileHandle};
11use crate::shims::windows::handle::{EvalContextExt as _, Handle};
12use crate::*;
13
14#[derive(Debug)]
15pub struct DirHandle {
16    pub(crate) path: PathBuf,
17}
18
19impl FileDescription for DirHandle {
20    fn name(&self) -> &'static str {
21        "directory"
22    }
23
24    fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
25        interp_ok(self.path.metadata())
26    }
27
28    fn destroy<'tcx>(
29        self,
30        _self_id: FdId,
31        _communicate_allowed: bool,
32        _ecx: &mut MiriInterpCx<'tcx>,
33    ) -> InterpResult<'tcx, io::Result<()>> {
34        interp_ok(Ok(()))
35    }
36}
37
38/// Windows supports handles without any read/write/delete permissions - these handles can get
39/// metadata, but little else. We represent that by storing the metadata from the time the handle
40/// was opened.
41#[derive(Debug)]
42pub struct MetadataHandle {
43    pub(crate) meta: Metadata,
44}
45
46impl FileDescription for MetadataHandle {
47    fn name(&self) -> &'static str {
48        "metadata-only"
49    }
50
51    fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
52        interp_ok(Ok(self.meta.clone()))
53    }
54
55    fn destroy<'tcx>(
56        self,
57        _self_id: FdId,
58        _communicate_allowed: bool,
59        _ecx: &mut MiriInterpCx<'tcx>,
60    ) -> InterpResult<'tcx, io::Result<()>> {
61        interp_ok(Ok(()))
62    }
63}
64
65#[derive(Copy, Clone, Debug, PartialEq)]
66enum CreationDisposition {
67    CreateAlways,
68    CreateNew,
69    OpenAlways,
70    OpenExisting,
71    TruncateExisting,
72}
73
74impl CreationDisposition {
75    fn new<'tcx>(
76        value: u32,
77        ecx: &mut MiriInterpCx<'tcx>,
78    ) -> InterpResult<'tcx, CreationDisposition> {
79        let create_always = ecx.eval_windows_u32("c", "CREATE_ALWAYS");
80        let create_new = ecx.eval_windows_u32("c", "CREATE_NEW");
81        let open_always = ecx.eval_windows_u32("c", "OPEN_ALWAYS");
82        let open_existing = ecx.eval_windows_u32("c", "OPEN_EXISTING");
83        let truncate_existing = ecx.eval_windows_u32("c", "TRUNCATE_EXISTING");
84
85        let out = if value == create_always {
86            CreationDisposition::CreateAlways
87        } else if value == create_new {
88            CreationDisposition::CreateNew
89        } else if value == open_always {
90            CreationDisposition::OpenAlways
91        } else if value == open_existing {
92            CreationDisposition::OpenExisting
93        } else if value == truncate_existing {
94            CreationDisposition::TruncateExisting
95        } else {
96            throw_unsup_format!("CreateFileW: Unsupported creation disposition: {value}");
97        };
98        interp_ok(out)
99    }
100}
101
102bitflags! {
103    #[derive(PartialEq)]
104    struct FileAttributes: u32 {
105        const ZERO = 0;
106        const NORMAL = 1 << 0;
107        /// This must be passed to allow getting directory handles. If not passed, we error on trying
108        /// to open directories
109        const BACKUP_SEMANTICS = 1 << 1;
110        /// Open a reparse point as a regular file - this is basically similar to 'readlink' in Unix
111        /// terminology. A reparse point is a file with custom logic when navigated to, of which
112        /// a symlink is one specific example.
113        const OPEN_REPARSE = 1 << 2;
114    }
115}
116
117impl FileAttributes {
118    fn new<'tcx>(
119        mut value: u32,
120        ecx: &mut MiriInterpCx<'tcx>,
121    ) -> InterpResult<'tcx, FileAttributes> {
122        let file_attribute_normal = ecx.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL");
123        let file_flag_backup_semantics = ecx.eval_windows_u32("c", "FILE_FLAG_BACKUP_SEMANTICS");
124        let file_flag_open_reparse_point =
125            ecx.eval_windows_u32("c", "FILE_FLAG_OPEN_REPARSE_POINT");
126
127        let mut out = FileAttributes::ZERO;
128        if value & file_flag_backup_semantics != 0 {
129            value &= !file_flag_backup_semantics;
130            out |= FileAttributes::BACKUP_SEMANTICS;
131        }
132        if value & file_flag_open_reparse_point != 0 {
133            value &= !file_flag_open_reparse_point;
134            out |= FileAttributes::OPEN_REPARSE;
135        }
136        if value & file_attribute_normal != 0 {
137            value &= !file_attribute_normal;
138            out |= FileAttributes::NORMAL;
139        }
140
141        if value != 0 {
142            throw_unsup_format!("CreateFileW: Unsupported flags_and_attributes: {value}");
143        }
144
145        if out == FileAttributes::ZERO {
146            // NORMAL is equivalent to 0. Avoid needing to check both cases by unifying the two.
147            out = FileAttributes::NORMAL;
148        }
149        interp_ok(out)
150    }
151}
152
153impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
154#[allow(non_snake_case)]
155pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
156    fn CreateFileW(
157        &mut self,
158        file_name: &OpTy<'tcx>,            // LPCWSTR
159        desired_access: &OpTy<'tcx>,       // DWORD
160        share_mode: &OpTy<'tcx>,           // DWORD
161        security_attributes: &OpTy<'tcx>,  // LPSECURITY_ATTRIBUTES
162        creation_disposition: &OpTy<'tcx>, // DWORD
163        flags_and_attributes: &OpTy<'tcx>, // DWORD
164        template_file: &OpTy<'tcx>,        // HANDLE
165    ) -> InterpResult<'tcx, Handle> {
166        // ^ Returns HANDLE
167        use CreationDisposition::*;
168
169        let this = self.eval_context_mut();
170        this.assert_target_os(Os::Windows, "CreateFileW");
171        this.check_no_isolation("`CreateFileW`")?;
172
173        // This function appears to always set the error to 0. This is important for some flag
174        // combinations, which may set error code on success.
175        this.set_last_error(IoError::Raw(Scalar::from_i32(0)))?;
176
177        let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
178        let mut desired_access = this.read_scalar(desired_access)?.to_u32()?;
179        let share_mode = this.read_scalar(share_mode)?.to_u32()?;
180        let security_attributes = this.read_pointer(security_attributes)?;
181        let creation_disposition = this.read_scalar(creation_disposition)?.to_u32()?;
182        let flags_and_attributes = this.read_scalar(flags_and_attributes)?.to_u32()?;
183        let template_file = this.read_target_usize(template_file)?;
184
185        let generic_read = this.eval_windows_u32("c", "GENERIC_READ");
186        let generic_write = this.eval_windows_u32("c", "GENERIC_WRITE");
187
188        let file_share_delete = this.eval_windows_u32("c", "FILE_SHARE_DELETE");
189        let file_share_read = this.eval_windows_u32("c", "FILE_SHARE_READ");
190        let file_share_write = this.eval_windows_u32("c", "FILE_SHARE_WRITE");
191
192        let creation_disposition = CreationDisposition::new(creation_disposition, this)?;
193        let attributes = FileAttributes::new(flags_and_attributes, this)?;
194
195        if share_mode != (file_share_delete | file_share_read | file_share_write) {
196            throw_unsup_format!("CreateFileW: Unsupported share mode: {share_mode}");
197        }
198        if !this.ptr_is_null(security_attributes)? {
199            throw_unsup_format!("CreateFileW: Security attributes are not supported");
200        }
201
202        if attributes.contains(FileAttributes::OPEN_REPARSE) && creation_disposition == CreateAlways
203        {
204            throw_machine_stop!(TerminationInfo::Abort("Invalid CreateFileW argument combination: FILE_FLAG_OPEN_REPARSE_POINT with CREATE_ALWAYS".to_string()));
205        }
206
207        if template_file != 0 {
208            throw_unsup_format!("CreateFileW: Template files are not supported");
209        }
210
211        // We need to know if the file is a directory to correctly open directory handles.
212        // This is racy, but currently the stdlib doesn't appear to offer a better solution.
213        let is_dir = file_name.is_dir();
214
215        // BACKUP_SEMANTICS is how Windows calls the act of opening a directory handle.
216        if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) && is_dir {
217            this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
218            return interp_ok(Handle::Invalid);
219        }
220
221        let desired_read = desired_access & generic_read != 0;
222        let desired_write = desired_access & generic_write != 0;
223
224        let mut options = OpenOptions::new();
225        if desired_read {
226            desired_access &= !generic_read;
227            options.read(true);
228        }
229        if desired_write {
230            desired_access &= !generic_write;
231            options.write(true);
232        }
233
234        if desired_access != 0 {
235            throw_unsup_format!(
236                "CreateFileW: Unsupported bits set for access mode: {desired_access:#x}"
237            );
238        }
239
240        // Per the documentation:
241        // If the specified file exists and is writable, the function truncates the file,
242        // the function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS.
243        // If the specified file does not exist and is a valid path, a new file is created,
244        // the function succeeds, and the last-error code is set to zero.
245        // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
246        //
247        // This is racy, but there doesn't appear to be an std API that both succeeds if a
248        // file exists but tells us it isn't new. Either we accept racing one way or another,
249        // or we use an iffy heuristic like file creation time. This implementation prefers
250        // to fail in the direction of erroring more often.
251        if let CreateAlways | OpenAlways = creation_disposition
252            && file_name.exists()
253        {
254            this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
255        }
256
257        let handle = if is_dir {
258            // Open this as a directory.
259            let fd_num = this.machine.fds.insert_new(DirHandle { path: file_name });
260            Ok(Handle::File(fd_num))
261        } else if creation_disposition == OpenExisting && !(desired_read || desired_write) {
262            // Windows supports handles with no permissions. These allow things such as reading
263            // metadata, but not file content.
264            file_name.metadata().map(|meta| {
265                let fd_num = this.machine.fds.insert_new(MetadataHandle { meta });
266                Handle::File(fd_num)
267            })
268        } else {
269            // Open this as a standard file.
270            match creation_disposition {
271                CreateAlways | OpenAlways => {
272                    options.create(true);
273                    if creation_disposition == CreateAlways {
274                        options.truncate(true);
275                    }
276                }
277                CreateNew => {
278                    options.create_new(true);
279                    // Per `create_new` documentation:
280                    // The file must be opened with write or append access in order to create a new file.
281                    // https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new
282                    if !desired_write {
283                        options.append(true);
284                    }
285                }
286                OpenExisting => {} // Default options
287                TruncateExisting => {
288                    options.truncate(true);
289                }
290            }
291
292            options.open(file_name).map(|file| {
293                let fd_num =
294                    this.machine.fds.insert_new(FileHandle { file, writable: desired_write });
295                Handle::File(fd_num)
296            })
297        };
298
299        match handle {
300            Ok(handle) => interp_ok(handle),
301            Err(e) => {
302                this.set_last_error(e)?;
303                interp_ok(Handle::Invalid)
304            }
305        }
306    }
307
308    fn GetFileInformationByHandle(
309        &mut self,
310        file: &OpTy<'tcx>,             // HANDLE
311        file_information: &OpTy<'tcx>, // LPBY_HANDLE_FILE_INFORMATION
312    ) -> InterpResult<'tcx, Scalar> {
313        // ^ Returns BOOL (i32 on Windows)
314        let this = self.eval_context_mut();
315        this.assert_target_os(Os::Windows, "GetFileInformationByHandle");
316        this.check_no_isolation("`GetFileInformationByHandle`")?;
317
318        let file = this.read_handle(file, "GetFileInformationByHandle")?;
319        let file_information = this.deref_pointer_as(
320            file_information,
321            this.windows_ty_layout("BY_HANDLE_FILE_INFORMATION"),
322        )?;
323
324        let fd_num = if let Handle::File(fd_num) = file {
325            fd_num
326        } else {
327            this.invalid_handle("GetFileInformationByHandle")?
328        };
329
330        let Some(desc) = this.machine.fds.get(fd_num) else {
331            this.invalid_handle("GetFileInformationByHandle")?
332        };
333
334        let metadata = match desc.metadata()? {
335            Ok(meta) => meta,
336            Err(e) => {
337                this.set_last_error(e)?;
338                return interp_ok(this.eval_windows("c", "FALSE"));
339            }
340        };
341
342        let size = metadata.len();
343
344        let file_type = metadata.file_type();
345        let attributes = if file_type.is_dir() {
346            this.eval_windows_u32("c", "FILE_ATTRIBUTE_DIRECTORY")
347        } else if file_type.is_file() {
348            this.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL")
349        } else {
350            this.eval_windows_u32("c", "FILE_ATTRIBUTE_DEVICE")
351        };
352
353        // Per the Windows documentation:
354        // "If the underlying file system does not support the [...] time, this member is zero (0)."
355        // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information
356        let created = extract_windows_epoch(this, metadata.created())?.unwrap_or((0, 0));
357        let accessed = extract_windows_epoch(this, metadata.accessed())?.unwrap_or((0, 0));
358        let written = extract_windows_epoch(this, metadata.modified())?.unwrap_or((0, 0));
359
360        this.write_int_fields_named(&[("dwFileAttributes", attributes.into())], &file_information)?;
361        write_filetime_field(this, &file_information, "ftCreationTime", created)?;
362        write_filetime_field(this, &file_information, "ftLastAccessTime", accessed)?;
363        write_filetime_field(this, &file_information, "ftLastWriteTime", written)?;
364        this.write_int_fields_named(
365            &[
366                ("dwVolumeSerialNumber", 0),
367                ("nFileSizeHigh", (size >> 32).into()),
368                ("nFileSizeLow", (size & 0xFFFFFFFF).into()),
369                ("nNumberOfLinks", 1),
370                ("nFileIndexHigh", 0),
371                ("nFileIndexLow", 0),
372            ],
373            &file_information,
374        )?;
375
376        interp_ok(this.eval_windows("c", "TRUE"))
377    }
378
379    fn DeleteFileW(
380        &mut self,
381        file_name: &OpTy<'tcx>, // LPCWSTR
382    ) -> InterpResult<'tcx, Scalar> {
383        // ^ Returns BOOL (i32 on Windows)
384        let this = self.eval_context_mut();
385        this.assert_target_os(Os::Windows, "DeleteFileW");
386        this.check_no_isolation("`DeleteFileW`")?;
387
388        let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
389        match std::fs::remove_file(file_name) {
390            Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
391            Err(e) => {
392                this.set_last_error(e)?;
393                interp_ok(this.eval_windows("c", "FALSE"))
394            }
395        }
396    }
397
398    fn NtWriteFile(
399        &mut self,
400        handle: &OpTy<'tcx>,          // HANDLE
401        event: &OpTy<'tcx>,           // HANDLE
402        apc_routine: &OpTy<'tcx>,     // PIO_APC_ROUTINE
403        apc_ctx: &OpTy<'tcx>,         // PVOID
404        io_status_block: &OpTy<'tcx>, // PIO_STATUS_BLOCK
405        buf: &OpTy<'tcx>,             // PVOID
406        n: &OpTy<'tcx>,               // ULONG
407        byte_offset: &OpTy<'tcx>,     // PLARGE_INTEGER
408        key: &OpTy<'tcx>,             // PULONG
409        dest: &MPlaceTy<'tcx>,        // return type: NTSTATUS
410    ) -> InterpResult<'tcx, ()> {
411        let this = self.eval_context_mut();
412        let handle = this.read_handle(handle, "NtWriteFile")?;
413        let event = this.read_handle(event, "NtWriteFile")?;
414        let apc_routine = this.read_pointer(apc_routine)?;
415        let apc_ctx = this.read_pointer(apc_ctx)?;
416        let buf = this.read_pointer(buf)?;
417        let count = this.read_scalar(n)?.to_u32()?;
418        let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer, but we only support null
419        let key = this.read_pointer(key)?;
420        let io_status_block =
421            this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
422
423        if event != Handle::Null {
424            throw_unsup_format!(
425                "`NtWriteFile` `Event` parameter is non-null, which is unsupported"
426            );
427        }
428
429        if !this.ptr_is_null(apc_routine)? {
430            throw_unsup_format!(
431                "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported"
432            );
433        }
434
435        if !this.ptr_is_null(apc_ctx)? {
436            throw_unsup_format!(
437                "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported"
438            );
439        }
440
441        if byte_offset != 0 {
442            throw_unsup_format!(
443                "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
444            );
445        }
446
447        if !this.ptr_is_null(key)? {
448            throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported");
449        }
450
451        let fd = match handle {
452            Handle::File(fd) => fd,
453            _ => this.invalid_handle("NtWriteFile")?,
454        };
455
456        let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? };
457
458        // Windows writes the output code to IO_STATUS_BLOCK.Status, and number of bytes written
459        // to IO_STATUS_BLOCK.Information.
460        // The status block value and the returned value don't need to match - but
461        // for the cases implemented by miri so far, we can choose to decide that they do.
462        let io_status = {
463            let anon = this.project_field_named(&io_status_block, "Anonymous")?;
464            this.project_field_named(&anon, "Status")?
465        };
466        let io_status_info = this.project_field_named(&io_status_block, "Information")?;
467
468        // It seems like short writes are not a thing on Windows, so we don't truncate `count` here.
469        // FIXME: if we are on a Unix host, short host writes are still visible to the program!
470
471        let finish = {
472            let io_status = io_status.clone();
473            let io_status_info = io_status_info.clone();
474            let dest = dest.clone();
475            callback!(
476                @capture<'tcx> {
477                    count: u32,
478                    io_status: MPlaceTy<'tcx>,
479                    io_status_info: MPlaceTy<'tcx>,
480                    dest: MPlaceTy<'tcx>,
481                }
482                |this, result: Result<usize, IoError>| {
483                    match result {
484                        Ok(read_size) => {
485                            assert!(read_size <= count.try_into().unwrap());
486                            // This must fit since `count` fits.
487                            this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
488                            this.write_int(0, &io_status)?;
489                            this.write_int(0, &dest)
490                        }
491                        Err(e) => {
492                            this.write_int(0, &io_status_info)?;
493                            let status = e.into_ntstatus();
494                            this.write_int(status, &io_status)?;
495                            this.write_int(status, &dest)
496                        }
497                }}
498            )
499        };
500        desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
501
502        // Return status is written to `dest` and `io_status_block` on callback completion.
503        interp_ok(())
504    }
505
506    fn NtReadFile(
507        &mut self,
508        handle: &OpTy<'tcx>,          // HANDLE
509        event: &OpTy<'tcx>,           // HANDLE
510        apc_routine: &OpTy<'tcx>,     // PIO_APC_ROUTINE
511        apc_ctx: &OpTy<'tcx>,         // PVOID
512        io_status_block: &OpTy<'tcx>, // PIO_STATUS_BLOCK
513        buf: &OpTy<'tcx>,             // PVOID
514        n: &OpTy<'tcx>,               // ULONG
515        byte_offset: &OpTy<'tcx>,     // PLARGE_INTEGER
516        key: &OpTy<'tcx>,             // PULONG
517        dest: &MPlaceTy<'tcx>,        // return type: NTSTATUS
518    ) -> InterpResult<'tcx, ()> {
519        let this = self.eval_context_mut();
520        let handle = this.read_handle(handle, "NtReadFile")?;
521        let event = this.read_handle(event, "NtReadFile")?;
522        let apc_routine = this.read_pointer(apc_routine)?;
523        let apc_ctx = this.read_pointer(apc_ctx)?;
524        let buf = this.read_pointer(buf)?;
525        let count = this.read_scalar(n)?.to_u32()?;
526        let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer, but we only support null
527        let key = this.read_pointer(key)?;
528        let io_status_block =
529            this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
530
531        if event != Handle::Null {
532            throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
533        }
534
535        if !this.ptr_is_null(apc_routine)? {
536            throw_unsup_format!(
537                "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
538            );
539        }
540
541        if !this.ptr_is_null(apc_ctx)? {
542            throw_unsup_format!(
543                "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
544            );
545        }
546
547        if byte_offset != 0 {
548            throw_unsup_format!(
549                "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
550            );
551        }
552
553        if !this.ptr_is_null(key)? {
554            throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
555        }
556
557        // See NtWriteFile above for commentary on this
558        let io_status = {
559            let anon = this.project_field_named(&io_status_block, "Anonymous")?;
560            this.project_field_named(&anon, "Status")?
561        };
562        let io_status_info = this.project_field_named(&io_status_block, "Information")?;
563
564        let fd = match handle {
565            Handle::File(fd) => fd,
566            _ => this.invalid_handle("NtWriteFile")?,
567        };
568
569        let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
570
571        // It seems like short reads are not a thing on Windows, so we don't truncate `count` here.
572        // FIXME: if we are on a Unix host, short host reads are still visible to the program!
573
574        let finish = {
575            let io_status = io_status.clone();
576            let io_status_info = io_status_info.clone();
577            let dest = dest.clone();
578            callback!(
579                @capture<'tcx> {
580                    count: u32,
581                    io_status: MPlaceTy<'tcx>,
582                    io_status_info: MPlaceTy<'tcx>,
583                    dest: MPlaceTy<'tcx>,
584                }
585                |this, result: Result<usize, IoError>| {
586                    match result {
587                        Ok(read_size) => {
588                            assert!(read_size <= count.try_into().unwrap());
589                            // This must fit since `count` fits.
590                            this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
591                            this.write_int(0, &io_status)?;
592                            this.write_int(0, &dest)
593                        }
594                        Err(e) => {
595                            this.write_int(0, &io_status_info)?;
596                            let status = e.into_ntstatus();
597                            this.write_int(status, &io_status)?;
598                            this.write_int(status, &dest)
599                        }
600                }}
601            )
602        };
603        desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
604
605        // See NtWriteFile for commentary on this
606        interp_ok(())
607    }
608
609    fn SetFilePointerEx(
610        &mut self,
611        file: &OpTy<'tcx>,         // HANDLE
612        dist_to_move: &OpTy<'tcx>, // LARGE_INTEGER
613        new_fp: &OpTy<'tcx>,       // PLARGE_INTEGER
614        move_method: &OpTy<'tcx>,  // DWORD
615    ) -> InterpResult<'tcx, Scalar> {
616        // ^ Returns BOOL (i32 on Windows)
617        let this = self.eval_context_mut();
618        let file = this.read_handle(file, "SetFilePointerEx")?;
619        let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?;
620        let new_fp_ptr = this.read_pointer(new_fp)?;
621        let move_method = this.read_scalar(move_method)?.to_u32()?;
622
623        let fd = match file {
624            Handle::File(fd) => fd,
625            _ => this.invalid_handle("SetFilePointerEx")?,
626        };
627
628        let Some(desc) = this.machine.fds.get(fd) else {
629            throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
630        };
631
632        let file_begin = this.eval_windows_u32("c", "FILE_BEGIN");
633        let file_current = this.eval_windows_u32("c", "FILE_CURRENT");
634        let file_end = this.eval_windows_u32("c", "FILE_END");
635
636        let seek = if move_method == file_begin {
637            SeekFrom::Start(dist_to_move.try_into().unwrap())
638        } else if move_method == file_current {
639            SeekFrom::Current(dist_to_move)
640        } else if move_method == file_end {
641            SeekFrom::End(dist_to_move)
642        } else {
643            throw_unsup_format!("Invalid move method: {move_method}")
644        };
645
646        match desc.seek(this.machine.communicate(), seek)? {
647            Ok(n) => {
648                if !this.ptr_is_null(new_fp_ptr)? {
649                    this.write_scalar(
650                        Scalar::from_i64(n.try_into().unwrap()),
651                        &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?,
652                    )?;
653                }
654                interp_ok(this.eval_windows("c", "TRUE"))
655            }
656            Err(e) => {
657                this.set_last_error(e)?;
658                interp_ok(this.eval_windows("c", "FALSE"))
659            }
660        }
661    }
662}
663
664/// Windows FILETIME is measured in 100-nanosecs since 1601
665fn extract_windows_epoch<'tcx>(
666    ecx: &MiriInterpCx<'tcx>,
667    time: io::Result<SystemTime>,
668) -> InterpResult<'tcx, Option<(u32, u32)>> {
669    match time.ok() {
670        Some(time) => {
671            let duration = ecx.system_time_since_windows_epoch(&time)?;
672            let duration_ticks = ecx.windows_ticks_for(duration)?;
673            #[expect(clippy::as_conversions)]
674            interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
675        }
676        None => interp_ok(None),
677    }
678}
679
680fn write_filetime_field<'tcx>(
681    cx: &mut MiriInterpCx<'tcx>,
682    val: &MPlaceTy<'tcx>,
683    name: &str,
684    (low, high): (u32, u32),
685) -> InterpResult<'tcx> {
686    cx.write_int_fields_named(
687        &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
688        &cx.project_field_named(val, name)?,
689    )
690}