Skip to content

Commit 6c34d46

Browse files
committed
Auto merge of #3502 - RalfJung:GetUserProfileDirectoryW, r=RalfJung
windows: basic support for GetUserProfileDirectoryW Fixes rust-lang/miri#3499
2 parents a3fddf2 + ccb87f1 commit 6c34d46

File tree

8 files changed

+184
-67
lines changed

8 files changed

+184
-67
lines changed

src/tools/miri/Cargo.lock

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,27 @@ dependencies = [
299299
"windows-sys 0.52.0",
300300
]
301301

302+
[[package]]
303+
name = "directories"
304+
version = "5.0.1"
305+
source = "registry+https://github.com/rust-lang/crates.io-index"
306+
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
307+
dependencies = [
308+
"dirs-sys",
309+
]
310+
311+
[[package]]
312+
name = "dirs-sys"
313+
version = "0.4.1"
314+
source = "registry+https://github.com/rust-lang/crates.io-index"
315+
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
316+
dependencies = [
317+
"libc",
318+
"option-ext",
319+
"redox_users",
320+
"windows-sys 0.48.0",
321+
]
322+
302323
[[package]]
303324
name = "encode_unicode"
304325
version = "0.3.6"
@@ -490,6 +511,16 @@ dependencies = [
490511
"windows-sys 0.48.0",
491512
]
492513

514+
[[package]]
515+
name = "libredox"
516+
version = "0.1.3"
517+
source = "registry+https://github.com/rust-lang/crates.io-index"
518+
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
519+
dependencies = [
520+
"bitflags 2.4.2",
521+
"libc",
522+
]
523+
493524
[[package]]
494525
name = "linux-raw-sys"
495526
version = "0.4.13"
@@ -558,6 +589,7 @@ dependencies = [
558589
"chrono",
559590
"colored",
560591
"ctrlc",
592+
"directories",
561593
"getrandom",
562594
"jemalloc-sys",
563595
"lazy_static",
@@ -614,6 +646,12 @@ version = "1.19.0"
614646
source = "registry+https://github.com/rust-lang/crates.io-index"
615647
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
616648

649+
[[package]]
650+
name = "option-ext"
651+
version = "0.2.0"
652+
source = "registry+https://github.com/rust-lang/crates.io-index"
653+
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
654+
617655
[[package]]
618656
name = "owo-colors"
619657
version = "3.5.0"
@@ -746,6 +784,17 @@ dependencies = [
746784
"bitflags 1.3.2",
747785
]
748786

787+
[[package]]
788+
name = "redox_users"
789+
version = "0.4.5"
790+
source = "registry+https://github.com/rust-lang/crates.io-index"
791+
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
792+
dependencies = [
793+
"getrandom",
794+
"libredox",
795+
"thiserror",
796+
]
797+
749798
[[package]]
750799
name = "regex"
751800
version = "1.10.3"

src/tools/miri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ aes = { version = "0.8.3", features = ["hazmat"] }
2525
measureme = "11"
2626
ctrlc = "3.2.5"
2727
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
28+
directories = "5"
2829

2930
# Copied from `compiler/rustc/Cargo.toml`.
3031
# But only for some targets, it fails for others. Rustc configures this in its CI, but we can't

src/tools/miri/src/shims/env.rs

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
160160
this.assert_target_os("windows", "GetEnvironmentVariableW");
161161

162162
let name_ptr = this.read_pointer(name_op)?;
163+
let buf_ptr = this.read_pointer(buf_op)?;
164+
let buf_size = this.read_scalar(size_op)?.to_u32()?; // in characters
165+
163166
let name = this.read_os_str_from_wide_str(name_ptr)?;
164167
Ok(match this.machine.env_vars.map.get(&name) {
165168
Some(&var_ptr) => {
166-
this.set_last_error(Scalar::from_u32(0))?; // make sure this is unambiguously not an error
167169
// The offset is used to strip the "{name}=" part of the string.
168170
#[rustfmt::skip]
169171
let name_offset_bytes = u64::try_from(name.len()).unwrap()
@@ -172,14 +174,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
172174
let var_ptr = var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?;
173175
let var = this.read_os_str_from_wide_str(var_ptr)?;
174176

175-
let buf_ptr = this.read_pointer(buf_op)?;
176-
// `buf_size` represents the size in characters.
177-
let buf_size = u64::from(this.read_scalar(size_op)?.to_u32()?);
178-
Scalar::from_u32(windows_check_buffer_size(
179-
this.write_os_str_to_wide_str(
180-
&var, buf_ptr, buf_size, /*truncate*/ false,
181-
)?,
182-
))
177+
Scalar::from_u32(windows_check_buffer_size(this.write_os_str_to_wide_str(
178+
&var,
179+
buf_ptr,
180+
buf_size.into(),
181+
)?))
182+
// This can in fact return 0. It is up to the caller to set last_error to 0
183+
// beforehand and check it afterwards to exclude that case.
183184
}
184185
None => {
185186
let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND");
@@ -375,9 +376,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
375376
// If we cannot get the current directory, we return 0
376377
match env::current_dir() {
377378
Ok(cwd) => {
378-
this.set_last_error(Scalar::from_u32(0))?; // make sure this is unambiguously not an error
379+
// This can in fact return 0. It is up to the caller to set last_error to 0
380+
// beforehand and check it afterwards to exclude that case.
379381
return Ok(Scalar::from_u32(windows_check_buffer_size(
380-
this.write_path_to_wide_str(&cwd, buf, size, /*truncate*/ false)?,
382+
this.write_path_to_wide_str(&cwd, buf, size)?,
381383
)));
382384
}
383385
Err(e) => this.set_last_error_from_io_error(e.kind())?,
@@ -494,9 +496,60 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
494496
fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> {
495497
let this = self.eval_context_mut();
496498
this.assert_target_os("windows", "GetCurrentProcessId");
497-
498499
this.check_no_isolation("`GetCurrentProcessId`")?;
499500

500501
Ok(std::process::id())
501502
}
503+
504+
#[allow(non_snake_case)]
505+
fn GetUserProfileDirectoryW(
506+
&mut self,
507+
token: &OpTy<'tcx, Provenance>, // HANDLE
508+
buf: &OpTy<'tcx, Provenance>, // LPWSTR
509+
size: &OpTy<'tcx, Provenance>, // LPDWORD
510+
) -> InterpResult<'tcx, Scalar<Provenance>> // returns BOOL
511+
{
512+
let this = self.eval_context_mut();
513+
this.assert_target_os("windows", "GetUserProfileDirectoryW");
514+
this.check_no_isolation("`GetUserProfileDirectoryW`")?;
515+
516+
let token = this.read_target_isize(token)?;
517+
let buf = this.read_pointer(buf)?;
518+
let size = this.deref_pointer(size)?;
519+
520+
if token != -4 {
521+
throw_unsup_format!(
522+
"GetUserProfileDirectoryW: only CURRENT_PROCESS_TOKEN is supported"
523+
);
524+
}
525+
526+
// See <https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-getuserprofiledirectoryw> for docs.
527+
Ok(match directories::UserDirs::new() {
528+
Some(dirs) => {
529+
let home = dirs.home_dir();
530+
let size_avail = if this.ptr_is_null(size.ptr())? {
531+
0 // if the buf pointer is null, we can't write to it; `size` will be updated to the required length
532+
} else {
533+
this.read_scalar(&size)?.to_u32()?
534+
};
535+
// Of course we cannot use `windows_check_buffer_size` here since this uses
536+
// a different method for dealing with a too-small buffer than the other functions...
537+
let (success, len) = this.write_path_to_wide_str(home, buf, size_avail.into())?;
538+
// The Windows docs just say that this is written on failure. But std
539+
// seems to rely on it always being written.
540+
this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?;
541+
if success {
542+
Scalar::from_i32(1) // return TRUE
543+
} else {
544+
this.set_last_error(this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"))?;
545+
Scalar::from_i32(0) // return FALSE
546+
}
547+
}
548+
None => {
549+
// We have to pick some error code.
550+
this.set_last_error(this.eval_windows("c", "ERROR_BAD_USER_PROFILE"))?;
551+
Scalar::from_i32(0) // return FALSE
552+
}
553+
})
554+
}
502555
}

src/tools/miri/src/shims/os_str.rs

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
7272
u16vec_to_osstring(u16_vec)
7373
}
7474

75-
/// Helper function to write an OsStr as a null-terminated sequence of bytes, which is what
76-
/// the Unix APIs usually handle. This function returns `Ok((false, length))` without trying
77-
/// to write if `size` is not large enough to fit the contents of `os_string` plus a null
78-
/// terminator. It returns `Ok((true, length))` if the writing process was successful. The
79-
/// string length returned does include the null terminator.
75+
/// Helper function to write an OsStr as a null-terminated sequence of bytes, which is what the
76+
/// Unix APIs usually handle. Returns `(success, full_len)`, where length includes the null
77+
/// terminator. On failure, nothing is written.
8078
fn write_os_str_to_c_str(
8179
&mut self,
8280
os_str: &OsStr,
@@ -87,19 +85,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
8785
self.eval_context_mut().write_c_str(bytes, ptr, size)
8886
}
8987

90-
/// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what the
91-
/// Windows APIs usually handle.
92-
///
93-
/// If `truncate == false` (the usual mode of operation), this function returns `Ok((false,
94-
/// length))` without trying to write if `size` is not large enough to fit the contents of
95-
/// `os_string` plus a null terminator. It returns `Ok((true, length))` if the writing process
96-
/// was successful. The string length returned does include the null terminator. Length is
97-
/// measured in units of `u16.`
98-
///
99-
/// If `truncate == true`, then in case `size` is not large enough it *will* write the first
100-
/// `size.saturating_sub(1)` many items, followed by a null terminator (if `size > 0`).
101-
/// The return value is still `(false, length)` in that case.
102-
fn write_os_str_to_wide_str(
88+
/// Internal helper to share code between `write_os_str_to_wide_str` and
89+
/// `write_os_str_to_wide_str_truncated`.
90+
fn write_os_str_to_wide_str_helper(
10391
&mut self,
10492
os_str: &OsStr,
10593
ptr: Pointer<Option<Provenance>>,
@@ -133,6 +121,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
133121
Ok((written, size_needed))
134122
}
135123

124+
/// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what the
125+
/// Windows APIs usually handle. Returns `(success, full_len)`, where length is measured
126+
/// in units of `u16` and includes the null terminator. On failure, nothing is written.
127+
fn write_os_str_to_wide_str(
128+
&mut self,
129+
os_str: &OsStr,
130+
ptr: Pointer<Option<Provenance>>,
131+
size: u64,
132+
) -> InterpResult<'tcx, (bool, u64)> {
133+
self.write_os_str_to_wide_str_helper(os_str, ptr, size, /*truncate*/ false)
134+
}
135+
136+
/// Like `write_os_str_to_wide_str`, but on failure as much as possible is written into
137+
/// the buffer (always with a null terminator).
138+
fn write_os_str_to_wide_str_truncated(
139+
&mut self,
140+
os_str: &OsStr,
141+
ptr: Pointer<Option<Provenance>>,
142+
size: u64,
143+
) -> InterpResult<'tcx, (bool, u64)> {
144+
self.write_os_str_to_wide_str_helper(os_str, ptr, size, /*truncate*/ true)
145+
}
146+
136147
/// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of bytes.
137148
fn alloc_os_str_as_c_str(
138149
&mut self,
@@ -160,9 +171,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
160171

161172
let arg_type = Ty::new_array(this.tcx.tcx, this.tcx.types.u16, size);
162173
let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?;
163-
let (written, _) = self
164-
.write_os_str_to_wide_str(os_str, arg_place.ptr(), size, /*truncate*/ false)
165-
.unwrap();
174+
let (written, _) = self.write_os_str_to_wide_str(os_str, arg_place.ptr(), size).unwrap();
166175
assert!(written);
167176
Ok(arg_place.ptr())
168177
}
@@ -217,12 +226,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
217226
path: &Path,
218227
ptr: Pointer<Option<Provenance>>,
219228
size: u64,
220-
truncate: bool,
221229
) -> InterpResult<'tcx, (bool, u64)> {
222230
let this = self.eval_context_mut();
223231
let os_str =
224232
this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
225-
this.write_os_str_to_wide_str(&os_str, ptr, size, truncate)
233+
this.write_os_str_to_wide_str(&os_str, ptr, size)
234+
}
235+
236+
/// Write a Path to the machine memory (as a null-terminated sequence of `u16`s),
237+
/// adjusting path separators if needed.
238+
fn write_path_to_wide_str_truncated(
239+
&mut self,
240+
path: &Path,
241+
ptr: Pointer<Option<Provenance>>,
242+
size: u64,
243+
) -> InterpResult<'tcx, (bool, u64)> {
244+
let this = self.eval_context_mut();
245+
let os_str =
246+
this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
247+
this.write_os_str_to_wide_str_truncated(&os_str, ptr, size)
226248
}
227249

228250
/// Allocate enough memory to store a Path as a null-terminated sequence of bytes,

src/tools/miri/src/shims/unix/linux/mem.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
2323
// old_address must be a multiple of the page size
2424
#[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
2525
if old_address.addr().bytes() % this.machine.page_size != 0 || new_size == 0 {
26-
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
26+
this.set_last_error(this.eval_libc("EINVAL"))?;
2727
return Ok(this.eval_libc("MAP_FAILED"));
2828
}
2929

@@ -37,7 +37,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
3737

3838
if flags & this.eval_libc_i32("MREMAP_MAYMOVE") == 0 {
3939
// We only support MREMAP_MAYMOVE, so not passing the flag is just a failure
40-
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
40+
this.set_last_error(this.eval_libc("EINVAL"))?;
4141
return Ok(this.eval_libc("MAP_FAILED"));
4242
}
4343

src/tools/miri/src/shims/unix/mem.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
5353

5454
// First, we do some basic argument validation as required by mmap
5555
if (flags & (map_private | map_shared)).count_ones() != 1 {
56-
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
56+
this.set_last_error(this.eval_libc("EINVAL"))?;
5757
return Ok(this.eval_libc("MAP_FAILED"));
5858
}
5959
if length == 0 {
60-
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
60+
this.set_last_error(this.eval_libc("EINVAL"))?;
6161
return Ok(this.eval_libc("MAP_FAILED"));
6262
}
6363

@@ -77,7 +77,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
7777
//
7878
// Miri doesn't support MAP_FIXED or any any protections other than PROT_READ|PROT_WRITE.
7979
if flags & map_fixed != 0 || prot != prot_read | prot_write {
80-
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("ENOTSUP")))?;
80+
this.set_last_error(this.eval_libc("ENOTSUP"))?;
8181
return Ok(this.eval_libc("MAP_FAILED"));
8282
}
8383

@@ -96,11 +96,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
9696

9797
let align = this.machine.page_align();
9898
let Some(map_length) = length.checked_next_multiple_of(this.machine.page_size) else {
99-
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
99+
this.set_last_error(this.eval_libc("EINVAL"))?;
100100
return Ok(this.eval_libc("MAP_FAILED"));
101101
};
102102
if map_length > this.target_usize_max() {
103-
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
103+
this.set_last_error(this.eval_libc("EINVAL"))?;
104104
return Ok(this.eval_libc("MAP_FAILED"));
105105
}
106106

@@ -131,16 +131,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
131131
// as a dealloc.
132132
#[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
133133
if addr.addr().bytes() % this.machine.page_size != 0 {
134-
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
134+
this.set_last_error(this.eval_libc("EINVAL"))?;
135135
return Ok(Scalar::from_i32(-1));
136136
}
137137

138138
let Some(length) = length.checked_next_multiple_of(this.machine.page_size) else {
139-
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
139+
this.set_last_error(this.eval_libc("EINVAL"))?;
140140
return Ok(Scalar::from_i32(-1));
141141
};
142142
if length > this.target_usize_max() {
143-
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
143+
this.set_last_error(this.eval_libc("EINVAL"))?;
144144
return Ok(this.eval_libc("MAP_FAILED"));
145145
}
146146

0 commit comments

Comments
 (0)