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 Handle::File(fd_num) = file else { this.invalid_handle("GetFileInformationByHandle")? };
325
326        let Some(desc) = this.machine.fds.get(fd_num) else {
327            this.invalid_handle("GetFileInformationByHandle")?
328        };
329
330        let metadata = match desc.metadata()? {
331            Ok(meta) => meta,
332            Err(e) => {
333                this.set_last_error(e)?;
334                return interp_ok(this.eval_windows("c", "FALSE"));
335            }
336        };
337
338        let size = metadata.len();
339
340        let file_type = metadata.file_type();
341        let attributes = if file_type.is_dir() {
342            this.eval_windows_u32("c", "FILE_ATTRIBUTE_DIRECTORY")
343        } else if file_type.is_file() {
344            this.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL")
345        } else {
346            this.eval_windows_u32("c", "FILE_ATTRIBUTE_DEVICE")
347        };
348
349        // Per the Windows documentation:
350        // "If the underlying file system does not support the [...] time, this member is zero (0)."
351        // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information
352        let created = extract_windows_epoch(this, metadata.created())?.unwrap_or((0, 0));
353        let accessed = extract_windows_epoch(this, metadata.accessed())?.unwrap_or((0, 0));
354        let written = extract_windows_epoch(this, metadata.modified())?.unwrap_or((0, 0));
355
356        this.write_int_fields_named(&[("dwFileAttributes", attributes.into())], &file_information)?;
357        write_filetime_field(this, &file_information, "ftCreationTime", created)?;
358        write_filetime_field(this, &file_information, "ftLastAccessTime", accessed)?;
359        write_filetime_field(this, &file_information, "ftLastWriteTime", written)?;
360        this.write_int_fields_named(
361            &[
362                ("dwVolumeSerialNumber", 0),
363                ("nFileSizeHigh", (size >> 32).into()),
364                ("nFileSizeLow", (size & 0xFFFFFFFF).into()),
365                ("nNumberOfLinks", 1),
366                ("nFileIndexHigh", 0),
367                ("nFileIndexLow", 0),
368            ],
369            &file_information,
370        )?;
371
372        interp_ok(this.eval_windows("c", "TRUE"))
373    }
374
375    fn DeleteFileW(
376        &mut self,
377        file_name: &OpTy<'tcx>, // LPCWSTR
378    ) -> InterpResult<'tcx, Scalar> {
379        // ^ Returns BOOL (i32 on Windows)
380        let this = self.eval_context_mut();
381        this.assert_target_os(Os::Windows, "DeleteFileW");
382        this.check_no_isolation("`DeleteFileW`")?;
383
384        let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
385        match std::fs::remove_file(file_name) {
386            Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
387            Err(e) => {
388                this.set_last_error(e)?;
389                interp_ok(this.eval_windows("c", "FALSE"))
390            }
391        }
392    }
393
394    fn NtWriteFile(
395        &mut self,
396        handle: &OpTy<'tcx>,          // HANDLE
397        event: &OpTy<'tcx>,           // HANDLE
398        apc_routine: &OpTy<'tcx>,     // PIO_APC_ROUTINE
399        apc_ctx: &OpTy<'tcx>,         // PVOID
400        io_status_block: &OpTy<'tcx>, // PIO_STATUS_BLOCK
401        buf: &OpTy<'tcx>,             // PVOID
402        n: &OpTy<'tcx>,               // ULONG
403        byte_offset: &OpTy<'tcx>,     // PLARGE_INTEGER
404        key: &OpTy<'tcx>,             // PULONG
405        dest: &MPlaceTy<'tcx>,        // return type: NTSTATUS
406    ) -> InterpResult<'tcx, ()> {
407        let this = self.eval_context_mut();
408        let handle = this.read_handle(handle, "NtWriteFile")?;
409        let event = this.read_handle(event, "NtWriteFile")?;
410        let apc_routine = this.read_pointer(apc_routine)?;
411        let apc_ctx = this.read_pointer(apc_ctx)?;
412        let buf = this.read_pointer(buf)?;
413        let count = this.read_scalar(n)?.to_u32()?;
414        let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer, but we only support null
415        let key = this.read_pointer(key)?;
416        let io_status_block =
417            this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
418
419        if event != Handle::Null {
420            throw_unsup_format!(
421                "`NtWriteFile` `Event` parameter is non-null, which is unsupported"
422            );
423        }
424
425        if !this.ptr_is_null(apc_routine)? {
426            throw_unsup_format!(
427                "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported"
428            );
429        }
430
431        if !this.ptr_is_null(apc_ctx)? {
432            throw_unsup_format!(
433                "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported"
434            );
435        }
436
437        if byte_offset != 0 {
438            throw_unsup_format!(
439                "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
440            );
441        }
442
443        if !this.ptr_is_null(key)? {
444            throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported");
445        }
446
447        let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
448
449        let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? };
450
451        // Windows writes the output code to IO_STATUS_BLOCK.Status, and number of bytes written
452        // to IO_STATUS_BLOCK.Information.
453        // The status block value and the returned value don't need to match - but
454        // for the cases implemented by miri so far, we can choose to decide that they do.
455        let io_status = {
456            let anon = this.project_field_named(&io_status_block, "Anonymous")?;
457            this.project_field_named(&anon, "Status")?
458        };
459        let io_status_info = this.project_field_named(&io_status_block, "Information")?;
460
461        // It seems like short writes are not a thing on Windows, so we don't truncate `count` here.
462        // FIXME: if we are on a Unix host, short host writes are still visible to the program!
463
464        let finish = {
465            let io_status = io_status.clone();
466            let io_status_info = io_status_info.clone();
467            let dest = dest.clone();
468            callback!(
469                @capture<'tcx> {
470                    count: u32,
471                    io_status: MPlaceTy<'tcx>,
472                    io_status_info: MPlaceTy<'tcx>,
473                    dest: MPlaceTy<'tcx>,
474                }
475                |this, result: Result<usize, IoError>| {
476                    match result {
477                        Ok(read_size) => {
478                            assert!(read_size <= count.try_into().unwrap());
479                            // This must fit since `count` fits.
480                            this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
481                            this.write_int(0, &io_status)?;
482                            this.write_int(0, &dest)
483                        }
484                        Err(e) => {
485                            this.write_int(0, &io_status_info)?;
486                            let status = e.into_ntstatus();
487                            this.write_int(status, &io_status)?;
488                            this.write_int(status, &dest)
489                        }
490                }}
491            )
492        };
493        desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
494
495        // Return status is written to `dest` and `io_status_block` on callback completion.
496        interp_ok(())
497    }
498
499    fn NtReadFile(
500        &mut self,
501        handle: &OpTy<'tcx>,          // HANDLE
502        event: &OpTy<'tcx>,           // HANDLE
503        apc_routine: &OpTy<'tcx>,     // PIO_APC_ROUTINE
504        apc_ctx: &OpTy<'tcx>,         // PVOID
505        io_status_block: &OpTy<'tcx>, // PIO_STATUS_BLOCK
506        buf: &OpTy<'tcx>,             // PVOID
507        n: &OpTy<'tcx>,               // ULONG
508        byte_offset: &OpTy<'tcx>,     // PLARGE_INTEGER
509        key: &OpTy<'tcx>,             // PULONG
510        dest: &MPlaceTy<'tcx>,        // return type: NTSTATUS
511    ) -> InterpResult<'tcx, ()> {
512        let this = self.eval_context_mut();
513        let handle = this.read_handle(handle, "NtReadFile")?;
514        let event = this.read_handle(event, "NtReadFile")?;
515        let apc_routine = this.read_pointer(apc_routine)?;
516        let apc_ctx = this.read_pointer(apc_ctx)?;
517        let buf = this.read_pointer(buf)?;
518        let count = this.read_scalar(n)?.to_u32()?;
519        let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer, but we only support null
520        let key = this.read_pointer(key)?;
521        let io_status_block =
522            this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
523
524        if event != Handle::Null {
525            throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
526        }
527
528        if !this.ptr_is_null(apc_routine)? {
529            throw_unsup_format!(
530                "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
531            );
532        }
533
534        if !this.ptr_is_null(apc_ctx)? {
535            throw_unsup_format!(
536                "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
537            );
538        }
539
540        if byte_offset != 0 {
541            throw_unsup_format!(
542                "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
543            );
544        }
545
546        if !this.ptr_is_null(key)? {
547            throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
548        }
549
550        // See NtWriteFile above for commentary on this
551        let io_status = {
552            let anon = this.project_field_named(&io_status_block, "Anonymous")?;
553            this.project_field_named(&anon, "Status")?
554        };
555        let io_status_info = this.project_field_named(&io_status_block, "Information")?;
556
557        let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
558
559        let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
560
561        // It seems like short reads are not a thing on Windows, so we don't truncate `count` here.
562        // FIXME: if we are on a Unix host, short host reads are still visible to the program!
563
564        let finish = {
565            let io_status = io_status.clone();
566            let io_status_info = io_status_info.clone();
567            let dest = dest.clone();
568            callback!(
569                @capture<'tcx> {
570                    count: u32,
571                    io_status: MPlaceTy<'tcx>,
572                    io_status_info: MPlaceTy<'tcx>,
573                    dest: MPlaceTy<'tcx>,
574                }
575                |this, result: Result<usize, IoError>| {
576                    match result {
577                        Ok(read_size) => {
578                            assert!(read_size <= count.try_into().unwrap());
579                            // This must fit since `count` fits.
580                            this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
581                            this.write_int(0, &io_status)?;
582                            this.write_int(0, &dest)
583                        }
584                        Err(e) => {
585                            this.write_int(0, &io_status_info)?;
586                            let status = e.into_ntstatus();
587                            this.write_int(status, &io_status)?;
588                            this.write_int(status, &dest)
589                        }
590                }}
591            )
592        };
593        desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
594
595        // See NtWriteFile for commentary on this
596        interp_ok(())
597    }
598
599    fn SetFilePointerEx(
600        &mut self,
601        file: &OpTy<'tcx>,         // HANDLE
602        dist_to_move: &OpTy<'tcx>, // LARGE_INTEGER
603        new_fp: &OpTy<'tcx>,       // PLARGE_INTEGER
604        move_method: &OpTy<'tcx>,  // DWORD
605    ) -> InterpResult<'tcx, Scalar> {
606        // ^ Returns BOOL (i32 on Windows)
607        let this = self.eval_context_mut();
608        let file = this.read_handle(file, "SetFilePointerEx")?;
609        let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?;
610        let new_fp_ptr = this.read_pointer(new_fp)?;
611        let move_method = this.read_scalar(move_method)?.to_u32()?;
612
613        let Handle::File(fd) = file else { this.invalid_handle("SetFilePointerEx")? };
614
615        let Some(desc) = this.machine.fds.get(fd) else {
616            throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
617        };
618
619        let file_begin = this.eval_windows_u32("c", "FILE_BEGIN");
620        let file_current = this.eval_windows_u32("c", "FILE_CURRENT");
621        let file_end = this.eval_windows_u32("c", "FILE_END");
622
623        let seek = if move_method == file_begin {
624            SeekFrom::Start(dist_to_move.try_into().unwrap())
625        } else if move_method == file_current {
626            SeekFrom::Current(dist_to_move)
627        } else if move_method == file_end {
628            SeekFrom::End(dist_to_move)
629        } else {
630            throw_unsup_format!("Invalid move method: {move_method}")
631        };
632
633        match desc.seek(this.machine.communicate(), seek)? {
634            Ok(n) => {
635                if !this.ptr_is_null(new_fp_ptr)? {
636                    this.write_scalar(
637                        Scalar::from_i64(n.try_into().unwrap()),
638                        &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?,
639                    )?;
640                }
641                interp_ok(this.eval_windows("c", "TRUE"))
642            }
643            Err(e) => {
644                this.set_last_error(e)?;
645                interp_ok(this.eval_windows("c", "FALSE"))
646            }
647        }
648    }
649}
650
651/// Windows FILETIME is measured in 100-nanosecs since 1601
652fn extract_windows_epoch<'tcx>(
653    ecx: &MiriInterpCx<'tcx>,
654    time: io::Result<SystemTime>,
655) -> InterpResult<'tcx, Option<(u32, u32)>> {
656    match time.ok() {
657        Some(time) => {
658            let duration = ecx.system_time_since_windows_epoch(&time)?;
659            let duration_ticks = ecx.windows_ticks_for(duration)?;
660            #[expect(clippy::as_conversions)]
661            interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
662        }
663        None => interp_ok(None),
664    }
665}
666
667fn write_filetime_field<'tcx>(
668    cx: &mut MiriInterpCx<'tcx>,
669    val: &MPlaceTy<'tcx>,
670    name: &str,
671    (low, high): (u32, u32),
672) -> InterpResult<'tcx> {
673    cx.write_int_fields_named(
674        &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
675        &cx.project_field_named(val, name)?,
676    )
677}