Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4f0c7ba

Browse files
authoredMar 21, 2023
Rollup merge of rust-lang#96391 - ChrisDenton:command-non-verbatim, r=joshtriplett
Windows: make `Command` prefer non-verbatim paths When spawning Commands, the path we use can end up being queried using `env::current_exe` (or the equivalent in other languages). Not all applications handle these paths properly therefore we should have a stronger preference for non-verbatim paths when spawning processes.
2 parents ef03fda + 920435f commit 4f0c7ba

File tree

3 files changed

+62
-38
lines changed

3 files changed

+62
-38
lines changed
 

‎library/std/src/sys/windows/args.rs

+17-6
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ use crate::fmt;
1111
use crate::io;
1212
use crate::num::NonZeroU16;
1313
use crate::os::windows::prelude::*;
14-
use crate::path::PathBuf;
15-
use crate::sys::c;
14+
use crate::path::{Path, PathBuf};
15+
use crate::sys::path::get_long_path;
1616
use crate::sys::process::ensure_no_nuls;
1717
use crate::sys::windows::os::current_exe;
18+
use crate::sys::{c, to_u16s};
1819
use crate::sys_common::wstr::WStrUnits;
1920
use crate::vec;
2021

@@ -311,7 +312,7 @@ pub(crate) fn make_bat_command_line(
311312
/// Takes a path and tries to return a non-verbatim path.
312313
///
313314
/// This is necessary because cmd.exe does not support verbatim paths.
314-
pub(crate) fn to_user_path(mut path: Vec<u16>) -> io::Result<Vec<u16>> {
315+
pub(crate) fn to_user_path(path: &Path) -> io::Result<Vec<u16>> {
315316
use crate::ptr;
316317
use crate::sys::windows::fill_utf16_buf;
317318

@@ -324,6 +325,8 @@ pub(crate) fn to_user_path(mut path: Vec<u16>) -> io::Result<Vec<u16>> {
324325
const N: u16 = b'N' as _;
325326
const C: u16 = b'C' as _;
326327

328+
let mut path = to_u16s(path)?;
329+
327330
// Early return if the path is too long to remove the verbatim prefix.
328331
const LEGACY_MAX_PATH: usize = 260;
329332
if path.len() > LEGACY_MAX_PATH {
@@ -337,7 +340,13 @@ pub(crate) fn to_user_path(mut path: Vec<u16>) -> io::Result<Vec<u16>> {
337340
fill_utf16_buf(
338341
|buffer, size| c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()),
339342
|full_path: &[u16]| {
340-
if full_path == &path[4..path.len() - 1] { full_path.into() } else { path }
343+
if full_path == &path[4..path.len() - 1] {
344+
let mut path: Vec<u16> = full_path.into();
345+
path.push(0);
346+
path
347+
} else {
348+
path
349+
}
341350
},
342351
)
343352
},
@@ -350,7 +359,9 @@ pub(crate) fn to_user_path(mut path: Vec<u16>) -> io::Result<Vec<u16>> {
350359
|buffer, size| c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()),
351360
|full_path: &[u16]| {
352361
if full_path == &path[6..path.len() - 1] {
353-
full_path.into()
362+
let mut path: Vec<u16> = full_path.into();
363+
path.push(0);
364+
path
354365
} else {
355366
// Restore the 'C' in "UNC".
356367
path[6] = b'C' as u16;
@@ -360,6 +371,6 @@ pub(crate) fn to_user_path(mut path: Vec<u16>) -> io::Result<Vec<u16>> {
360371
)
361372
},
362373
// For everything else, leave the path unchanged.
363-
_ => Ok(path),
374+
_ => get_long_path(path, false),
364375
}
365376
}

‎library/std/src/sys/windows/path.rs

+41-24
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,19 @@ fn parse_next_component(path: &OsStr, verbatim: bool) -> (&OsStr, &OsStr) {
220220
///
221221
/// This path may or may not have a verbatim prefix.
222222
pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
223+
let path = to_u16s(path)?;
224+
get_long_path(path, true)
225+
}
226+
227+
/// Get a normalized absolute path that can bypass path length limits.
228+
///
229+
/// Setting prefer_verbatim to true suggests a stronger preference for verbatim
230+
/// paths even when not strictly necessary. This allows the Windows API to avoid
231+
/// repeating our work. However, if the path may be given back to users or
232+
/// passed to other application then it's preferable to use non-verbatim paths
233+
/// when possible. Non-verbatim paths are better understood by users and handled
234+
/// by more software.
235+
pub(crate) fn get_long_path(mut path: Vec<u16>, prefer_verbatim: bool) -> io::Result<Vec<u16>> {
223236
// Normally the MAX_PATH is 260 UTF-16 code units (including the NULL).
224237
// However, for APIs such as CreateDirectory[1], the limit is 248.
225238
//
@@ -243,7 +256,6 @@ pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
243256
// \\?\UNC\
244257
const UNC_PREFIX: &[u16] = &[SEP, SEP, QUERY, SEP, U, N, C, SEP];
245258

246-
let mut path = to_u16s(path)?;
247259
if path.starts_with(VERBATIM_PREFIX) || path.starts_with(NT_PREFIX) || path == &[0] {
248260
// Early return for paths that are already verbatim or empty.
249261
return Ok(path);
@@ -275,29 +287,34 @@ pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
275287
|mut absolute| {
276288
path.clear();
277289

278-
// Secondly, add the verbatim prefix. This is easier here because we know the
279-
// path is now absolute and fully normalized (e.g. `/` has been changed to `\`).
280-
let prefix = match absolute {
281-
// C:\ => \\?\C:\
282-
[_, COLON, SEP, ..] => VERBATIM_PREFIX,
283-
// \\.\ => \\?\
284-
[SEP, SEP, DOT, SEP, ..] => {
285-
absolute = &absolute[4..];
286-
VERBATIM_PREFIX
287-
}
288-
// Leave \\?\ and \??\ as-is.
289-
[SEP, SEP, QUERY, SEP, ..] | [SEP, QUERY, QUERY, SEP, ..] => &[],
290-
// \\ => \\?\UNC\
291-
[SEP, SEP, ..] => {
292-
absolute = &absolute[2..];
293-
UNC_PREFIX
294-
}
295-
// Anything else we leave alone.
296-
_ => &[],
297-
};
298-
299-
path.reserve_exact(prefix.len() + absolute.len() + 1);
300-
path.extend_from_slice(prefix);
290+
// Only prepend the prefix if needed.
291+
if prefer_verbatim || absolute.len() + 1 >= LEGACY_MAX_PATH {
292+
// Secondly, add the verbatim prefix. This is easier here because we know the
293+
// path is now absolute and fully normalized (e.g. `/` has been changed to `\`).
294+
let prefix = match absolute {
295+
// C:\ => \\?\C:\
296+
[_, COLON, SEP, ..] => VERBATIM_PREFIX,
297+
// \\.\ => \\?\
298+
[SEP, SEP, DOT, SEP, ..] => {
299+
absolute = &absolute[4..];
300+
VERBATIM_PREFIX
301+
}
302+
// Leave \\?\ and \??\ as-is.
303+
[SEP, SEP, QUERY, SEP, ..] | [SEP, QUERY, QUERY, SEP, ..] => &[],
304+
// \\ => \\?\UNC\
305+
[SEP, SEP, ..] => {
306+
absolute = &absolute[2..];
307+
UNC_PREFIX
308+
}
309+
// Anything else we leave alone.
310+
_ => &[],
311+
};
312+
313+
path.reserve_exact(prefix.len() + absolute.len() + 1);
314+
path.extend_from_slice(prefix);
315+
} else {
316+
path.reserve_exact(absolute.len() + 1);
317+
}
301318
path.extend_from_slice(absolute);
302319
path.push(0);
303320
},

‎library/std/src/sys/windows/process.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,7 @@ impl Command {
266266
let (program, mut cmd_str) = if is_batch_file {
267267
(
268268
command_prompt()?,
269-
args::make_bat_command_line(
270-
&args::to_user_path(program)?,
271-
&self.args,
272-
self.force_quotes_enabled,
273-
)?,
269+
args::make_bat_command_line(&program, &self.args, self.force_quotes_enabled)?,
274270
)
275271
} else {
276272
let cmd_str = make_command_line(&self.program, &self.args, self.force_quotes_enabled)?;
@@ -410,7 +406,7 @@ fn resolve_exe<'a>(
410406
if has_exe_suffix {
411407
// The application name is a path to a `.exe` file.
412408
// Let `CreateProcessW` figure out if it exists or not.
413-
return path::maybe_verbatim(Path::new(exe_path));
409+
return args::to_user_path(Path::new(exe_path));
414410
}
415411
let mut path = PathBuf::from(exe_path);
416412

@@ -422,7 +418,7 @@ fn resolve_exe<'a>(
422418
// It's ok to use `set_extension` here because the intent is to
423419
// remove the extension that was just added.
424420
path.set_extension("");
425-
return path::maybe_verbatim(&path);
421+
return args::to_user_path(&path);
426422
}
427423
} else {
428424
ensure_no_nuls(exe_path)?;
@@ -510,7 +506,7 @@ where
510506
/// Check if a file exists without following symlinks.
511507
fn program_exists(path: &Path) -> Option<Vec<u16>> {
512508
unsafe {
513-
let path = path::maybe_verbatim(path).ok()?;
509+
let path = args::to_user_path(path).ok()?;
514510
// Getting attributes using `GetFileAttributesW` does not follow symlinks
515511
// and it will almost always be successful if the link exists.
516512
// There are some exceptions for special system files (e.g. the pagefile)

0 commit comments

Comments
 (0)
Please sign in to comment.