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 Handle::File(fd_num) = file else { this.invalid_handle("GetFileInformationByHandle")? };
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(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 fn NtWriteFile(
395 &mut self,
396 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, ()> {
407 let this = self.eval_context_mut();
408 let handle = this.read_handle(handle, "NtWriteFile")?;
409 let event = this.read_handle(event, "NtWriteFile")?;
410 let apc_routine = this.read_pointer(apc_routine)?;
411 let apc_ctx = this.read_pointer(apc_ctx)?;
412 let buf = this.read_pointer(buf)?;
413 let count = this.read_scalar(n)?.to_u32()?;
414 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
416 let io_status_block =
417 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
418
419 if event != Handle::Null {
420 throw_unsup_format!(
421 "`NtWriteFile` `Event` parameter is non-null, which is unsupported"
422 );
423 }
424
425 if !this.ptr_is_null(apc_routine)? {
426 throw_unsup_format!(
427 "`NtWriteFile` `ApcRoutine` parameter is non-null, which is unsupported"
428 );
429 }
430
431 if !this.ptr_is_null(apc_ctx)? {
432 throw_unsup_format!(
433 "`NtWriteFile` `ApcContext` parameter is non-null, which is unsupported"
434 );
435 }
436
437 if byte_offset != 0 {
438 throw_unsup_format!(
439 "`NtWriteFile` `ByteOffset` parameter is non-null, which is unsupported"
440 );
441 }
442
443 if !this.ptr_is_null(key)? {
444 throw_unsup_format!("`NtWriteFile` `Key` parameter is non-null, which is unsupported");
445 }
446
447 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
448
449 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtWriteFile")? };
450
451 let io_status = {
456 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
457 this.project_field_named(&anon, "Status")?
458 };
459 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
460
461 let finish = {
465 let io_status = io_status.clone();
466 let io_status_info = io_status_info.clone();
467 let dest = dest.clone();
468 callback!(
469 @capture<'tcx> {
470 count: u32,
471 io_status: MPlaceTy<'tcx>,
472 io_status_info: MPlaceTy<'tcx>,
473 dest: MPlaceTy<'tcx>,
474 }
475 |this, result: Result<usize, IoError>| {
476 match result {
477 Ok(read_size) => {
478 assert!(read_size <= count.try_into().unwrap());
479 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
481 this.write_int(0, &io_status)?;
482 this.write_int(0, &dest)
483 }
484 Err(e) => {
485 this.write_int(0, &io_status_info)?;
486 let status = e.into_ntstatus();
487 this.write_int(status, &io_status)?;
488 this.write_int(status, &dest)
489 }
490 }}
491 )
492 };
493 desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
494
495 interp_ok(())
497 }
498
499 fn NtReadFile(
500 &mut self,
501 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, ()> {
512 let this = self.eval_context_mut();
513 let handle = this.read_handle(handle, "NtReadFile")?;
514 let event = this.read_handle(event, "NtReadFile")?;
515 let apc_routine = this.read_pointer(apc_routine)?;
516 let apc_ctx = this.read_pointer(apc_ctx)?;
517 let buf = this.read_pointer(buf)?;
518 let count = this.read_scalar(n)?.to_u32()?;
519 let byte_offset = this.read_target_usize(byte_offset)?; let key = this.read_pointer(key)?;
521 let io_status_block =
522 this.deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?;
523
524 if event != Handle::Null {
525 throw_unsup_format!("`NtReadFile` `Event` parameter is non-null, which is unsupported");
526 }
527
528 if !this.ptr_is_null(apc_routine)? {
529 throw_unsup_format!(
530 "`NtReadFile` `ApcRoutine` parameter is non-null, which is unsupported"
531 );
532 }
533
534 if !this.ptr_is_null(apc_ctx)? {
535 throw_unsup_format!(
536 "`NtReadFile` `ApcContext` parameter is non-null, which is unsupported"
537 );
538 }
539
540 if byte_offset != 0 {
541 throw_unsup_format!(
542 "`NtReadFile` `ByteOffset` parameter is non-null, which is unsupported"
543 );
544 }
545
546 if !this.ptr_is_null(key)? {
547 throw_unsup_format!("`NtReadFile` `Key` parameter is non-null, which is unsupported");
548 }
549
550 let io_status = {
552 let anon = this.project_field_named(&io_status_block, "Anonymous")?;
553 this.project_field_named(&anon, "Status")?
554 };
555 let io_status_info = this.project_field_named(&io_status_block, "Information")?;
556
557 let Handle::File(fd) = handle else { this.invalid_handle("NtWriteFile")? };
558
559 let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
560
561 let finish = {
565 let io_status = io_status.clone();
566 let io_status_info = io_status_info.clone();
567 let dest = dest.clone();
568 callback!(
569 @capture<'tcx> {
570 count: u32,
571 io_status: MPlaceTy<'tcx>,
572 io_status_info: MPlaceTy<'tcx>,
573 dest: MPlaceTy<'tcx>,
574 }
575 |this, result: Result<usize, IoError>| {
576 match result {
577 Ok(read_size) => {
578 assert!(read_size <= count.try_into().unwrap());
579 this.write_int(u64::try_from(read_size).unwrap(), &io_status_info)?;
581 this.write_int(0, &io_status)?;
582 this.write_int(0, &dest)
583 }
584 Err(e) => {
585 this.write_int(0, &io_status_info)?;
586 let status = e.into_ntstatus();
587 this.write_int(status, &io_status)?;
588 this.write_int(status, &dest)
589 }
590 }}
591 )
592 };
593 desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
594
595 interp_ok(())
597 }
598
599 fn SetFilePointerEx(
600 &mut self,
601 file: &OpTy<'tcx>, dist_to_move: &OpTy<'tcx>, new_fp: &OpTy<'tcx>, move_method: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
606 let this = self.eval_context_mut();
608 let file = this.read_handle(file, "SetFilePointerEx")?;
609 let dist_to_move = this.read_scalar(dist_to_move)?.to_i64()?;
610 let new_fp_ptr = this.read_pointer(new_fp)?;
611 let move_method = this.read_scalar(move_method)?.to_u32()?;
612
613 let Handle::File(fd) = file else { this.invalid_handle("SetFilePointerEx")? };
614
615 let Some(desc) = this.machine.fds.get(fd) else {
616 throw_unsup_format!("`SetFilePointerEx` is only supported on file backed handles");
617 };
618
619 let file_begin = this.eval_windows_u32("c", "FILE_BEGIN");
620 let file_current = this.eval_windows_u32("c", "FILE_CURRENT");
621 let file_end = this.eval_windows_u32("c", "FILE_END");
622
623 let seek = if move_method == file_begin {
624 SeekFrom::Start(dist_to_move.try_into().unwrap())
625 } else if move_method == file_current {
626 SeekFrom::Current(dist_to_move)
627 } else if move_method == file_end {
628 SeekFrom::End(dist_to_move)
629 } else {
630 throw_unsup_format!("Invalid move method: {move_method}")
631 };
632
633 match desc.seek(this.machine.communicate(), seek)? {
634 Ok(n) => {
635 if !this.ptr_is_null(new_fp_ptr)? {
636 this.write_scalar(
637 Scalar::from_i64(n.try_into().unwrap()),
638 &this.deref_pointer_as(new_fp, this.machine.layouts.i64)?,
639 )?;
640 }
641 interp_ok(this.eval_windows("c", "TRUE"))
642 }
643 Err(e) => {
644 this.set_last_error(e)?;
645 interp_ok(this.eval_windows("c", "FALSE"))
646 }
647 }
648 }
649}
650
651fn extract_windows_epoch<'tcx>(
653 ecx: &MiriInterpCx<'tcx>,
654 time: io::Result<SystemTime>,
655) -> InterpResult<'tcx, Option<(u32, u32)>> {
656 match time.ok() {
657 Some(time) => {
658 let duration = ecx.system_time_since_windows_epoch(&time)?;
659 let duration_ticks = ecx.windows_ticks_for(duration)?;
660 #[expect(clippy::as_conversions)]
661 interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
662 }
663 None => interp_ok(None),
664 }
665}
666
667fn write_filetime_field<'tcx>(
668 cx: &mut MiriInterpCx<'tcx>,
669 val: &MPlaceTy<'tcx>,
670 name: &str,
671 (low, high): (u32, u32),
672) -> InterpResult<'tcx> {
673 cx.write_int_fields_named(
674 &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
675 &cx.project_field_named(val, name)?,
676 )
677}