-
Notifications
You must be signed in to change notification settings - Fork 13.5k
Add Command::resolve_in_parent_path
#142035
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
rustbot has assigned @workingjubilee. Use |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Ah right, |
@rustbot author |
@bors2 try jobs= |
Add `Command::resolve_in_parent_path` This is part of #141976 (ACP: rust-lang/libs-team#591). libs-api could not come to a consensus on what the final API should look like so they've agreed to experiment with a few different approaches. This PR just implements the `resolve_in_parent_path` method which forces `Command` to ignore `PATH` set on the child for the purposes of resolving the executable. There was no consensus on what should happen if, for example, `chroot` is set (which can change the meaning of paths) so for now I've just done the easy thing. Figuring out the correct behaviour here will need to be done before this is considered for stabilisation but hopefully having it on nightly will nudge people in to giving their opinions. try-job: `aarch64-apple` try-job: `x86_64-msvc-*` try-job: `dist-various-*`
unsafe { | ||
let name = program.to_bytes(); | ||
let mut buffer = | ||
[const { mem::MaybeUninit::<u8>::uninit() }; libc::PATH_MAX as usize]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm aware that paths can be longer than PATH_MAX
but I can't allocate here. I could allocate a buffer before fork but this is an experimental feature so I really want it to be as self-contained as possible so it can be removed or radically changed without too much disruption. Unsized locals would be another alternative...
In any case, if this feature ever approaches stabilisation, I can fix it then.
/// On Unix the process is executed after fork and after setting up the rest of the new environment. | ||
/// So if `chroot`, `uid`, `gid` or `groups` are used then the way `PATH` is interpreted may be changed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this was a point without agreement on the libs-api side. E.g. what should happen if chroot
etc is set so the paths in PATH
essentially point somewhere different. I decided to implement it the easy way for the sake of experimentation and hope that someone will bug me if they think we must do something different and explain their reasons.
💔 Test failed
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
f1663e5
to
c8a279b
Compare
@bors2 try jobs=aarch64-apple,x86_64-msvc-,dist-various- |
Add `Command::resolve_in_parent_path` This is part of #141976 (ACP: rust-lang/libs-team#591). libs-api could not come to a consensus on what the final API should look like so they've agreed to experiment with a few different approaches. This PR just implements the `resolve_in_parent_path` method which forces `Command` to ignore `PATH` set on the child for the purposes of resolving the executable. There was no consensus on what should happen if, for example, `chroot` is set (which can change the meaning of paths) so for now I've just done the easy thing. Figuring out the correct behaviour here will need to be done before this is considered for stabilisation but hopefully having it on nightly will nudge people in to giving their opinions. try-job: aarch64-apple try-job: x86_64-msvc-* try-job: dist-various-*
💔 Test failed
|
@bors2 try jobs= |
Add `Command::resolve_in_parent_path` This is part of #141976 (ACP: rust-lang/libs-team#591). libs-api could not come to a consensus on what the final API should look like so they've agreed to experiment with a few different approaches. This PR just implements the `resolve_in_parent_path` method which forces `Command` to ignore `PATH` set on the child for the purposes of resolving the executable. There was no consensus on what should happen if, for example, `chroot` is set (which can change the meaning of paths) so for now I've just done the easy thing. Figuring out the correct behaviour here will need to be done before this is considered for stabilisation but hopefully having it on nightly will nudge people in to giving their opinions. try-job: `x86_64-msvc-*` try-job: `dist-various-*`
@bors2 try jobs= |
❗ A try build is currently in progress. You can cancel it using |
@bors2 try cancel |
Try build cancelled. Cancelled workflows: |
@bors2 try jobs= |
Add `Command::resolve_in_parent_path` This is part of #141976 (ACP: rust-lang/libs-team#591). libs-api could not come to a consensus on what the final API should look like so they've agreed to experiment with a few different approaches. This PR just implements the `resolve_in_parent_path` method which forces `Command` to ignore `PATH` set on the child for the purposes of resolving the executable. There was no consensus on what should happen if, for example, `chroot` is set (which can change the meaning of paths) so for now I've just done the easy thing. Figuring out the correct behaviour here will need to be done before this is considered for stabilisation but hopefully having it on nightly will nudge people in to giving their opinions. try-job: `x86_64-msvc-*` try-job: `dist-various-*`
So it turns out the apple failures (after the first) where all due to a try-job issue and not genuine failures. That's good because I was deeply confused about why it was failing. That's bad because I did waste time trying to figure it out. |
💔 Test failed
|
@bors2 try jobs= |
Add `Command::resolve_in_parent_path` This is part of #141976 (ACP: rust-lang/libs-team#591). libs-api could not come to a consensus on what the final API should look like so they've agreed to experiment with a few different approaches. This PR just implements the `resolve_in_parent_path` method which forces `Command` to ignore `PATH` set on the child for the purposes of resolving the executable. There was no consensus on what should happen if, for example, `chroot` is set (which can change the meaning of paths) so for now I've just done the easy thing. Figuring out the correct behaviour here will need to be done before this is considered for stabilisation but hopefully having it on nightly will nudge people in to giving their opinions. try-job: `x86_64-msvc-*` try-job: `dist-various-*`
I'm basically just fixing the unimplemented platforms at this point so I think this is ready for review @rustbot ready |
💔 Test failed
|
@bors2 try jobs= |
Add `Command::resolve_in_parent_path` This is part of #141976 (ACP: rust-lang/libs-team#591). libs-api could not come to a consensus on what the final API should look like so they've agreed to experiment with a few different approaches. This PR just implements the `resolve_in_parent_path` method which forces `Command` to ignore `PATH` set on the child for the purposes of resolving the executable. There was no consensus on what should happen if, for example, `chroot` is set (which can change the meaning of paths) so for now I've just done the easy thing. Figuring out the correct behaviour here will need to be done before this is considered for stabilisation but hopefully having it on nightly will nudge people in to giving their opinions. try-job: `x86_64-msvc-*` try-job: `dist-various-*`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If my ability to read code has not completely left me then I have found a tiny problem.
if let Some(envp) = maybe_envp | ||
&& self.get_resolve_in_parent_path() | ||
&& self.env_saw_path() | ||
&& self.get_program_kind() == ProgramKind::PathLookup | ||
{ | ||
use crate::ffi::CStr; | ||
// execvpe is a gnu extension... | ||
#[cfg(all(target_os = "linux", target_env = "gnu"))] | ||
unsafe fn exec_with_env( | ||
program: &CStr, | ||
args: &CStringArray, | ||
envp: &CStringArray, | ||
) -> io::Error { | ||
unsafe { libc::execvpe(program.as_ptr(), args.as_ptr(), envp.as_ptr()) }; | ||
io::Error::last_os_error() | ||
} | ||
|
||
// ...so if we're not gnu then use our own implementation. | ||
#[cfg(not(all(target_os = "linux", target_env = "gnu")))] | ||
unsafe fn exec_with_env( | ||
program: &CStr, | ||
args: &CStringArray, | ||
envp: &CStringArray, | ||
) -> io::Error { | ||
unsafe { | ||
let name = program.to_bytes(); | ||
let mut buffer = | ||
[const { mem::MaybeUninit::<u8>::uninit() }; libc::PATH_MAX as usize]; | ||
let mut environ = *sys::env::environ(); | ||
// Search the environment for PATH and, if found, | ||
// search the paths for the executable by trying to execve each candidate. | ||
while !(*environ).is_null() { | ||
let kv = CStr::from_ptr(*environ); | ||
if let Some(value) = kv.to_bytes().strip_prefix(b"PATH=") { | ||
for path in value.split(|&b| b == b':') { | ||
if buffer.len() - 2 >= path.len().saturating_add(name.len()) { | ||
let buf_ptr = buffer.as_mut_ptr().cast::<u8>(); | ||
let mut offset = 0; | ||
if !path.is_empty() { | ||
buf_ptr.copy_from(path.as_ptr(), path.len()); | ||
offset += path.len(); | ||
if path.last() != Some(&b'/') { | ||
*buf_ptr.add(path.len()) = b'/'; | ||
offset += 1; | ||
} | ||
} | ||
buf_ptr.add(offset).copy_from(name.as_ptr(), name.len()); | ||
offset += name.len(); | ||
*buf_ptr.add(offset) = 0; | ||
libc::execve(buf_ptr.cast(), args.as_ptr(), envp.as_ptr()); | ||
} | ||
} | ||
break; | ||
} | ||
environ = environ.add(1); | ||
} | ||
// If execve is successful then it'll never return, | ||
// thus we only reach this point on failure.. | ||
io::Error::from_raw_os_error(libc::ENOENT) | ||
} | ||
} | ||
_reset = Some(Reset(*sys::env::environ())); | ||
return Err(exec_with_env(self.get_program_cstr(), self.get_argv(), envp)); | ||
} else if let Some(envp) = maybe_envp { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks like the actual key part of the diff to review, I guess.
unsafe { | ||
let name = program.to_bytes(); | ||
let mut buffer = | ||
[const { mem::MaybeUninit::<u8>::uninit() }; libc::PATH_MAX as usize]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder how accurate PATH_MAX
actually is in practice...
let child_paths = if env_saw_path | ||
&& !self.force_parent_path | ||
&& let Some(env) = maybe_env.as_ref() | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm. this means that force_parent_path
removes child_paths
from consideration on line
rust/library/std/src/sys/process/windows.rs
Lines 536 to 538 in ee32374
// 1. Child paths | |
// This is for consistency with Rust's historic behavior. | |
if let Some(paths) = child_paths { |
|| (!self.get_resolve_in_parent_path() | ||
&& self.env_saw_path() | ||
&& !self.program_is_path()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hrm. do we ever examine this bool individually? feels like nah.
let name = program.to_bytes(); | ||
let mut buffer = | ||
[const { mem::MaybeUninit::<u8>::uninit() }; libc::PATH_MAX as usize]; | ||
let mut environ = *sys::env::environ(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can rely on environ always being defined since otherwise it's a linkage error, yeah, maybe.
let mut environ = *sys::env::environ(); | ||
// Search the environment for PATH and, if found, | ||
// search the paths for the executable by trying to execve each candidate. | ||
while !(*environ).is_null() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
environ
itself can have the value of a null pointer, however, thanks to clearenv.
This is part of #141976 (ACP: rust-lang/libs-team#591). libs-api could not come to a consensus on what the final API should look like so they've agreed to experiment with a few different approaches.
This PR just implements the
resolve_in_parent_path
method which forcesCommand
to ignorePATH
set on the child for the purposes of resolving the executable. There was no consensus on what should happen if, for example,chroot
is set (which can change the meaning of paths) so for now I've just done the easy thing. Figuring out the correct behaviour here will need to be done before this is considered for stabilisation but hopefully having it on nightly will nudge people in to giving their opinions.