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#[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 const BACKUP_SEMANTICS = 1 << 1;
110 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 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>, 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> {
166 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.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 let is_dir = file_name.is_dir();
214
215 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 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 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 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 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 if !desired_write {
283 options.append(true);
284 }
285 }
286 OpenExisting => {} 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>, file_information: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
313 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 fd_num = if let Handle::File(fd_num) = file {
325 fd_num
326 } else {
327 this.invalid_handle("GetFileInformationByHandle")?
328 };
329
330 let Some(desc) = this.machine.fds.get(fd_num) else {
331 this.invalid_handle("GetFileInformationByHandle")?
332 };
333
334 let metadata = match desc.metadata()? {
335 Ok(meta) => meta,
336 Err(e) => {
337 this.set_last_error(e)?;
338 return interp_ok(this.eval_windows("c", "FALSE"));
339 }
340 };
341
342 let size = metadata.len();
343
344 let file_type = metadata.file_type();
345 let attributes = if file_type.is_dir() {
346 this.eval_windows_u32("c", "FILE_ATTRIBUTE_DIRECTORY")
347 } else if file_type.is_file() {
348 this.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL")
349 } else {
350 this.eval_windows_u32("c", "FILE_ATTRIBUTE_DEVICE")
351 };
352
353 let created = extract_windows_epoch(this, metadata.created())?.unwrap_or((0, 0));
357 let accessed = extract_windows_epoch(this, metadata.accessed())?.unwrap_or((0, 0));
358 let written = extract_windows_epoch(this, metadata.modified())?.unwrap_or((0, 0));
359
360 this.write_int_fields_named(&[("dwFileAttributes", attributes.into())], &file_information)?;
361 write_filetime_field(this, &file_information, "ftCreationTime", created)?;
362 write_filetime_field(this, &file_information, "ftLastAccessTime", accessed)?;
363 write_filetime_field(this, &file_information, "ftLastWriteTime", written)?;
364 this.write_int_fields_named(
365 &[
366 ("dwVolumeSerialNumber", 0),
367 ("nFileSizeHigh", (size >> 32).into()),
368 ("nFileSizeLow", (size & 0xFFFFFFFF).into()),
369 ("nNumberOfLinks", 1),
370 ("nFileIndexHigh", 0),
371 ("nFileIndexLow", 0),
372 ],
373 &file_information,
374 )?;
375
376 interp_ok(this.eval_windows("c", "TRUE"))
377 }
378
379 fn DeleteFileW(
380 &mut self,
381 file_name: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
383 let this = self.eval_context_mut();
385 this.assert_target_os(Os::Windows, "DeleteFileW");
386 this.check_no_isolation("`DeleteFileW`")?;
387
388 let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
389 match std::fs::remove_file(file_name) {
390 Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
391 Err(e) => {
392 this.set_last_error(e)?;
393 interp_ok(this.eval_windows("c", "FALSE"))
394 }
395 }
396 }
397
398 fn NtWriteFile(
399 &mut self,
400 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, ()> {
411 let this = self.eval_context_mut();
412 let handle = this.read_handle(handle, "NtWriteFile")?;
413 let event = this.read_handle(event, "NtWriteFile")?;
414 let apc_routine = this.read_pointer(apc_routine)?;
415 let apc_ctx = this.read_pointer(apc_ctx)?;
416 let buf = this.read_pointer(buf)?;
417 let count = this.read_scalar(n)?.to_u32()?;
418 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
420 let io_status_block =
421 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
422
423 if event != Handle::Null {
424 throw_unsup_format!(
425 "`NtWriteFile` `Event` parameter is non-null, which is unsupported"
426 );
427 }
428
429 if !this.ptr_is_null(apc_routine)? {
430 throw_unsup_format!(
431 "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported"
432 );
433 }
434
435 if !this.ptr_is_null(apc_ctx)? {
436 throw_unsup_format!(
437 "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported"
438 );
439 }
440
441 if byte_offset != 0 {
442 throw_unsup_format!(
443 "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
444 );
445 }
446
447 if !this.ptr_is_null(key)? {
448 throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported");
449 }
450
451 let fd = match handle {
452 Handle::File(fd) => fd,
453 _ => this.invalid_handle("NtWriteFile")?,
454 };
455
456 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? };
457
458 let io_status = {
463 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
464 this.project_field_named(&anon, "Status")?
465 };
466 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
467
468 let finish = {
472 let io_status = io_status.clone();
473 let io_status_info = io_status_info.clone();
474 let dest = dest.clone();
475 callback!(
476 @capture<'tcx> {
477 count: u32,
478 io_status: MPlaceTy<'tcx>,
479 io_status_info: MPlaceTy<'tcx>,
480 dest: MPlaceTy<'tcx>,
481 }
482 |this, result: Result<usize, IoError>| {
483 match result {
484 Ok(read_size) => {
485 assert!(read_size <= count.try_into().unwrap());
486 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
488 this.write_int(0, &io_status)?;
489 this.write_int(0, &dest)
490 }
491 Err(e) => {
492 this.write_int(0, &io_status_info)?;
493 let status = e.into_ntstatus();
494 this.write_int(status, &io_status)?;
495 this.write_int(status, &dest)
496 }
497 }}
498 )
499 };
500 desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
501
502 interp_ok(())
504 }
505
506 fn NtReadFile(
507 &mut self,
508 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, ()> {
519 let this = self.eval_context_mut();
520 let handle = this.read_handle(handle, "NtReadFile")?;
521 let event = this.read_handle(event, "NtReadFile")?;
522 let apc_routine = this.read_pointer(apc_routine)?;
523 let apc_ctx = this.read_pointer(apc_ctx)?;
524 let buf = this.read_pointer(buf)?;
525 let count = this.read_scalar(n)?.to_u32()?;
526 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
528 let io_status_block =
529 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
530
531 if event != Handle::Null {
532 throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
533 }
534
535 if !this.ptr_is_null(apc_routine)? {
536 throw_unsup_format!(
537 "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
538 );
539 }
540
541 if !this.ptr_is_null(apc_ctx)? {
542 throw_unsup_format!(
543 "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
544 );
545 }
546
547 if byte_offset != 0 {
548 throw_unsup_format!(
549 "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
550 );
551 }
552
553 if !this.ptr_is_null(key)? {
554 throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
555 }
556
557 let io_status = {
559 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
560 this.project_field_named(&anon, "Status")?
561 };
562 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
563
564 let fd = match handle {
565 Handle::File(fd) => fd,
566 _ => this.invalid_handle("NtWriteFile")?,
567 };
568
569 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
570
571 let finish = {
575 let io_status = io_status.clone();
576 let io_status_info = io_status_info.clone();
577 let dest = dest.clone();
578 callback!(
579 @capture<'tcx> {
580 count: u32,
581 io_status: MPlaceTy<'tcx>,
582 io_status_info: MPlaceTy<'tcx>,
583 dest: MPlaceTy<'tcx>,
584 }
585 |this, result: Result<usize, IoError>| {
586 match result {
587 Ok(read_size) => {
588 assert!(read_size <= count.try_into().unwrap());
589 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
591 this.write_int(0, &io_status)?;
592 this.write_int(0, &dest)
593 }
594 Err(e) => {
595 this.write_int(0, &io_status_info)?;
596 let status = e.into_ntstatus();
597 this.write_int(status, &io_status)?;
598 this.write_int(status, &dest)
599 }
600 }}
601 )
602 };
603 desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
604
605 interp_ok(())
607 }
608
609 fn SetFilePointerEx(
610 &mut self,
611 file: &OpTy<'tcx>, dist_to_move: &OpTy<'tcx>, new_fp: &OpTy<'tcx>, move_method: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
616 let this = self.eval_context_mut();
618 let file = this.read_handle(file, "SetFilePointerEx")?;
619 let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?;
620 let new_fp_ptr = this.read_pointer(new_fp)?;
621 let move_method = this.read_scalar(move_method)?.to_u32()?;
622
623 let fd = match file {
624 Handle::File(fd) => fd,
625 _ => this.invalid_handle("SetFilePointerEx")?,
626 };
627
628 let Some(desc) = this.machine.fds.get(fd) else {
629 throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
630 };
631
632 let file_begin = this.eval_windows_u32("c", "FILE_BEGIN");
633 let file_current = this.eval_windows_u32("c", "FILE_CURRENT");
634 let file_end = this.eval_windows_u32("c", "FILE_END");
635
636 let seek = if move_method == file_begin {
637 SeekFrom::Start(dist_to_move.try_into().unwrap())
638 } else if move_method == file_current {
639 SeekFrom::Current(dist_to_move)
640 } else if move_method == file_end {
641 SeekFrom::End(dist_to_move)
642 } else {
643 throw_unsup_format!("Invalid move method: {move_method}")
644 };
645
646 match desc.seek(this.machine.communicate(), seek)? {
647 Ok(n) => {
648 if !this.ptr_is_null(new_fp_ptr)? {
649 this.write_scalar(
650 Scalar::from_i64(n.try_into().unwrap()),
651 &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?,
652 )?;
653 }
654 interp_ok(this.eval_windows("c", "TRUE"))
655 }
656 Err(e) => {
657 this.set_last_error(e)?;
658 interp_ok(this.eval_windows("c", "FALSE"))
659 }
660 }
661 }
662}
663
664fn extract_windows_epoch<'tcx>(
666 ecx: &MiriInterpCx<'tcx>,
667 time: io::Result<SystemTime>,
668) -> InterpResult<'tcx, Option<(u32, u32)>> {
669 match time.ok() {
670 Some(time) => {
671 let duration = ecx.system_time_since_windows_epoch(&time)?;
672 let duration_ticks = ecx.windows_ticks_for(duration)?;
673 #[expect(clippy::as_conversions)]
674 interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
675 }
676 None => interp_ok(None),
677 }
678}
679
680fn write_filetime_field<'tcx>(
681 cx: &mut MiriInterpCx<'tcx>,
682 val: &MPlaceTy<'tcx>,
683 name: &str,
684 (low, high): (u32, u32),
685) -> InterpResult<'tcx> {
686 cx.write_int_fields_named(
687 &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
688 &cx.project_field_named(val, name)?,
689 )
690}