Skip to content

Commit 1fca5a9

Browse files
committed
Auto merge of #2725 - RalfJung:host-to-target-path, r=RalfJung
make unix path handling on Windows hosts (and vice versa) preserve absoluteness Also adds a magic Miri extern function so this conversion can be invoked by the program. That avoids having to duplicate that logic.
2 parents 8da3bce + 03f1ce4 commit 1fca5a9

File tree

11 files changed

+228
-71
lines changed

11 files changed

+228
-71
lines changed

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,21 @@ extern "Rust" {
576576

577577
/// Miri-provided extern function to deallocate memory.
578578
fn miri_dealloc(ptr: *mut u8, size: usize, align: usize);
579+
580+
/// Convert a path from the host Miri runs on to the target Miri interprets.
581+
/// Performs conversion of path separators as needed.
582+
///
583+
/// Usually Miri performs this kind of conversion automatically. However, manual conversion
584+
/// might be necessary when reading an environment variable that was set on the host
585+
/// (such as TMPDIR) and using it as a target path.
586+
///
587+
/// Only works with isolation disabled.
588+
///
589+
/// `in` must point to a null-terminated string, and will be read as the input host path.
590+
/// `out` must point to at least `out_size` many bytes, and the result will be stored there
591+
/// with a null terminator.
592+
/// Returns 0 if the `out` buffer was large enough, and the required size otherwise.
593+
fn miri_host_to_target_path(path: *const i8, out: *mut i8, out_size: usize) -> usize;
579594
}
580595
```
581596

src/shims/foreign_items.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{collections::hash_map::Entry, io::Write, iter};
1+
use std::{collections::hash_map::Entry, io::Write, iter, path::Path};
22

33
use log::trace;
44

@@ -442,6 +442,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
442442
}
443443
this.machine.static_roots.push(alloc_id);
444444
}
445+
"miri_host_to_target_path" => {
446+
let [ptr, out, out_size] = this.check_shim(abi, Abi::Rust, link_name, args)?;
447+
let ptr = this.read_pointer(ptr)?;
448+
let out = this.read_pointer(out)?;
449+
let out_size = this.read_scalar(out_size)?.to_machine_usize(this)?;
450+
451+
// The host affects program behavior here, so this requires isolation to be disabled.
452+
this.check_no_isolation("`miri_host_to_target_path`")?;
453+
454+
// We read this as a plain OsStr and write it as a path, which will convert it to the target.
455+
let path = this.read_os_str_from_c_str(ptr)?.to_owned();
456+
let (success, needed_size) = this.write_path_to_c_str(Path::new(&path), out, out_size)?;
457+
// Return value: 0 on success, otherwise the size it would have needed.
458+
this.write_int(if success { 0 } else { needed_size }, dest)?;
459+
}
445460

446461
// Obtains the size of a Miri backtrace. See the README for details.
447462
"miri_backtrace_size" => {

src/shims/os_str.rs

+63-17
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
174174
let this = self.eval_context_ref();
175175
let os_str = this.read_os_str_from_c_str(ptr)?;
176176

177-
Ok(match this.convert_path_separator(Cow::Borrowed(os_str), PathConversion::TargetToHost) {
177+
Ok(match this.convert_path(Cow::Borrowed(os_str), PathConversion::TargetToHost) {
178178
Cow::Borrowed(x) => Cow::Borrowed(Path::new(x)),
179179
Cow::Owned(y) => Cow::Owned(PathBuf::from(y)),
180180
})
@@ -188,10 +188,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
188188
let this = self.eval_context_ref();
189189
let os_str = this.read_os_str_from_wide_str(ptr)?;
190190

191-
Ok(this
192-
.convert_path_separator(Cow::Owned(os_str), PathConversion::TargetToHost)
193-
.into_owned()
194-
.into())
191+
Ok(this.convert_path(Cow::Owned(os_str), PathConversion::TargetToHost).into_owned().into())
195192
}
196193

197194
/// Write a Path to the machine memory (as a null-terminated sequence of bytes),
@@ -203,8 +200,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
203200
size: u64,
204201
) -> InterpResult<'tcx, (bool, u64)> {
205202
let this = self.eval_context_mut();
206-
let os_str = this
207-
.convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
203+
let os_str =
204+
this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
208205
this.write_os_str_to_c_str(&os_str, ptr, size)
209206
}
210207

@@ -217,8 +214,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
217214
size: u64,
218215
) -> InterpResult<'tcx, (bool, u64)> {
219216
let this = self.eval_context_mut();
220-
let os_str = this
221-
.convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
217+
let os_str =
218+
this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
222219
this.write_os_str_to_wide_str(&os_str, ptr, size)
223220
}
224221

@@ -230,18 +227,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
230227
memkind: MemoryKind<MiriMemoryKind>,
231228
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
232229
let this = self.eval_context_mut();
233-
let os_str = this
234-
.convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
230+
let os_str =
231+
this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
235232
this.alloc_os_str_as_c_str(&os_str, memkind)
236233
}
237234

238-
fn convert_path_separator<'a>(
235+
#[allow(clippy::get_first)]
236+
fn convert_path<'a>(
239237
&self,
240238
os_str: Cow<'a, OsStr>,
241239
direction: PathConversion,
242240
) -> Cow<'a, OsStr> {
243241
let this = self.eval_context_ref();
244242
let target_os = &this.tcx.sess.target.os;
243+
245244
#[cfg(windows)]
246245
return if target_os == "windows" {
247246
// Windows-on-Windows, all fine.
@@ -252,24 +251,71 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
252251
PathConversion::HostToTarget => ('\\', '/'),
253252
PathConversion::TargetToHost => ('/', '\\'),
254253
};
255-
let converted = os_str
254+
let mut converted = os_str
256255
.encode_wide()
257256
.map(|wchar| if wchar == from as u16 { to as u16 } else { wchar })
258257
.collect::<Vec<_>>();
258+
// We also have to ensure that absolute paths remain absolute.
259+
match direction {
260+
PathConversion::HostToTarget => {
261+
// If this is an absolute Windows path that starts with a drive letter (`C:/...`
262+
// after separator conversion), it would not be considered absolute by Unix
263+
// target code.
264+
if converted.get(1).copied() == Some(b':' as u16)
265+
&& converted.get(2).copied() == Some(b'/' as u16)
266+
{
267+
// We add a `/` at the beginning, to store the absolute Windows
268+
// path in something that looks like an absolute Unix path.
269+
converted.insert(0, b'/' as u16);
270+
}
271+
}
272+
PathConversion::TargetToHost => {
273+
// If the path is `\C:\`, the leading backslash was probably added by the above code
274+
// and we should get rid of it again.
275+
if converted.get(0).copied() == Some(b'\\' as u16)
276+
&& converted.get(2).copied() == Some(b':' as u16)
277+
&& converted.get(3).copied() == Some(b'\\' as u16)
278+
{
279+
converted.remove(0);
280+
}
281+
}
282+
}
259283
Cow::Owned(OsString::from_wide(&converted))
260284
};
261285
#[cfg(unix)]
262286
return if target_os == "windows" {
263287
// Windows target, Unix host.
264288
let (from, to) = match direction {
265-
PathConversion::HostToTarget => ('/', '\\'),
266-
PathConversion::TargetToHost => ('\\', '/'),
289+
PathConversion::HostToTarget => (b'/', b'\\'),
290+
PathConversion::TargetToHost => (b'\\', b'/'),
267291
};
268-
let converted = os_str
292+
let mut converted = os_str
269293
.as_bytes()
270294
.iter()
271-
.map(|&wchar| if wchar == from as u8 { to as u8 } else { wchar })
295+
.map(|&wchar| if wchar == from { to } else { wchar })
272296
.collect::<Vec<_>>();
297+
// We also have to ensure that absolute paths remain absolute.
298+
match direction {
299+
PathConversion::HostToTarget => {
300+
// If this start withs a `\`, we add `\\?` so it starts with `\\?\` which is
301+
// some magic path on Windos that *is* considered absolute.
302+
if converted.get(0).copied() == Some(b'\\') {
303+
converted.splice(0..0, b"\\\\?".iter().copied());
304+
}
305+
}
306+
PathConversion::TargetToHost => {
307+
// If this starts with `//?/`, it was probably produced by the above code and we
308+
// remove the `//?` that got added to get the Unix path back out.
309+
if converted.get(0).copied() == Some(b'/')
310+
&& converted.get(1).copied() == Some(b'/')
311+
&& converted.get(2).copied() == Some(b'?')
312+
&& converted.get(3).copied() == Some(b'/')
313+
{
314+
// Remove first 3 characters
315+
converted.splice(0..3, std::iter::empty());
316+
}
317+
}
318+
}
273319
Cow::Owned(OsString::from_vec(converted))
274320
} else {
275321
// Unix-on-Unix, all is fine.

src/shims/unix/fs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1667,7 +1667,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
16671667
// 'readlink' truncates the resolved path if the provided buffer is not large
16681668
// enough, and does *not* add a null terminator. That means we cannot use the usual
16691669
// `write_path_to_c_str` and have to re-implement parts of it ourselves.
1670-
let resolved = this.convert_path_separator(
1670+
let resolved = this.convert_path(
16711671
Cow::Borrowed(resolved.as_ref()),
16721672
crate::shims::os_str::PathConversion::HostToTarget,
16731673
);

test-cargo-miri/src/main.rs

+23-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use byteorder::{BigEndian, ByteOrder};
22
use std::env;
33
#[cfg(unix)]
44
use std::io::{self, BufRead};
5+
use std::path::PathBuf;
56

67
fn main() {
78
// Check env var set by `build.rs`.
@@ -21,12 +22,30 @@ fn main() {
2122
// If there were no arguments, access stdin and test working dir.
2223
// (We rely on the test runner to always disable isolation when passing no arguments.)
2324
if std::env::args().len() <= 1 {
25+
fn host_to_target_path(path: String) -> PathBuf {
26+
use std::ffi::{CStr, CString};
27+
28+
let path = CString::new(path).unwrap();
29+
let mut out = Vec::with_capacity(1024);
30+
31+
unsafe {
32+
extern "Rust" {
33+
fn miri_host_to_target_path(
34+
path: *const i8,
35+
out: *mut i8,
36+
out_size: usize,
37+
) -> usize;
38+
}
39+
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
40+
assert_eq!(ret, 0);
41+
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
42+
PathBuf::from(out)
43+
}
44+
}
45+
2446
// CWD should be crate root.
25-
// We have to normalize slashes, as the env var might be set for a different target's conventions.
2647
let env_dir = env::current_dir().unwrap();
27-
let env_dir = env_dir.to_string_lossy().replace("\\", "/");
28-
let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
29-
let crate_dir = crate_dir.to_string_lossy().replace("\\", "/");
48+
let crate_dir = host_to_target_path(env::var("CARGO_MANIFEST_DIR").unwrap());
3049
assert_eq!(env_dir, crate_dir);
3150

3251
#[cfg(unix)]

test-cargo-miri/subcrate/main.rs

+23-6
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,30 @@ use std::path::PathBuf;
44
fn main() {
55
println!("subcrate running");
66

7+
fn host_to_target_path(path: String) -> PathBuf {
8+
use std::ffi::{CStr, CString};
9+
10+
let path = CString::new(path).unwrap();
11+
let mut out = Vec::with_capacity(1024);
12+
13+
unsafe {
14+
extern "Rust" {
15+
fn miri_host_to_target_path(
16+
path: *const i8,
17+
out: *mut i8,
18+
out_size: usize,
19+
) -> usize;
20+
}
21+
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
22+
assert_eq!(ret, 0);
23+
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
24+
PathBuf::from(out)
25+
}
26+
}
27+
728
// CWD should be workspace root, i.e., one level up from crate root.
8-
// We have to normalize slashes, as the env var might be set for a different target's conventions.
929
let env_dir = env::current_dir().unwrap();
10-
let env_dir = env_dir.to_string_lossy().replace("\\", "/");
11-
let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
12-
let crate_dir = crate_dir.to_string_lossy().replace("\\", "/");
13-
let crate_dir = PathBuf::from(crate_dir);
14-
let crate_dir = crate_dir.parent().unwrap().to_string_lossy();
30+
let crate_dir = host_to_target_path(env::var("CARGO_MANIFEST_DIR").unwrap());
31+
let crate_dir = crate_dir.parent().unwrap();
1532
assert_eq!(env_dir, crate_dir);
1633
}

test-cargo-miri/subcrate/test.rs

+24-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
11
use std::env;
22

3+
use std::path::PathBuf;
4+
35
use byteorder::{ByteOrder, LittleEndian};
46

57
fn main() {
68
println!("subcrate testing");
79

10+
fn host_to_target_path(path: String) -> PathBuf {
11+
use std::ffi::{CStr, CString};
12+
13+
let path = CString::new(path).unwrap();
14+
let mut out = Vec::with_capacity(1024);
15+
16+
unsafe {
17+
extern "Rust" {
18+
fn miri_host_to_target_path(
19+
path: *const i8,
20+
out: *mut i8,
21+
out_size: usize,
22+
) -> usize;
23+
}
24+
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
25+
assert_eq!(ret, 0);
26+
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
27+
PathBuf::from(out)
28+
}
29+
}
30+
831
// CWD should be crate root.
932
// We have to normalize slashes, as the env var might be set for a different target's conventions.
1033
let env_dir = env::current_dir().unwrap();
11-
let env_dir = env_dir.to_string_lossy().replace("\\", "/");
12-
let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
13-
let crate_dir = crate_dir.to_string_lossy().replace("\\", "/");
34+
let crate_dir = host_to_target_path(env::var("CARGO_MANIFEST_DIR").unwrap());
1435
assert_eq!(env_dir, crate_dir);
1536

1637
// Make sure we can call dev-dependencies.

tests/pass-dep/shims/libc-fs.rs

+16-15
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#![feature(io_error_uncategorized)]
66

77
use std::convert::TryInto;
8-
use std::ffi::CString;
8+
use std::ffi::{CStr, CString};
99
use std::fs::{canonicalize, remove_dir_all, remove_file, File};
1010
use std::io::{Error, ErrorKind, Write};
1111
use std::os::unix::ffi::OsStrExt;
@@ -23,20 +23,21 @@ fn main() {
2323
}
2424

2525
fn tmp() -> PathBuf {
26-
std::env::var("MIRI_TEMP")
27-
.map(|tmp| {
28-
// MIRI_TEMP is set outside of our emulated
29-
// program, so it may have path separators that don't
30-
// correspond to our target platform. We normalize them here
31-
// before constructing a `PathBuf`
32-
33-
#[cfg(windows)]
34-
return PathBuf::from(tmp.replace("/", "\\"));
35-
36-
#[cfg(not(windows))]
37-
return PathBuf::from(tmp.replace("\\", "/"));
38-
})
39-
.unwrap_or_else(|_| std::env::temp_dir())
26+
let path = std::env::var("MIRI_TEMP")
27+
.unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap());
28+
// These are host paths. We need to convert them to the target.
29+
let path = CString::new(path).unwrap();
30+
let mut out = Vec::with_capacity(1024);
31+
32+
unsafe {
33+
extern "Rust" {
34+
fn miri_host_to_target_path(path: *const i8, out: *mut i8, out_size: usize) -> usize;
35+
}
36+
let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity());
37+
assert_eq!(ret, 0);
38+
let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap();
39+
PathBuf::from(out)
40+
}
4041
}
4142

4243
/// Prepare: compute filename and make sure the file does not exist.

0 commit comments

Comments
 (0)