-
Notifications
You must be signed in to change notification settings - Fork 352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support getting file metadata on Windows #4067
base: master
Are you sure you want to change the base?
Changes from 3 commits
9b61c05
ab0e138
e19deaa
f1bb77d
9a76a85
cc6eae5
3653169
a64dbd1
274d90f
51273f5
aa6bbf6
938430f
3c2ed8a
ebfc768
d989984
e5ada76
52c1676
5ac99da
ef5ab7f
6ee99aa
a61daf2
92f41ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ use std::path::PathBuf; | |
use std::time::SystemTime; | ||
|
||
use crate::shims::files::FileDescription; | ||
use crate::shims::time::system_time_to_duration; | ||
use crate::shims::windows::handle::{EvalContextExt as _, Handle}; | ||
use crate::*; | ||
|
||
|
@@ -177,7 +176,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | |
throw_unsup_format!("CreateFileW: Template files are not supported"); | ||
} | ||
|
||
let exists = file_name.exists(); | ||
let is_dir = file_name.is_dir(); | ||
|
||
if flags_and_attributes == file_attribute_normal && is_dir { | ||
|
@@ -204,20 +202,46 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | |
); | ||
} | ||
|
||
// This is racy, but there doesn't appear to be an std API that both succeeds if a file | ||
// exists but tells us it isn't new. Either we accept racing one way or another, or we | ||
// use an iffy heuristic like file creation time. This implementation prefers to fail in the | ||
// direction of erroring more often. | ||
let exists = file_name.exists(); | ||
|
||
if creation_disposition == create_always { | ||
if file_name.exists() { | ||
// Per the documentation: | ||
// If the specified file exists and is writable, the function truncates the file, the | ||
// function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS. | ||
// If the specified file does not exist and is a valid path, a new file is created, the | ||
// function succeeds, and the last-error code is set to zero. | ||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew | ||
if exists { | ||
this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?; | ||
} else { | ||
this.set_last_error(IoError::Raw(Scalar::from_u32(0)))?; | ||
} | ||
options.create(true); | ||
options.truncate(true); | ||
} else if creation_disposition == create_new { | ||
options.create_new(true); | ||
// Per `create_new` documentation: | ||
// If the specified file does not exist and is a valid path to a writable location, the | ||
// function creates a file and the last-error code is set to zero. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The setting to zero does not seem to happen here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the wrong copy-paste - I meant to put the std docs for create_new here. Microsoft doesn't say anything about zero error here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you have access to a Windows host, maybe you can try this:
|
||
// https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new | ||
if !desired_write { | ||
options.append(true); | ||
} | ||
} else if creation_disposition == open_always { | ||
if file_name.exists() { | ||
// Per the documentation: | ||
// If the specified file exists, the function succeeds and the last-error code is set | ||
// to ERROR_ALREADY_EXISTS. | ||
// If the specified file does not exist and is a valid path to a writable location, the | ||
// function creates a file and the last-error code is set to zero. | ||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew | ||
if exists { | ||
this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?; | ||
} else { | ||
this.set_last_error(IoError::Raw(Scalar::from_u32(0)))?; | ||
} | ||
options.create(true); | ||
} else if creation_disposition == open_existing { | ||
|
@@ -230,7 +254,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | |
); | ||
} | ||
|
||
let handle = if is_dir && exists { | ||
let handle = if is_dir { | ||
let fh = &mut this.machine.fds; | ||
let fd_num = fh.insert_new(DirHandle { path: file_name }); | ||
Ok(Handle::File(fd_num)) | ||
|
@@ -304,9 +328,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | |
this.eval_windows_u32("c", "FILE_ATTRIBUTE_DEVICE") | ||
}; | ||
|
||
let created = extract_windows_epoch(metadata.created())?.unwrap_or((0, 0)); | ||
let accessed = extract_windows_epoch(metadata.accessed())?.unwrap_or((0, 0)); | ||
let written = extract_windows_epoch(metadata.modified())?.unwrap_or((0, 0)); | ||
let created = extract_windows_epoch(this, metadata.created())?.unwrap_or((0, 0)); | ||
let accessed = extract_windows_epoch(this, metadata.accessed())?.unwrap_or((0, 0)); | ||
let written = extract_windows_epoch(this, metadata.modified())?.unwrap_or((0, 0)); | ||
|
||
this.write_int_fields_named(&[("dwFileAttributes", attributes.into())], &file_information)?; | ||
write_filetime_field(this, &file_information, "ftCreationTime", created)?; | ||
|
@@ -330,21 +354,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | |
|
||
/// Windows FILETIME is measured in 100-nanosecs since 1601 | ||
fn extract_windows_epoch<'tcx>( | ||
ecx: &MiriInterpCx<'tcx>, | ||
time: io::Result<SystemTime>, | ||
) -> InterpResult<'tcx, Option<(u32, u32)>> { | ||
// (seconds in a year * years between 1970 and 1601) + (89 leap days in that span) | ||
const SECONDS_TO_EPOCH: u64 = 3600 * 24 * 365 * (1970 - 1601) + 3600 * 24 * 89; | ||
// seconds between unix and windows epochs * 10 million (nanoseconds/second / 100) | ||
const TIME_TO_EPOCH: u64 = SECONDS_TO_EPOCH * 10_000_000; | ||
match time.ok() { | ||
Some(time) => { | ||
let duration = system_time_to_duration(&time)?; | ||
// 10 million is the number of 100ns periods per second. | ||
let secs = duration.as_secs().saturating_mul(10_000_000); | ||
let nanos_hundred: u64 = (duration.subsec_nanos() / 100).into(); | ||
let total = secs.saturating_add(nanos_hundred).saturating_add(TIME_TO_EPOCH); | ||
let duration = ecx.system_time_since_windows_epoch(&time)?; | ||
let duration_ticks = ecx.windows_ticks_for(duration)?; | ||
#[allow(clippy::cast_possible_truncation)] | ||
interp_ok(Some((total as u32, (total >> 32) as u32))) | ||
interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32))) | ||
} | ||
None => interp_ok(None), | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is too early, since one doesn't know yet that this is used for. IMO it'd be better placed at the
if exists
below.(Also, no need to
let
-bind, I don't feel like this makes things much clearer.)