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#[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 const BACKUP_SEMANTICS = 1 << 1;
107 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 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>, 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> {
163 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.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 let is_dir = file_name.is_dir();
211
212 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 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 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 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 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 if !desired_write {
280 options.append(true);
281 }
282 }
283 OpenExisting => {} 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>, file_information: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
310 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 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>, ) -> InterpResult<'tcx, Scalar> {
380 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>, event: &OpTy<'tcx>, apc_routine: &OpTy<'tcx>, apc_ctx: &OpTy<'tcx>, io_status_block: &OpTy<'tcx>, buf: &OpTy<'tcx>, n: &OpTy<'tcx>, byte_offset: &OpTy<'tcx>, key: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> 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)?; 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 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 = {
469 let io_status = io_status.clone();
470 let io_status_info = io_status_info.clone();
471 let dest = dest.clone();
472 callback!(
473 @capture<'tcx> {
474 count: u32,
475 io_status: MPlaceTy<'tcx>,
476 io_status_info: MPlaceTy<'tcx>,
477 dest: MPlaceTy<'tcx>,
478 }
479 |this, result: Result<usize, IoError>| {
480 match result {
481 Ok(read_size) => {
482 assert!(read_size <= count.try_into().unwrap());
483 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
485 this.write_int(0, &io_status)?;
486 this.write_int(0, &dest)
487 }
488 Err(e) => {
489 this.write_int(0, &io_status_info)?;
490 let status = e.into_ntstatus();
491 this.write_int(status, &io_status)?;
492 this.write_int(status, &dest)
493 }
494 }}
495 )
496 };
497 desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
498
499 interp_ok(())
501 }
502
503 fn NtReadFile(
504 &mut self,
505 handle: &OpTy<'tcx>, event: &OpTy<'tcx>, apc_routine: &OpTy<'tcx>, apc_ctx: &OpTy<'tcx>, io_status_block: &OpTy<'tcx>, buf: &OpTy<'tcx>, n: &OpTy<'tcx>, byte_offset: &OpTy<'tcx>, key: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> {
516 let this = self.eval_context_mut();
517 let handle = this.read_handle(handle, "NtReadFile")?;
518 let event = this.read_handle(event, "NtReadFile")?;
519 let apc_routine = this.read_pointer(apc_routine)?;
520 let apc_ctx = this.read_pointer(apc_ctx)?;
521 let buf = this.read_pointer(buf)?;
522 let count = this.read_scalar(n)?.to_u32()?;
523 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
525 let io_status_block =
526 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
527
528 if event != Handle::Null {
529 throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
530 }
531
532 if !this.ptr_is_null(apc_routine)? {
533 throw_unsup_format!(
534 "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
535 );
536 }
537
538 if !this.ptr_is_null(apc_ctx)? {
539 throw_unsup_format!(
540 "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
541 );
542 }
543
544 if byte_offset != 0 {
545 throw_unsup_format!(
546 "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
547 );
548 }
549
550 if !this.ptr_is_null(key)? {
551 throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
552 }
553
554 let io_status = {
556 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
557 this.project_field_named(&anon, "Status")?
558 };
559 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
560
561 let fd = match handle {
562 Handle::File(fd) => fd,
563 _ => this.invalid_handle("NtWriteFile")?,
564 };
565
566 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
567
568 let finish = {
572 let io_status = io_status.clone();
573 let io_status_info = io_status_info.clone();
574 let dest = dest.clone();
575 callback!(
576 @capture<'tcx> {
577 count: u32,
578 io_status: MPlaceTy<'tcx>,
579 io_status_info: MPlaceTy<'tcx>,
580 dest: MPlaceTy<'tcx>,
581 }
582 |this, result: Result<usize, IoError>| {
583 match result {
584 Ok(read_size) => {
585 assert!(read_size <= count.try_into().unwrap());
586 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
588 this.write_int(0, &io_status)?;
589 this.write_int(0, &dest)
590 }
591 Err(e) => {
592 this.write_int(0, &io_status_info)?;
593 let status = e.into_ntstatus();
594 this.write_int(status, &io_status)?;
595 this.write_int(status, &dest)
596 }
597 }}
598 )
599 };
600 desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
601
602 interp_ok(())
604 }
605
606 fn SetFilePointerEx(
607 &mut self,
608 file: &OpTy<'tcx>, dist_to_move: &OpTy<'tcx>, new_fp: &OpTy<'tcx>, move_method: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
613 let this = self.eval_context_mut();
615 let file = this.read_handle(file, "SetFilePointerEx")?;
616 let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?;
617 let new_fp_ptr = this.read_pointer(new_fp)?;
618 let move_method = this.read_scalar(move_method)?.to_u32()?;
619
620 let fd = match file {
621 Handle::File(fd) => fd,
622 _ => this.invalid_handle("SetFilePointerEx")?,
623 };
624
625 let Some(desc) = this.machine.fds.get(fd) else {
626 throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
627 };
628
629 let file_begin = this.eval_windows_u32("c", "FILE_BEGIN");
630 let file_current = this.eval_windows_u32("c", "FILE_CURRENT");
631 let file_end = this.eval_windows_u32("c", "FILE_END");
632
633 let seek = if move_method == file_begin {
634 SeekFrom::Start(dist_to_move.try_into().unwrap())
635 } else if move_method == file_current {
636 SeekFrom::Current(dist_to_move)
637 } else if move_method == file_end {
638 SeekFrom::End(dist_to_move)
639 } else {
640 throw_unsup_format!("Invalid move method: {move_method}")
641 };
642
643 match desc.seek(this.machine.communicate(), seek)? {
644 Ok(n) => {
645 if !this.ptr_is_null(new_fp_ptr)? {
646 this.write_scalar(
647 Scalar::from_i64(n.try_into().unwrap()),
648 &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?,
649 )?;
650 }
651 interp_ok(this.eval_windows("c", "TRUE"))
652 }
653 Err(e) => {
654 this.set_last_error(e)?;
655 interp_ok(this.eval_windows("c", "FALSE"))
656 }
657 }
658 }
659}
660
661fn extract_windows_epoch<'tcx>(
663 ecx: &MiriInterpCx<'tcx>,
664 time: io::Result<SystemTime>,
665) -> InterpResult<'tcx, Option<(u32, u32)>> {
666 match time.ok() {
667 Some(time) => {
668 let duration = ecx.system_time_since_windows_epoch(&time)?;
669 let duration_ticks = ecx.windows_ticks_for(duration)?;
670 #[expect(clippy::as_conversions)]
671 interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
672 }
673 None => interp_ok(None),
674 }
675}
676
677fn write_filetime_field<'tcx>(
678 cx: &mut MiriInterpCx<'tcx>,
679 val: &MPlaceTy<'tcx>,
680 name: &str,
681 (low, high): (u32, u32),
682) -> InterpResult<'tcx> {
683 cx.write_int_fields_named(
684 &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
685 &cx.project_field_named(val, name)?,
686 )
687}