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