Skip to content

Commit

Permalink
Auto merge of #2067 - RalfJung:strerror_r, r=RalfJung
Browse files Browse the repository at this point in the history
implement strerror_r

This isn't perfect; we end up using [this match](https://github.com/rust-lang/rust/blob/72a25d05bf1a4b155d74139ef700ff93af6d8e22/library/std/src/io/error.rs#L380) rather than the platform-specific messages, but at least we show something -- this is mostly informational anyway.

Cc #2057
  • Loading branch information
bors committed Apr 17, 2022
2 parents d1f31f9 + db2c4b6 commit 35aeba7
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 34 deletions.
95 changes: 61 additions & 34 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@ use crate::*;

impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}

const UNIX_IO_ERROR_TABLE: &[(std::io::ErrorKind, &str)] = {
use std::io::ErrorKind::*;
&[
(ConnectionRefused, "ECONNREFUSED"),
(ConnectionReset, "ECONNRESET"),
(PermissionDenied, "EPERM"),
(BrokenPipe, "EPIPE"),
(NotConnected, "ENOTCONN"),
(ConnectionAborted, "ECONNABORTED"),
(AddrNotAvailable, "EADDRNOTAVAIL"),
(AddrInUse, "EADDRINUSE"),
(NotFound, "ENOENT"),
(Interrupted, "EINTR"),
(InvalidInput, "EINVAL"),
(TimedOut, "ETIMEDOUT"),
(AlreadyExists, "EEXIST"),
(WouldBlock, "EWOULDBLOCK"),
(DirectoryNotEmpty, "ENOTEMPTY"),
]
};

/// Gets an instance for a path.
fn try_resolve_did<'mir, 'tcx>(tcx: TyCtxt<'tcx>, path: &[&str]) -> Option<DefId> {
tcx.crates(()).iter().find(|&&krate| tcx.crate_name(krate).as_str() == path[0]).and_then(
Expand Down Expand Up @@ -502,39 +523,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
this.read_scalar(&errno_place.into())?.check_init()
}

/// Sets the last OS error using a `std::io::ErrorKind`. This function tries to produce the most
/// similar OS error from the `std::io::ErrorKind` and sets it as the last OS error.
fn set_last_error_from_io_error(&mut self, err_kind: std::io::ErrorKind) -> InterpResult<'tcx> {
use std::io::ErrorKind::*;
let this = self.eval_context_mut();
/// This function tries to produce the most similar OS error from the `std::io::ErrorKind`
/// as a platform-specific errnum.
fn io_error_to_errnum(&self, err_kind: std::io::ErrorKind) -> InterpResult<'tcx, Scalar<Tag>> {
let this = self.eval_context_ref();
let target = &this.tcx.sess.target;
let target_os = &target.os;
let last_error = if target.families.iter().any(|f| f == "unix") {
this.eval_libc(match err_kind {
ConnectionRefused => "ECONNREFUSED",
ConnectionReset => "ECONNRESET",
PermissionDenied => "EPERM",
BrokenPipe => "EPIPE",
NotConnected => "ENOTCONN",
ConnectionAborted => "ECONNABORTED",
AddrNotAvailable => "EADDRNOTAVAIL",
AddrInUse => "EADDRINUSE",
NotFound => "ENOENT",
Interrupted => "EINTR",
InvalidInput => "EINVAL",
TimedOut => "ETIMEDOUT",
AlreadyExists => "EEXIST",
WouldBlock => "EWOULDBLOCK",
DirectoryNotEmpty => "ENOTEMPTY",
_ => {
throw_unsup_format!(
"io error {:?} cannot be translated into a raw os error",
err_kind
)
if target.families.iter().any(|f| f == "unix") {
for &(kind, name) in UNIX_IO_ERROR_TABLE {
if err_kind == kind {
return this.eval_libc(name);
}
})?
}
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::*;
this.eval_windows(
"c",
match err_kind {
Expand All @@ -546,14 +549,38 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
err_kind
),
},
)?
)
} else {
throw_unsup_format!(
"setting the last OS error from an io::Error is unsupported for {}.",
target_os
"converting io::Error into errnum is unsupported for OS {}",
target.os
)
};
this.set_last_error(last_error)
}
}

/// The inverse of `io_error_to_errnum`.
fn errnum_to_io_error(&self, errnum: Scalar<Tag>) -> InterpResult<'tcx, std::io::ErrorKind> {
let this = self.eval_context_ref();
let target = &this.tcx.sess.target;
if target.families.iter().any(|f| f == "unix") {
let errnum = errnum.to_i32()?;
for &(kind, name) in UNIX_IO_ERROR_TABLE {
if errnum == this.eval_libc_i32(name)? {
return Ok(kind);
}
}
throw_unsup_format!("raw errnum {:?} cannot be translated into io::Error", errnum)
} else {
throw_unsup_format!(
"converting errnum into io::Error is unsupported for OS {}",
target.os
)
}
}

/// Sets the last OS error using a `std::io::ErrorKind`.
fn set_last_error_from_io_error(&mut self, err_kind: std::io::ErrorKind) -> InterpResult<'tcx> {
self.set_last_error(self.io_error_to_errnum(err_kind)?)
}

/// Helper function that consumes an `std::io::Result<T>` and returns an
Expand Down
14 changes: 14 additions & 0 deletions src/shims/posix/foreign_items.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::ffi::OsStr;

use log::trace;

use rustc_middle::mir;
Expand Down Expand Up @@ -421,6 +423,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
// We do not support forking, so there is nothing to do here.
this.write_null(dest)?;
}
"strerror_r" | "__xpg_strerror_r" => {
let &[ref errnum, ref buf, ref buflen] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let errnum = this.read_scalar(errnum)?.check_init()?;
let buf = this.read_pointer(buf)?;
let buflen = this.read_scalar(buflen)?.to_machine_usize(this)?;

let error = this.errnum_to_io_error(errnum)?;
let formatted = error.to_string();
let (complete, _) = this.write_os_str_to_c_str(OsStr::new(&formatted), buf, buflen)?;
let ret = if complete { 0 } else { this.eval_libc_i32("ERANGE")? };
this.write_int(ret, 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.
Expand Down
2 changes: 2 additions & 0 deletions tests/run-pass/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ fn test_errors() {
// The following tests also check that the `__errno_location()` shim is working properly.
// 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());
// 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.
Expand Down

0 comments on commit 35aeba7

Please sign in to comment.