Skip to content

Commit 3504d52

Browse files
committed
Auto merge of #1268 - JOE1994:dir, r=RalfJung
Windows shims for GetCurrentDirectoryW/SetCurrentDirectoryW Implemented shims for Windows * [GetCurrentDirectoryW](https://github.com/rust-lang/rust/blob/75208942f6144daac669e8e382029fc33bdce841/src/libstd/sys/windows/os.rs#L242) (`getcwd` for Windows) * [SetCurrentDirectoryW](https://github.com/rust-lang/rust/blob/75208942f6144daac669e8e382029fc33bdce841/src/libstd/sys/windows/os.rs#L250) (`chdir` for Windows) Currently passes test : `./miri run tests/run-pass/current_dir.rs -Zmiri-disable-isolation`
2 parents c706f50 + 9bdb4bb commit 3504d52

File tree

5 files changed

+174
-80
lines changed

5 files changed

+174
-80
lines changed

src/helpers.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
7171
.not_undef()
7272
}
7373

74+
/// Helper function to get a `windows` constant as a `Scalar`.
75+
fn eval_windows(&mut self, name: &str) -> InterpResult<'tcx, Scalar<Tag>> {
76+
self.eval_context_mut()
77+
.eval_path_scalar(&["std", "sys", "windows", "c", name])?
78+
.not_undef()
79+
}
80+
7481
/// Helper function to get a `libc` constant as an `i32`.
7582
fn eval_libc_i32(&mut self, name: &str) -> InterpResult<'tcx, i32> {
7683
// TODO: Cache the result.
@@ -407,6 +414,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
407414
use std::io::ErrorKind::*;
408415
let this = self.eval_context_mut();
409416
let target = &this.tcx.sess.target.target;
417+
let target_os = &target.target_os;
410418
let last_error = if target.options.target_family == Some("unix".to_owned()) {
411419
this.eval_libc(match e.kind() {
412420
ConnectionRefused => "ECONNREFUSED",
@@ -427,12 +435,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
427435
throw_unsup_format!("io error {} cannot be transformed into a raw os error", e)
428436
}
429437
})?
438+
} else if target_os == "windows" {
439+
// FIXME: we have to finish implementing the Windows equivalent of this.
440+
this.eval_windows(match e.kind() {
441+
NotFound => "ERROR_FILE_NOT_FOUND",
442+
_ => throw_unsup_format!("io error {} cannot be transformed into a raw os error", e)
443+
})?
430444
} else {
431-
// FIXME: we have to implement the Windows equivalent of this.
432-
throw_unsup_format!(
433-
"setting the last OS error from an io::Error is unsupported for {}.",
434-
target.target_os
435-
)
445+
throw_unsup_format!("setting the last OS error from an io::Error is unsupported for {}.", target_os)
436446
};
437447
this.set_last_error(last_error)
438448
}

src/shims/env.rs

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@ use rustc_data_structures::fx::FxHashMap;
1010
use rustc::ty::layout::Size;
1111
use rustc_mir::interpret::Pointer;
1212

13+
/// Check whether an operation that writes to a target buffer was successful.
14+
/// Accordingly select return value.
15+
/// Local helper function to be used in Windows shims.
16+
fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 {
17+
if success {
18+
// If the function succeeds, the return value is the number of characters stored in the target buffer,
19+
// not including the terminating null character.
20+
u32::try_from(len).unwrap()
21+
} else {
22+
// If the target buffer was not large enough to hold the data, the return value is the buffer size, in characters,
23+
// required to hold the string and its terminating null character.
24+
u32::try_from(len.checked_add(1).unwrap()).unwrap()
25+
}
26+
}
27+
1328
#[derive(Default)]
1429
pub struct EnvVars<'tcx> {
1530
/// Stores pointers to the environment variables. These variables must be stored as
@@ -105,10 +120,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
105120
#[allow(non_snake_case)]
106121
fn GetEnvironmentVariableW(
107122
&mut self,
108-
name_op: OpTy<'tcx, Tag>, // LPCWSTR
109-
buf_op: OpTy<'tcx, Tag>, // LPWSTR
110-
size_op: OpTy<'tcx, Tag>, // DWORD
111-
) -> InterpResult<'tcx, u32> {
123+
name_op: OpTy<'tcx, Tag>, // LPCWSTR
124+
buf_op: OpTy<'tcx, Tag>, // LPWSTR
125+
size_op: OpTy<'tcx, Tag>, // DWORD
126+
) -> InterpResult<'tcx, u32> { // Returns DWORD (u32 in Windows)
112127
let this = self.eval_context_mut();
113128
this.assert_target_os("windows", "GetEnvironmentVariableW");
114129

@@ -125,21 +140,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
125140
let buf_ptr = this.read_scalar(buf_op)?.not_undef()?;
126141
// `buf_size` represents the size in characters.
127142
let buf_size = u64::from(this.read_scalar(size_op)?.to_u32()?);
128-
let (success, len) = this.write_os_str_to_wide_str(&var, buf_ptr, buf_size)?;
129-
130-
if success {
131-
// If the function succeeds, the return value is the number of characters stored in the buffer pointed to by lpBuffer,
132-
// not including the terminating null character.
133-
u32::try_from(len).unwrap()
134-
} else {
135-
// If lpBuffer is not large enough to hold the data, the return value is the buffer size, in characters,
136-
// required to hold the string and its terminating null character and the contents of lpBuffer are undefined.
137-
u32::try_from(len).unwrap().checked_add(1).unwrap()
138-
}
143+
windows_check_buffer_size(this.write_os_str_to_wide_str(&var, buf_ptr, buf_size)?)
139144
}
140145
None => {
141-
let envvar_not_found = this.eval_path_scalar(&["std", "sys", "windows", "c", "ERROR_ENVVAR_NOT_FOUND"])?;
142-
this.set_last_error(envvar_not_found.not_undef()?)?;
146+
let envvar_not_found = this.eval_windows("ERROR_ENVVAR_NOT_FOUND")?;
147+
this.set_last_error(envvar_not_found)?;
143148
0 // return zero upon failure
144149
}
145150
})
@@ -289,6 +294,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
289294
size_op: OpTy<'tcx, Tag>,
290295
) -> InterpResult<'tcx, Scalar<Tag>> {
291296
let this = self.eval_context_mut();
297+
let target_os = &this.tcx.sess.target.target.target_os;
298+
assert!(target_os == "linux" || target_os == "macos", "`getcwd` is only available for the UNIX target family");
292299

293300
this.check_no_isolation("getcwd")?;
294301

@@ -308,8 +315,33 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
308315
Ok(Scalar::null_ptr(&*this.tcx))
309316
}
310317

318+
#[allow(non_snake_case)]
319+
fn GetCurrentDirectoryW(
320+
&mut self,
321+
size_op: OpTy<'tcx, Tag>, // DWORD
322+
buf_op: OpTy<'tcx, Tag>, // LPTSTR
323+
) -> InterpResult<'tcx, u32> {
324+
let this = self.eval_context_mut();
325+
this.assert_target_os("windows", "GetCurrentDirectoryW");
326+
327+
this.check_no_isolation("GetCurrentDirectoryW")?;
328+
329+
let size = u64::from(this.read_scalar(size_op)?.to_u32()?);
330+
let buf = this.read_scalar(buf_op)?.not_undef()?;
331+
332+
// If we cannot get the current directory, we return 0
333+
match env::current_dir() {
334+
Ok(cwd) =>
335+
return Ok(windows_check_buffer_size(this.write_path_to_wide_str(&cwd, buf, size)?)),
336+
Err(e) => this.set_last_error_from_io_error(e)?,
337+
}
338+
Ok(0)
339+
}
340+
311341
fn chdir(&mut self, path_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
312342
let this = self.eval_context_mut();
343+
let target_os = &this.tcx.sess.target.target.target_os;
344+
assert!(target_os == "linux" || target_os == "macos", "`getcwd` is only available for the UNIX target family");
313345

314346
this.check_no_isolation("chdir")?;
315347

@@ -324,6 +356,27 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
324356
}
325357
}
326358

359+
#[allow(non_snake_case)]
360+
fn SetCurrentDirectoryW (
361+
&mut self,
362+
path_op: OpTy<'tcx, Tag> // LPCTSTR
363+
) -> InterpResult<'tcx, i32> { // Returns BOOL (i32 in Windows)
364+
let this = self.eval_context_mut();
365+
this.assert_target_os("windows", "SetCurrentDirectoryW");
366+
367+
this.check_no_isolation("SetCurrentDirectoryW")?;
368+
369+
let path = this.read_path_from_wide_str(this.read_scalar(path_op)?.not_undef()?)?;
370+
371+
match env::set_current_dir(path) {
372+
Ok(()) => Ok(1),
373+
Err(e) => {
374+
this.set_last_error_from_io_error(e)?;
375+
Ok(0)
376+
}
377+
}
378+
}
379+
327380
/// Updates the `environ` static.
328381
/// The first time it gets called, also initializes `extra.environ`.
329382
fn update_environ(&mut self) -> InterpResult<'tcx> {

src/shims/foreign_items/windows.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
4040
this.write_scalar(Scalar::from_i32(result), dest)?;
4141
}
4242

43+
"GetCurrentDirectoryW" => {
44+
let result = this.GetCurrentDirectoryW(args[0], args[1])?;
45+
this.write_scalar(Scalar::from_u32(result), dest)?;
46+
}
47+
48+
"SetCurrentDirectoryW" => {
49+
let result = this.SetCurrentDirectoryW(args[0])?;
50+
this.write_scalar(Scalar::from_i32(result), dest)?;
51+
}
52+
4353
// File related shims
4454
"GetStdHandle" => {
4555
let which = this.read_scalar(args[0])?.to_i32()?;

src/shims/os_str.rs

Lines changed: 75 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,53 @@ use rustc::ty::layout::LayoutOf;
1313

1414
use crate::*;
1515

16+
/// Represent how path separator conversion should be done.
17+
enum Pathconversion {
18+
HostToTarget,
19+
TargetToHost,
20+
}
21+
22+
/// Perform path separator conversion if needed.
23+
fn convert_path_separator<'a>(
24+
os_str: &'a OsStr,
25+
target_os: &str,
26+
direction: Pathconversion,
27+
) -> Cow<'a, OsStr> {
28+
#[cfg(windows)]
29+
return if target_os == "windows" {
30+
// Windows-on-Windows, all fine.
31+
Cow::Borrowed(os_str)
32+
} else {
33+
// Unix target, Windows host.
34+
let (from, to) = match direction {
35+
Pathconversion::HostToTarget => ('\\', '/'),
36+
Pathconversion::TargetToHost => ('/', '\\'),
37+
};
38+
let converted = os_str
39+
.encode_wide()
40+
.map(|wchar| if wchar == from as u16 { to as u16 } else { wchar })
41+
.collect::<Vec<_>>();
42+
Cow::Owned(OsString::from_wide(&converted))
43+
};
44+
#[cfg(unix)]
45+
return if target_os == "windows" {
46+
// Windows target, Unix host.
47+
let (from, to) = match direction {
48+
Pathconversion::HostToTarget => ('/', '\\'),
49+
Pathconversion::TargetToHost => ('\\', '/'),
50+
};
51+
let converted = os_str
52+
.as_bytes()
53+
.iter()
54+
.map(|&wchar| if wchar == from as u8 { to as u8 } else { wchar })
55+
.collect::<Vec<_>>();
56+
Cow::Owned(OsString::from_vec(converted))
57+
} else {
58+
// Unix-on-Unix, all is fine.
59+
Cow::Borrowed(os_str)
60+
};
61+
}
62+
1663
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
1764
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
1865
/// Helper function to read an OsString from a null-terminated sequence of bytes, which is what
@@ -179,70 +226,43 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
179226
let this = self.eval_context_ref();
180227
let os_str = this.read_os_str_from_c_str(scalar)?;
181228

182-
#[cfg(windows)]
183-
return Ok(if this.tcx.sess.target.target.target_os == "windows" {
184-
// Windows-on-Windows, all fine.
185-
Cow::Borrowed(Path::new(os_str))
186-
} else {
187-
// Unix target, Windows host. Need to convert target '/' to host '\'.
188-
let converted = os_str
189-
.encode_wide()
190-
.map(|wchar| if wchar == '/' as u16 { '\\' as u16 } else { wchar })
191-
.collect::<Vec<_>>();
192-
Cow::Owned(PathBuf::from(OsString::from_wide(&converted)))
193-
});
194-
#[cfg(unix)]
195-
return Ok(if this.tcx.sess.target.target.target_os == "windows" {
196-
// Windows target, Unix host. Need to convert target '\' to host '/'.
197-
let converted = os_str
198-
.as_bytes()
199-
.iter()
200-
.map(|&wchar| if wchar == '/' as u8 { '\\' as u8 } else { wchar })
201-
.collect::<Vec<_>>();
202-
Cow::Owned(PathBuf::from(OsString::from_vec(converted)))
203-
} else {
204-
// Unix-on-Unix, all is fine.
205-
Cow::Borrowed(Path::new(os_str))
206-
});
229+
Ok(match convert_path_separator(os_str, &this.tcx.sess.target.target.target_os, Pathconversion::TargetToHost) {
230+
Cow::Borrowed(x) => Cow::Borrowed(Path::new(x)),
231+
Cow::Owned(y) => Cow::Owned(PathBuf::from(y)),
232+
})
233+
}
234+
235+
/// Read a null-terminated sequence of `u16`s, and perform path separator conversion if needed.
236+
fn read_path_from_wide_str(&self, scalar: Scalar<Tag>) -> InterpResult<'tcx, PathBuf> {
237+
let this = self.eval_context_ref();
238+
let os_str = this.read_os_str_from_wide_str(scalar)?;
239+
240+
Ok(PathBuf::from(&convert_path_separator(&os_str, &this.tcx.sess.target.target.target_os, Pathconversion::TargetToHost)))
207241
}
208242

209-
/// Write a Path to the machine memory, adjusting path separators if needed.
243+
/// Write a Path to the machine memory (as a null-terminated sequence of bytes),
244+
/// adjusting path separators if needed.
210245
fn write_path_to_c_str(
211246
&mut self,
212247
path: &Path,
213248
scalar: Scalar<Tag>,
214249
size: u64,
215250
) -> InterpResult<'tcx, (bool, u64)> {
216251
let this = self.eval_context_mut();
217-
218-
#[cfg(windows)]
219-
let os_str = if this.tcx.sess.target.target.target_os == "windows" {
220-
// Windows-on-Windows, all fine.
221-
Cow::Borrowed(path.as_os_str())
222-
} else {
223-
// Unix target, Windows host. Need to convert host '\\' to target '/'.
224-
let converted = path
225-
.as_os_str()
226-
.encode_wide()
227-
.map(|wchar| if wchar == '\\' as u16 { '/' as u16 } else { wchar })
228-
.collect::<Vec<_>>();
229-
Cow::Owned(OsString::from_wide(&converted))
230-
};
231-
#[cfg(unix)]
232-
let os_str = if this.tcx.sess.target.target.target_os == "windows" {
233-
// Windows target, Unix host. Need to convert host '/' to target '\'.
234-
let converted = path
235-
.as_os_str()
236-
.as_bytes()
237-
.iter()
238-
.map(|&wchar| if wchar == '/' as u8 { '\\' as u8 } else { wchar })
239-
.collect::<Vec<_>>();
240-
Cow::Owned(OsString::from_vec(converted))
241-
} else {
242-
// Unix-on-Unix, all is fine.
243-
Cow::Borrowed(path.as_os_str())
244-
};
245-
252+
let os_str = convert_path_separator(path.as_os_str(), &this.tcx.sess.target.target.target_os, Pathconversion::HostToTarget);
246253
this.write_os_str_to_c_str(&os_str, scalar, size)
247254
}
255+
256+
/// Write a Path to the machine memory (as a null-terminated sequence of `u16`s),
257+
/// adjusting path separators if needed.
258+
fn write_path_to_wide_str(
259+
&mut self,
260+
path: &Path,
261+
scalar: Scalar<Tag>,
262+
size: u64,
263+
) -> InterpResult<'tcx, (bool, u64)> {
264+
let this = self.eval_context_mut();
265+
let os_str = convert_path_separator(path.as_os_str(), &this.tcx.sess.target.target.target_os, Pathconversion::HostToTarget);
266+
this.write_os_str_to_wide_str(&os_str, scalar, size)
267+
}
248268
}

tests/run-pass/current_dir.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
// ignore-windows: TODO the windows hook is not done yet
21
// compile-flags: -Zmiri-disable-isolation
32
use std::env;
4-
use std::path::Path;
3+
use std::io::ErrorKind;
54

65
fn main() {
76
// Test that `getcwd` is available
@@ -11,7 +10,9 @@ fn main() {
1110
// keep the current directory equal to `cwd`.
1211
let parent = cwd.parent().unwrap_or(&cwd);
1312
// Test that `chdir` is available
14-
assert!(env::set_current_dir(&Path::new("..")).is_ok());
13+
assert!(env::set_current_dir("..").is_ok());
1514
// Test that `..` goes to the parent directory
1615
assert_eq!(env::current_dir().unwrap(), parent);
16+
// Test that `chdir` to a non-existing directory returns a proper error
17+
assert_eq!(env::set_current_dir("thisdoesnotexist").unwrap_err().kind(), ErrorKind::NotFound);
1718
}

0 commit comments

Comments
 (0)