miri/shims/windows/
fs.rs

1use std::fs::{Metadata, OpenOptions};
2use std::io;
3use std::path::PathBuf;
4use std::time::SystemTime;
5
6use bitflags::bitflags;
7
8use crate::shims::files::{FileDescription, FileHandle};
9use crate::shims::windows::handle::{EvalContextExt as _, Handle};
10use crate::*;
11
12#[derive(Debug)]
13pub struct DirHandle {
14    pub(crate) path: PathBuf,
15}
16
17impl FileDescription for DirHandle {
18    fn name(&self) -> &'static str {
19        "directory"
20    }
21
22    fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
23        interp_ok(self.path.metadata())
24    }
25
26    fn close<'tcx>(
27        self,
28        _communicate_allowed: bool,
29        _ecx: &mut MiriInterpCx<'tcx>,
30    ) -> InterpResult<'tcx, io::Result<()>> {
31        interp_ok(Ok(()))
32    }
33}
34
35/// Windows supports handles without any read/write/delete permissions - these handles can get
36/// metadata, but little else. We represent that by storing the metadata from the time the handle
37/// was opened.
38#[derive(Debug)]
39pub struct MetadataHandle {
40    pub(crate) meta: Metadata,
41}
42
43impl FileDescription for MetadataHandle {
44    fn name(&self) -> &'static str {
45        "metadata-only"
46    }
47
48    fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
49        interp_ok(Ok(self.meta.clone()))
50    }
51
52    fn close<'tcx>(
53        self,
54        _communicate_allowed: bool,
55        _ecx: &mut MiriInterpCx<'tcx>,
56    ) -> InterpResult<'tcx, io::Result<()>> {
57        interp_ok(Ok(()))
58    }
59}
60
61#[derive(Copy, Clone, Debug, PartialEq)]
62enum CreationDisposition {
63    CreateAlways,
64    CreateNew,
65    OpenAlways,
66    OpenExisting,
67    TruncateExisting,
68}
69
70impl CreationDisposition {
71    fn new<'tcx>(
72        value: u32,
73        ecx: &mut MiriInterpCx<'tcx>,
74    ) -> InterpResult<'tcx, CreationDisposition> {
75        let create_always = ecx.eval_windows_u32("c", "CREATE_ALWAYS");
76        let create_new = ecx.eval_windows_u32("c", "CREATE_NEW");
77        let open_always = ecx.eval_windows_u32("c", "OPEN_ALWAYS");
78        let open_existing = ecx.eval_windows_u32("c", "OPEN_EXISTING");
79        let truncate_existing = ecx.eval_windows_u32("c", "TRUNCATE_EXISTING");
80
81        let out = if value == create_always {
82            CreationDisposition::CreateAlways
83        } else if value == create_new {
84            CreationDisposition::CreateNew
85        } else if value == open_always {
86            CreationDisposition::OpenAlways
87        } else if value == open_existing {
88            CreationDisposition::OpenExisting
89        } else if value == truncate_existing {
90            CreationDisposition::TruncateExisting
91        } else {
92            throw_unsup_format!("CreateFileW: Unsupported creation disposition: {value}");
93        };
94        interp_ok(out)
95    }
96}
97
98bitflags! {
99    #[derive(PartialEq)]
100    struct FileAttributes: u32 {
101        const ZERO = 0;
102        const NORMAL = 1 << 0;
103        /// This must be passed to allow getting directory handles. If not passed, we error on trying
104        /// to open directories
105        const BACKUP_SEMANTICS = 1 << 1;
106        /// Open a reparse point as a regular file - this is basically similar to 'readlink' in Unix
107        /// terminology. A reparse point is a file with custom logic when navigated to, of which
108        /// a symlink is one specific example.
109        const OPEN_REPARSE = 1 << 2;
110    }
111}
112
113impl FileAttributes {
114    fn new<'tcx>(
115        mut value: u32,
116        ecx: &mut MiriInterpCx<'tcx>,
117    ) -> InterpResult<'tcx, FileAttributes> {
118        let file_attribute_normal = ecx.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL");
119        let file_flag_backup_semantics = ecx.eval_windows_u32("c", "FILE_FLAG_BACKUP_SEMANTICS");
120        let file_flag_open_reparse_point =
121            ecx.eval_windows_u32("c", "FILE_FLAG_OPEN_REPARSE_POINT");
122
123        let mut out = FileAttributes::ZERO;
124        if value & file_flag_backup_semantics != 0 {
125            value &= !file_flag_backup_semantics;
126            out |= FileAttributes::BACKUP_SEMANTICS;
127        }
128        if value & file_flag_open_reparse_point != 0 {
129            value &= !file_flag_open_reparse_point;
130            out |= FileAttributes::OPEN_REPARSE;
131        }
132        if value & file_attribute_normal != 0 {
133            value &= !file_attribute_normal;
134            out |= FileAttributes::NORMAL;
135        }
136
137        if value != 0 {
138            throw_unsup_format!("CreateFileW: Unsupported flags_and_attributes: {value}");
139        }
140
141        if out == FileAttributes::ZERO {
142            // NORMAL is equivalent to 0. Avoid needing to check both cases by unifying the two.
143            out = FileAttributes::NORMAL;
144        }
145        interp_ok(out)
146    }
147}
148
149impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
150#[allow(non_snake_case)]
151pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
152    fn CreateFileW(
153        &mut self,
154        file_name: &OpTy<'tcx>,            // LPCWSTR
155        desired_access: &OpTy<'tcx>,       // DWORD
156        share_mode: &OpTy<'tcx>,           // DWORD
157        security_attributes: &OpTy<'tcx>,  // LPSECURITY_ATTRIBUTES
158        creation_disposition: &OpTy<'tcx>, // DWORD
159        flags_and_attributes: &OpTy<'tcx>, // DWORD
160        template_file: &OpTy<'tcx>,        // HANDLE
161    ) -> InterpResult<'tcx, Handle> {
162        // ^ Returns HANDLE
163        use CreationDisposition::*;
164
165        let this = self.eval_context_mut();
166        this.assert_target_os("windows", "CreateFileW");
167        this.check_no_isolation("`CreateFileW`")?;
168
169        // This function appears to always set the error to 0. This is important for some flag
170        // combinations, which may set error code on success.
171        this.set_last_error(IoError::Raw(Scalar::from_i32(0)))?;
172
173        let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
174        let mut desired_access = this.read_scalar(desired_access)?.to_u32()?;
175        let share_mode = this.read_scalar(share_mode)?.to_u32()?;
176        let security_attributes = this.read_pointer(security_attributes)?;
177        let creation_disposition = this.read_scalar(creation_disposition)?.to_u32()?;
178        let flags_and_attributes = this.read_scalar(flags_and_attributes)?.to_u32()?;
179        let template_file = this.read_target_usize(template_file)?;
180
181        let generic_read = this.eval_windows_u32("c", "GENERIC_READ");
182        let generic_write = this.eval_windows_u32("c", "GENERIC_WRITE");
183
184        let file_share_delete = this.eval_windows_u32("c", "FILE_SHARE_DELETE");
185        let file_share_read = this.eval_windows_u32("c", "FILE_SHARE_READ");
186        let file_share_write = this.eval_windows_u32("c", "FILE_SHARE_WRITE");
187
188        let creation_disposition = CreationDisposition::new(creation_disposition, this)?;
189        let attributes = FileAttributes::new(flags_and_attributes, this)?;
190
191        if share_mode != (file_share_delete | file_share_read | file_share_write) {
192            throw_unsup_format!("CreateFileW: Unsupported share mode: {share_mode}");
193        }
194        if !this.ptr_is_null(security_attributes)? {
195            throw_unsup_format!("CreateFileW: Security attributes are not supported");
196        }
197
198        if attributes.contains(FileAttributes::OPEN_REPARSE) && creation_disposition == CreateAlways
199        {
200            throw_machine_stop!(TerminationInfo::Abort("Invalid CreateFileW argument combination: FILE_FLAG_OPEN_REPARSE_POINT with CREATE_ALWAYS".to_string()));
201        }
202
203        if template_file != 0 {
204            throw_unsup_format!("CreateFileW: Template files are not supported");
205        }
206
207        // We need to know if the file is a directory to correctly open directory handles.
208        // This is racy, but currently the stdlib doesn't appear to offer a better solution.
209        let is_dir = file_name.is_dir();
210
211        // BACKUP_SEMANTICS is how Windows calls the act of opening a directory handle.
212        if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) && is_dir {
213            this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
214            return interp_ok(Handle::Invalid);
215        }
216
217        let desired_read = desired_access & generic_read != 0;
218        let desired_write = desired_access & generic_write != 0;
219
220        let mut options = OpenOptions::new();
221        if desired_read {
222            desired_access &= !generic_read;
223            options.read(true);
224        }
225        if desired_write {
226            desired_access &= !generic_write;
227            options.write(true);
228        }
229
230        if desired_access != 0 {
231            throw_unsup_format!(
232                "CreateFileW: Unsupported bits set for access mode: {desired_access:#x}"
233            );
234        }
235
236        // Per the documentation:
237        // If the specified file exists and is writable, the function truncates the file,
238        // the function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS.
239        // If the specified file does not exist and is a valid path, a new file is created,
240        // the function succeeds, and the last-error code is set to zero.
241        // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
242        //
243        // This is racy, but there doesn't appear to be an std API that both succeeds if a
244        // file exists but tells us it isn't new. Either we accept racing one way or another,
245        // or we use an iffy heuristic like file creation time. This implementation prefers
246        // to fail in the direction of erroring more often.
247        if let CreateAlways | OpenAlways = creation_disposition
248            && file_name.exists()
249        {
250            this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
251        }
252
253        let handle = if is_dir {
254            // Open this as a directory.
255            let fd_num = this.machine.fds.insert_new(DirHandle { path: file_name });
256            Ok(Handle::File(fd_num))
257        } else if creation_disposition == OpenExisting && !(desired_read || desired_write) {
258            // Windows supports handles with no permissions. These allow things such as reading
259            // metadata, but not file content.
260            file_name.metadata().map(|meta| {
261                let fd_num = this.machine.fds.insert_new(MetadataHandle { meta });
262                Handle::File(fd_num)
263            })
264        } else {
265            // Open this as a standard file.
266            match creation_disposition {
267                CreateAlways | OpenAlways => {
268                    options.create(true);
269                    if creation_disposition == CreateAlways {
270                        options.truncate(true);
271                    }
272                }
273                CreateNew => {
274                    options.create_new(true);
275                    // Per `create_new` documentation:
276                    // The file must be opened with write or append access in order to create a new file.
277                    // https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new
278                    if !desired_write {
279                        options.append(true);
280                    }
281                }
282                OpenExisting => {} // Default options
283                TruncateExisting => {
284                    options.truncate(true);
285                }
286            }
287
288            options.open(file_name).map(|file| {
289                let fd_num =
290                    this.machine.fds.insert_new(FileHandle { file, writable: desired_write });
291                Handle::File(fd_num)
292            })
293        };
294
295        match handle {
296            Ok(handle) => interp_ok(handle),
297            Err(e) => {
298                this.set_last_error(e)?;
299                interp_ok(Handle::Invalid)
300            }
301        }
302    }
303
304    fn GetFileInformationByHandle(
305        &mut self,
306        file: &OpTy<'tcx>,             // HANDLE
307        file_information: &OpTy<'tcx>, // LPBY_HANDLE_FILE_INFORMATION
308    ) -> InterpResult<'tcx, Scalar> {
309        // ^ Returns BOOL (i32 on Windows)
310        let this = self.eval_context_mut();
311        this.assert_target_os("windows", "GetFileInformationByHandle");
312        this.check_no_isolation("`GetFileInformationByHandle`")?;
313
314        let file = this.read_handle(file, "GetFileInformationByHandle")?;
315        let file_information = this.deref_pointer_as(
316            file_information,
317            this.windows_ty_layout("BY_HANDLE_FILE_INFORMATION"),
318        )?;
319
320        let fd_num = if let Handle::File(fd_num) = file {
321            fd_num
322        } else {
323            this.invalid_handle("GetFileInformationByHandle")?
324        };
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("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
395/// Windows FILETIME is measured in 100-nanosecs since 1601
396fn extract_windows_epoch<'tcx>(
397    ecx: &MiriInterpCx<'tcx>,
398    time: io::Result<SystemTime>,
399) -> InterpResult<'tcx, Option<(u32, u32)>> {
400    match time.ok() {
401        Some(time) => {
402            let duration = ecx.system_time_since_windows_epoch(&time)?;
403            let duration_ticks = ecx.windows_ticks_for(duration)?;
404            #[allow(clippy::cast_possible_truncation)]
405            interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
406        }
407        None => interp_ok(None),
408    }
409}
410
411fn write_filetime_field<'tcx>(
412    cx: &mut MiriInterpCx<'tcx>,
413    val: &MPlaceTy<'tcx>,
414    name: &str,
415    (low, high): (u32, u32),
416) -> InterpResult<'tcx> {
417    cx.write_int_fields_named(
418        &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
419        &cx.project_field_named(val, name)?,
420    )
421}