Skip to content

Commit 350ba73

Browse files
committed
Add Command::resolve_in_parent_path
1 parent 61413ae commit 350ba73

File tree

6 files changed

+95
-13
lines changed

6 files changed

+95
-13
lines changed

library/std/src/process.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,22 @@ impl Command {
11951195
pub fn get_current_dir(&self) -> Option<&Path> {
11961196
self.inner.get_current_dir()
11971197
}
1198+
1199+
/// Resolve the program given in [`new`](Self::new) using the parent's PATH.
1200+
///
1201+
/// Usually when a `env` is used to set `PATH` on a child process,
1202+
/// that new `PATH` will be used to discover the process.
1203+
/// With `resolve_in_parent_path` to `true`, the parent's `PATH` will be used instead.
1204+
///
1205+
/// # Platform-specific behavior
1206+
///
1207+
/// On Unix the process is executed after fork and after setting up the rest of the new environment.
1208+
/// So if `chroot`, `uid`, `gid` or `groups` are used then the way `PATH` is interpreted may be changed.
1209+
#[unstable(feature = "command_resolve", issue = "none")]
1210+
pub fn resolve_in_parent_path(&mut self, use_parent: bool) -> &mut Self {
1211+
self.inner.resolve_in_parent_path(use_parent);
1212+
self
1213+
}
11981214
}
11991215

12001216
#[stable(feature = "rust1", since = "1.0.0")]

library/std/src/process/tests.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use super::{Command, Output, Stdio};
22
use crate::io::prelude::*;
33
use crate::io::{BorrowedBuf, ErrorKind};
44
use crate::mem::MaybeUninit;
5-
use crate::str;
5+
use crate::{fs, str};
66

77
fn known_command() -> Command {
8-
if cfg!(windows) { Command::new("help") } else { Command::new("echo") }
8+
if cfg!(windows) { Command::new("help.exe") } else { Command::new("echo") }
99
}
1010

1111
#[cfg(target_os = "android")]
@@ -603,3 +603,27 @@ fn terminate_exited_process() {
603603
assert!(p.kill().is_ok());
604604
assert!(p.kill().is_ok());
605605
}
606+
607+
#[test]
608+
fn resolve_in_parent_path() {
609+
let tempdir = crate::test_helpers::tmpdir();
610+
let mut cmd = if cfg!(windows) {
611+
// On Windows std prepends the system paths when searching for executables
612+
// but not if PATH is set on the command. Therefore adding a broken exe
613+
// using PATH will cause spawn to fail if it's invoked.
614+
let mut cmd = known_command();
615+
fs::write(tempdir.join(cmd.get_program().to_str().unwrap()), "").unwrap();
616+
cmd.env("PATH", tempdir.path());
617+
cmd
618+
} else {
619+
let mut cmd = known_command();
620+
cmd.env("PATH", "");
621+
cmd
622+
};
623+
624+
assert!(cmd.spawn().is_err());
625+
cmd.resolve_in_parent_path(true);
626+
assert!(cmd.spawn().is_ok());
627+
cmd.resolve_in_parent_path(false);
628+
assert!(cmd.spawn().is_err());
629+
}

library/std/src/sys/process/unix/common.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub struct Command {
9898
#[cfg(target_os = "linux")]
9999
create_pidfd: bool,
100100
pgroup: Option<pid_t>,
101+
force_parent_path: bool,
101102
}
102103

103104
// passed back to std::process with the pipes connected to the child, if any
@@ -185,6 +186,7 @@ impl Command {
185186
#[cfg(target_os = "linux")]
186187
create_pidfd: false,
187188
pgroup: None,
189+
force_parent_path: false,
188190
}
189191
}
190192

@@ -220,6 +222,9 @@ impl Command {
220222
self.cwd(&OsStr::new("/"));
221223
}
222224
}
225+
pub fn resolve_in_parent_path(&mut self, use_parent: bool) {
226+
self.force_parent_path = use_parent;
227+
}
223228

224229
#[cfg(target_os = "linux")]
225230
pub fn create_pidfd(&mut self, val: bool) {
@@ -298,6 +303,10 @@ impl Command {
298303
pub fn get_chroot(&self) -> Option<&CStr> {
299304
self.chroot.as_deref()
300305
}
306+
#[allow(dead_code)]
307+
pub fn get_search_paths(&self) -> Option<&OsStr> {
308+
self.search_paths.as_deref()
309+
}
301310

302311
pub fn get_closures(&mut self) -> &mut Vec<Box<dyn FnMut() -> io::Result<()> + Send + Sync>> {
303312
&mut self.closures
@@ -338,6 +347,11 @@ impl Command {
338347
self.program.to_bytes().contains(&b'/')
339348
}
340349

350+
#[allow(dead_code)]
351+
pub fn use_child_path(&self) -> bool {
352+
!self.force_parent_path && self.env_saw_path() && !self.program_is_path()
353+
}
354+
341355
pub fn setup_io(
342356
&self,
343357
default: Stdio,

library/std/src/sys/process/unix/unix.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ impl Command {
8989
// The child calls `mem::forget` to leak the lock, which is crucial because
9090
// releasing a lock is not async-signal-safe.
9191
let env_lock = sys::env::env_read_lock();
92+
9293
let pid = unsafe { self.do_fork()? };
9394

9495
if pid == 0 {
@@ -276,7 +277,7 @@ impl Command {
276277
unsafe fn do_exec(
277278
&mut self,
278279
stdio: ChildPipes,
279-
maybe_envp: Option<&CStringArray>,
280+
mut maybe_envp: Option<&CStringArray>,
280281
) -> Result<!, io::Error> {
281282
use crate::sys::{self, cvt_r};
282283

@@ -378,13 +379,15 @@ impl Command {
378379
callback()?;
379380
}
380381

381-
// Although we're performing an exec here we may also return with an
382-
// error from this function (without actually exec'ing) in which case we
383-
// want to be sure to restore the global environment back to what it
384-
// once was, ensuring that our temporary override, when free'd, doesn't
385-
// corrupt our process's environment.
386382
let mut _reset = None;
387-
if let Some(envp) = maybe_envp {
383+
if let Some(envp) = maybe_envp
384+
&& self.use_child_path()
385+
{
386+
// Although we're performing an exec here we may also return with an
387+
// error from this function (without actually exec'ing) in which case we
388+
// want to be sure to restore the global environment back to what it
389+
// once was, ensuring that our temporary override, when free'd, doesn't
390+
// corrupt our process's environment.
388391
struct Reset(*const *const libc::c_char);
389392

390393
impl Drop for Reset {
@@ -397,9 +400,18 @@ impl Command {
397400

398401
_reset = Some(Reset(*sys::env::environ()));
399402
*sys::env::environ() = envp.as_ptr();
403+
// We've set the environment, no need to set it again.
404+
maybe_envp = None;
405+
}
406+
if let Some(envp) = maybe_envp {
407+
libc::execvpe(
408+
self.get_program_cstr().as_ptr(),
409+
self.get_argv().as_ptr(),
410+
envp.as_ptr(),
411+
);
412+
} else {
413+
libc::execvp(self.get_program_cstr().as_ptr(), self.get_argv().as_ptr());
400414
}
401-
402-
libc::execvp(self.get_program_cstr().as_ptr(), self.get_argv().as_ptr());
403415
Err(io::Error::last_os_error())
404416
}
405417

@@ -453,7 +465,7 @@ impl Command {
453465

454466
if self.get_gid().is_some()
455467
|| self.get_uid().is_some()
456-
|| (self.env_saw_path() && !self.program_is_path())
468+
|| self.use_child_path()
457469
|| !self.get_closures().is_empty()
458470
|| self.get_groups().is_some()
459471
|| self.get_chroot().is_some()
@@ -793,6 +805,7 @@ impl Command {
793805
// Safety: -1 indicates we don't have a pidfd.
794806
let mut p = Process::new(0, -1);
795807

808+
// FIXME: if a list of paths to search is passed then retry for every path.
796809
let spawn_res = spawn_fn(
797810
&mut p.pid,
798811
self.get_program_cstr().as_ptr(),

library/std/src/sys/process/unsupported.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub struct Command {
2121
stdin: Option<Stdio>,
2222
stdout: Option<Stdio>,
2323
stderr: Option<Stdio>,
24+
force_parent_path: bool,
2425
}
2526

2627
// passed back to std::process with the pipes connected to the child, if any
@@ -52,6 +53,7 @@ impl Command {
5253
stdin: None,
5354
stdout: None,
5455
stderr: None,
56+
force_parent_path: false,
5557
}
5658
}
5759

@@ -79,6 +81,10 @@ impl Command {
7981
self.stderr = Some(stderr);
8082
}
8183

84+
pub fn resolve_in_parent_path(&mut self, use_parent: bool) {
85+
self.force_parent_path = use_parent;
86+
}
87+
8288
pub fn get_program(&self) -> &OsStr {
8389
&self.program
8490
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ pub struct Command {
158158
startupinfo_fullscreen: bool,
159159
startupinfo_untrusted_source: bool,
160160
startupinfo_force_feedback: Option<bool>,
161+
force_parent_path: bool,
161162
}
162163

163164
pub enum Stdio {
@@ -192,6 +193,7 @@ impl Command {
192193
startupinfo_fullscreen: false,
193194
startupinfo_untrusted_source: false,
194195
startupinfo_force_feedback: None,
196+
force_parent_path: false,
195197
}
196198
}
197199

@@ -224,6 +226,10 @@ impl Command {
224226
self.force_quotes_enabled = enabled;
225227
}
226228

229+
pub fn resolve_in_parent_path(&mut self, use_parent: bool) {
230+
self.force_parent_path = use_parent;
231+
}
232+
227233
pub fn raw_arg(&mut self, command_str_to_append: &OsStr) {
228234
self.args.push(Arg::Raw(command_str_to_append.to_os_string()))
229235
}
@@ -274,7 +280,10 @@ impl Command {
274280
let env_saw_path = self.env.have_changed_path();
275281
let maybe_env = self.env.capture_if_changed();
276282

277-
let child_paths = if env_saw_path && let Some(env) = maybe_env.as_ref() {
283+
let child_paths = if env_saw_path
284+
&& !self.force_parent_path
285+
&& let Some(env) = maybe_env.as_ref()
286+
{
278287
env.get(&EnvKey::new("PATH")).map(|s| s.as_os_str())
279288
} else {
280289
None

0 commit comments

Comments
 (0)