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