Windows: add basic support for FormatMessageW

This commit is contained in:
Ralf Jung 2024-04-15 09:35:06 +02:00
parent c3136b2031
commit fb779ee0ac
5 changed files with 79 additions and 19 deletions

View File

@ -78,6 +78,17 @@ const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
("EAGAIN", WouldBlock),
]
};
// This mapping should match `decode_error_kind` in
// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/windows/mod.rs>.
const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
use std::io::ErrorKind::*;
// FIXME: this is still incomplete.
&[
("ERROR_ACCESS_DENIED", PermissionDenied),
("ERROR_FILE_NOT_FOUND", NotFound),
("ERROR_INVALID_PARAMETER", InvalidInput),
]
};
/// Gets an instance for a path.
///
@ -712,20 +723,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
throw_unsup_format!("io error {:?} cannot be translated into a raw os error", err_kind)
} else if target.families.iter().any(|f| f == "windows") {
// FIXME: we have to finish implementing the Windows equivalent of this.
use std::io::ErrorKind::*;
Ok(this.eval_windows(
"c",
match err_kind {
NotFound => "ERROR_FILE_NOT_FOUND",
PermissionDenied => "ERROR_ACCESS_DENIED",
_ =>
throw_unsup_format!(
"io error {:?} cannot be translated into a raw os error",
err_kind
),
},
))
for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
if err_kind == kind {
return Ok(this.eval_windows("c", name));
}
}
throw_unsup_format!("io error {:?} cannot be translated into a raw os error", err_kind);
} else {
throw_unsup_format!(
"converting io::Error into errnum is unsupported for OS {}",
@ -749,8 +752,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
return Ok(Some(kind));
}
}
// Our table is as complete as the mapping in std, so we are okay with saying "that's a
// strange one" here.
return Ok(None);
} else if target.families.iter().any(|f| f == "windows") {
let errnum = errnum.to_u32()?;
for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
if errnum == this.eval_windows("c", name).to_u32()? {
return Ok(Some(kind));
}
}
return Ok(None);
} else {
throw_unsup_format!(

View File

@ -98,6 +98,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
///
/// If `truncate == true`, then in case `size` is not large enough it *will* write the first
/// `size.saturating_sub(1)` many items, followed by a null terminator (if `size > 0`).
/// The return value is still `(false, length)` in that case.
fn write_os_str_to_wide_str(
&mut self,
os_str: &OsStr,

View File

@ -1,3 +1,4 @@
use std::ffi::OsStr;
use std::iter;
use std::str;
@ -533,6 +534,43 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.set_last_error(insufficient_buffer)?;
}
}
"FormatMessageW" => {
let [flags, module, message_id, language_id, buffer, size, arguments] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let flags = this.read_scalar(flags)?.to_u32()?;
let _module = this.read_pointer(module)?; // seems to contain a module name
let message_id = this.read_scalar(message_id)?;
let _language_id = this.read_scalar(language_id)?.to_u32()?;
let buffer = this.read_pointer(buffer)?;
let size = this.read_scalar(size)?.to_u32()?;
let _arguments = this.read_pointer(arguments)?;
// We only support `FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS`
// This also means `arguments` can be ignored.
if flags != 4096u32 | 512u32 {
throw_unsup_format!("FormatMessageW: unsupported flags {flags:#x}");
}
let error = this.try_errnum_to_io_error(message_id)?;
let formatted = match error {
Some(err) => format!("{err}"),
None => format!("<unknown error in FormatMessageW: {message_id}>"),
};
let (complete, length) = this.write_os_str_to_wide_str(
OsStr::new(&formatted),
buffer,
size.into(),
/*trunacte*/ false,
)?;
if !complete {
// The API docs don't say what happens when the buffer is not big enough...
// Let's just bail.
throw_unsup_format!("FormatMessageW: buffer not big enough");
}
// The return value is the number of characters stored *excluding* the null terminator.
this.write_int(length.checked_sub(1).unwrap(), dest)?;
}
// Incomplete shims that we "stub out" just to get pre-main initialization code to work.
// These shims are enabled only when the caller is in the standard library.

View File

@ -260,7 +260,7 @@ fn test_errors() {
// Opening a non-existing file should fail with a "not found" error.
assert_eq!(ErrorKind::NotFound, File::open(&path).unwrap_err().kind());
// Make sure we can also format this.
format!("{0:?}: {0}", File::open(&path).unwrap_err());
format!("{0}: {0:?}", File::open(&path).unwrap_err());
// Removing a non-existing file should fail with a "not found" error.
assert_eq!(ErrorKind::NotFound, remove_file(&path).unwrap_err().kind());
// Reading the metadata of a non-existing file should fail with a "not found" error.

View File

@ -1,7 +1,19 @@
use std::io::IsTerminal;
use std::io::{self, IsTerminal};
fn main() {
// We can't really assume that this is truly a terminal, and anyway on Windows Miri will always
// return `false` here, but we can check that the call succeeds.
std::io::stdout().is_terminal();
io::stdout().is_terminal();
// Ensure we can format `io::Error` created from OS errors
// (calls OS-specific error formatting functions).
let raw_os_error = if cfg!(unix) {
22 // EINVAL (on most Unixes, anyway)
} else if cfg!(windows) {
87 // ERROR_INVALID_PARAMETER
} else {
panic!("unsupported OS")
};
let err = io::Error::from_raw_os_error(raw_os_error);
format!("{err}: {err:?}");
}