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#[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 const BACKUP_SEMANTICS = 1 << 1;
106 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 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>, desired_access: &OpTy<'tcx>, share_mode: &OpTy<'tcx>, security_attributes: &OpTy<'tcx>, creation_disposition: &OpTy<'tcx>, flags_and_attributes: &OpTy<'tcx>, template_file: &OpTy<'tcx>, ) -> InterpResult<'tcx, Handle> {
162 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.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 let is_dir = file_name.is_dir();
210
211 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 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 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 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 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 if !desired_write {
279 options.append(true);
280 }
281 }
282 OpenExisting => {} 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>, file_information: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
309 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 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>, ) -> InterpResult<'tcx, Scalar> {
379 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
395fn 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}