Repository navigation
Conversation
|
r? @joboet rustbot has assigned @joboet. Use Why was this reviewer chosen?The reviewer was selected based on:
|
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
I highly recommend having a look at the POSIX specification – GNU/Linux mostly follows that, but the specification sometimes leaves some things unspecified and we should be careful not to break those.
Also, this is currently unsound, we cannot perform any memory allocation in do_exec, since it's called after fork for the standard spawn path. You probably need to preallocate some memory when creating the Command (or when setting certain parameters).
| None => c"/bin:/usr/bin".to_bytes_with_nul(), | ||
| } | ||
| } | ||
| None => parent_path.as_bytes_with_nul(), |
There was a problem hiding this comment.
I recommend moving the environment variable lookup here so that it's only performed when actually needed.
|
Reminder, once the PR becomes ready for a review, use |
This comment has been minimized.
This comment has been minimized.
213bb02 to
8319b09
Compare
This comment has been minimized.
This comment has been minimized.
| assert_failed(index, len); | ||
| } | ||
|
|
||
| self.ptrs.insert(index, item.into_raw()); |
There was a problem hiding this comment.
This leaks the string if insert panics (see #155748, I made this mistake before).
|
Yep, I saw. I was actually working on it before the notif I received for the I have to check a couple of other platforms that wasn't listed earlier to see how execvp works.
Do we know what we want to do for certain platforms like Solaris/AIX? I think those platforms are closed-source, so I couldn't find info about
|
71e457f to
0289d85
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
This comment has been minimized.
This comment has been minimized.
0289d85 to
4c2289e
Compare
This comment has been minimized.
This comment has been minimized.
4c2289e to
78a1404
Compare
This comment has been minimized.
This comment has been minimized.
…H on Apple, Android, OpenBSD, FreeBSD, and DragonflyBSD as the default fallback path when the PATH environment variable was not found from maybe_envp/environ(); furthermore, added support for invoking the shell when execve returns an ENOEXEC error on Android, all Linux platforms (except musl), OpenBSD, NetBSD (for exec, but not posix_spawn), Apple, DragonFlyBSD, and Haiku.
78a1404 to
4359483
Compare
|
@rustbot ready |
…code in separate functions
2c3f041 to
07cf726
Compare
| @@ -83,6 +83,40 @@ cfg_select! { | |||
| } | |||
| } | |||
|
|
|||
| /// Retrieves the fallback path *specifically* used by each platform's `execvp` | |||
| /// implementation. | |||
| fn get_default_path() -> CString { | |||
There was a problem hiding this comment.
(Apologies for the repeat ping, previous message is label outdated due to new commits)
@joboet see my comment earlier #157144 (comment)
Unresolved question here is should we just directly use ":/bin:/usr/bin" for uclibc's execvp default fallback path? This is different from its _PATH_DEFPATH value which is "/usr/bin:/bin"
There was a problem hiding this comment.
Do we know what we want to do for certain platforms like Solaris/AIX? I think those platforms are closed-source, so I couldn't find info about _PATH_DEFPATH/_PATH_BSHELL. I know Solaris is listed as tier 2 with host tools, so being able to build the standard library is supposed to be guaranteed.
@psumbera for Solaris, @xingxue-ibm for AIX: Do these platforms have constants like _PATH_DEFPATH that are used by execvp when no PATH is available? Or is there a better way to emulate execvp behavior?
Probably doing a libc release in 1-2 weeks, as a heads up if there's something to add there.
There was a problem hiding this comment.
AIX's paths.h defines _PATH_BSHELL but not _PATH_DEFPATH. I opened PR rust-lang/libc#5676 to add _PATH_BSHELL to libc for AIX.
There was a problem hiding this comment.
@xingxue-ibm does AIX invoke the shell (e.g. if not i.e. _PATH_BSHELL) on ENOEXEC in their execvp implementation? (something similar to what glibc does here) Follow up question I have is does posix_spawn (or equivalent name) forward to execvpe? If it does, I would like to put up a FIXME comment in those areas.
(To note for reviewers, if _PATH_BSHELL or some equivalent path is used in ENOEXEC behavior or if _PATH_DEFPATH is used, we can put up a different/future PR to add these behaviors in since the code is written in a way that you can easily insert the correct behavior in Command::get_shell_argv or get_default_path)
There was a problem hiding this comment.
I checked the current Solaris ON libc sources.
Solaris defines _PATH_BSHELL in <paths.h> as "/usr/bin/sh", but it does not define _PATH_DEFPATH.
When PATH is unset, Solaris execvp does not use a fixed fallback constant. Its get_PATH() helper calls confstr(_CS_PATH) and then:
- appends
":/usr/sbin"when either the real or effective UID is 0; - otherwise appends
":", causing the current directory to be searched last.
Therefore, the closest emulation is to obtain _CS_PATH before the fork and implement the UID-dependent suffix, rather than hard-coding a _PATH_DEFPATH. The suffix should be selected after any requested credential change has been applied.
Solaris does handle ENOEXEC by invoking a shell. The executable is _PATH_BSHELL (/usr/bin/sh) normally, or /usr/xpg4/bin/sh for an XPG4-mode caller; its argv[0] is "sh". posix_spawnp() uses the same path and shell helpers.
For rust-lang/libc, it would be useful to add both Solaris _PATH_BSHELL and _CS_PATH (65). One caveat: the existing Rust Solaris confstr binding links to __confstr_xpg7, whereas Solaris execvp uses the mode-sensitive base confstr. Thus simply adding Solaris to the existing libc::confstr branch would select the XPG7 path and would not exactly reproduce current execvp behavior.
Oracle's documentation also confirms that the ENOEXEC shell depends on the caller's conformance mode: execvp(2) and standards(7).
There was a problem hiding this comment.
@xingxue-ibm does AIX invoke the shell (e.g. if not i.e. _PATH_BSHELL) on ENOEXEC in their execvp implementation? (something similar to what glibc does here) Follow up question I have is does posix_spawn (or equivalent name) forward to execvpe? If it does, I would like to put up a FIXME comment in those areas.
I don't have access to the AIX system source code, but I checked on AIX 7.2 by running tests and disassembling some routines. Here are my findings:
• ENOEXEC: execvp and execvpe run execv("/usr/bin/sh", {"sh", "--", <script>, <args...>}). The shell path is the literal /usr/bin/sh, not _PATH_BSHELL, which AIX defines as /usr/bin/bsh in /usr/include/paths.h.
• No PATH: The fallback is :/bin:/usr/bin, with the current directory searched first (the same string you found for uClibc). AIX's paths.h has no _PATH_DEFPATH.
• posix_spawnp: It forwards to execvpe. It forks, and the child calls execvpe (or execvp if envp is NULL).
A FIXME in both places works for me, with the AIX handling in a follow-up PR.
There was a problem hiding this comment.
@xingxue-ibm Got you and thank you for testing execvp/posix_spawnp behavior on AIX!
If there's no reliance on _PATH_BSHELL (as well as no existence of _PATH_DEFPATH) and AIX forwards posix_spawnp to execvpe, then I'll hardcode the values here (should be self explanatory since it would be cfg gated on AIX). Will update the PR in the morning once I get some rest.
There was a problem hiding this comment.
No, I am not suggesting removing the existing #[link_name = "__confstr_xpg7"] from rust-lang/libc. That mapping from libc#4035 is intentional and should remain.
On Solaris, when C code is compiled with _XOPEN_SOURCE=700, <unistd.h> redirects the source-level name confstr to the ELF symbol __confstr_xpg7. The libc test configuration uses _XOPEN_SOURCE=700, which is why the Rust libc::confstr declaration needs that link_name to represent the XPG7-facing API correctly.
This is separate from what the already-compiled Solaris libc implementation of execvp does internally. Its path is effectively:
execvp -> get_PATH -> base ELF symbol "confstr"
The base confstr symbol is itself mode-sensitive: it reads the process's __xpg4/__xpg6 state and returns the corresponding path. Therefore, Rust does not need a new compile-time cfg analogous to a musl-version cfg, and it does not need to determine the XPG mode itself.
To preserve the behavior of the old execvp call, std can keep the public libc::confstr binding unchanged and declare a separate private Solaris-only binding with a different Rust name:
unsafe extern "C" {
#[link_name = "confstr"]
fn solaris_confstr_base(
name: libc::c_int,
buf: *mut libc::c_char,
len: libc::size_t,
) -> libc::size_t;
}That private function and libc::confstr refer to two different Solaris ELF symbols, so there is no conflict and no breaking change to rust-lang/libc.
The shell choice is a separate issue. For an ordinary Rust process, where __xpg4 remains at its default value of 0, _PATH_BSHELL (/usr/bin/sh) matches execvp. If exact behavior for a process explicitly linked into XPG mode is also required, the shell must likewise be selected from that process mode; globally replacing _PATH_BSHELL with /usr/xpg4/bin/sh would instead break the default mode.
There was a problem hiding this comment.
Okay, I think I understand how this would work, and thank you for taking the time to clarify this with me!
In regards to this:
The shell choice is a separate issue. For an ordinary Rust process, where `__xpg4` remains at its default value of 0, `_PATH_BSHELL` (`/usr/bin/sh`) matches `execvp`. If exact behavior for a process explicitly linked into XPG mode is also required, the shell must likewise be selected from that process mode; globally replacing `_PATH_BSHELL` with `/usr/xpg4/bin/sh` would instead break the default mode.
I suppose here, would we do something similar as done for confstr for __xpg4? Like:
unsafe extern "C" {
static __xpg4: i32;
}From reading illumos' code, I'm assuming __xpg4 is also an int/i32 on Solaris. Is __xpg4 something I could retrieve on all the current Solaris targets that Rust supports?
Or is there a better/correct approach than the above?
To kinda recap how I think it would work in my head, I would modify library/std/src/sys/conf.rs to contain Solaris' mode-sensitive confstr and then expand + modify our current confstr for apple/linux on gnu target envs to solaris (and illumos). I would maybe put the __xpg4 extern value in this file as well.
The get_default_path() function in library/std/src/sys/process/unix/common.rs would just expand the confstr calling branch to Solaris, and then in last_execvpe_error in library/std/src/sys/process/unix/unix.rs, I would have a separate branch for Solaris that would check on __xpg4 to determine whether we'd use _PATH_BSHELL or /usr/xpg4/bin/sh for the first argument in our libc::execve call. In get_shell_argv() since both Solaris and AIX put "sh" as argv[0], I'd just cfg gate it for both to set shell_argv[0] to "sh".
There was a problem hiding this comment.
Yes, for Solaris, reading __xpg4 is the correct way to reproduce the shell selection made by execvp.
I checked the current Solaris 11.4 ON sources. __xpg4 is an int, and it is exported by libc.so.1 in the SUNW_0.8 symbol version with size 4 and the NODIRECT flag. The same libc mapfile is used for both currently supported Solaris targets, sparcv9-sun-solaris and x86_64-pc-solaris. The illumos ABI has the same definition in its libc mapfile.
NODIRECT is important here: the normal definition in libc is 0, while values-xpg4.o (and values-xpg6.o) can interpose a definition that enables the conforming mode for the whole process. Consequently, a private std binding observes the same value as libc's execvp. I would use libc::c_int rather than spelling the Rust representation as i32, for example:
#[cfg(target_os = "solaris")]
fn is_xpg4() -> bool {
unsafe extern "C" {
#[link_name = "__xpg4"]
static XPG4_MODE: libc::c_int;
}
// SAFETY: __xpg4 is an exported process-wide libc data symbol.
unsafe { XPG4_MODE != 0 }
}This can remain a private Solaris implementation detail in std; I do not think it needs to be added to the public rust-lang/libc API.
There are a few details I would change in the proposed layout:
-
The mode-dependent shell is Solaris-specific. Current Solaris uses
_PATH_BSHELLwhen__xpg4 == 0and/usr/xpg4/bin/shotherwise. The illumosexecvpimplementation you linked, however, always uses_PATH_BSHELLforENOEXEC; it uses__xpg4only when constructing the fallbackPATH. -
The executable pathname and
argv[0]need to remain separate. On Solaris the call is conceptually:
execve(shell_path, [c"sh", file, args..].as_ptr(), envp)where shell_path is selected using __xpg4. The current code uses shell_argv[0] both as the pathname passed to execve and as argv[0], so supporting Solaris/AIX's "sh" value requires carrying the shell pathname separately from the argument vector.
-
For Solaris, calling the private base
confstris only the first part of the fallback path.execvpthen appends:/usr/sbinwhen either the real or effective UID is 0, and otherwise appends:so that the current directory is searched last. The UID test must reflect the credentials afterCommand::uidand anypre_execcallback have run. Since allocation is not allowed there, the root and non-root variants could be prepared beforeforkand selected afterwards. -
Illumos should not simply share the Solaris
confstr(_CS_PATH)branch. In the linked illumos implementation,confstr(_CS_PATH)returns the XPG path, whereasexecvpchooses among separate__xpg4/UID-dependent paths. Illumos therefore needs its own fallback-path handling if the goal is to preserve its nativeexecvpbehavior exactly.
So the __xpg4 binding itself is fine for Solaris, but I would keep the Solaris and illumos path/shell cases separate rather than treating them as one confstr implementation.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
b206c99 to
2200bb8
Compare
…ng and refactor code that uses this function
2200bb8 to
c6475f9
Compare
View all comments
This PR fixes the bug described in #156951 where
CommandExt::execmutating the global environment pointer could cause correctness issue with concurrentexeccalls (also this function holds an environment read lock, so it shouldn't cause any writes to the global environment pointer anyways). We do everything withinCommandExt::execviaexecvethanexecvp, which means we have to resolve the path manually.I took reference to what was done in an archived (Feb 2026) upstream mirror of glibc's
execvpandexecvpe. I also took reference to #55359 on how they implemented the concerned portion ofCommandExt::execwithexecve. There's also this OpenBSD libc implementation ofexecvpethat I saw, but I didn't deeply take a look at this one and saw how it differ.Other than that, I'm unsure how to handle the error
ETIMEOUT/TimedOutas glibc breaks parsing through thePATHenvironment variable value and returns the error while #55359 continues parsing.Let me know if you also want me to put some of
execvecode in a helper function or refactor it in other ways.From @joboet comment in #157144 (comment), this table contain platform behavior on the default fallback path with execvp and whether it invokes shell or not on
ENOEXEC.process_spawnpexecvpPATHPATH_DEFPATHposix_spawnpcalls shell onENOEXECexecvpcalls shell onENOEXECCS_PATH=/bin:/usr/bin(same asconfstr(_CS_PATH), but different from_PATH_DEFPATH=/usr/bin:/bin)confstr(_CS_PATH)_PATH_DEFPATH=/usr/bin:/bin(notably, this is different fromconfstr(_CS_PATH)=sysctl(..)=/usr/bin:/bin:/usr/sbin:/sbin)/usr/local/bin:/bin:/usr/bin=_PATH_DEFPATH(not equal toconfstr(_CS_PATH)=/bin:/usr/bin__execvpe_PATH_DEFPATH=/sbin:/bin:/usr/sbin:/usr/bin:(not equal toconfstr(_CS_PATH)=_PATH_STDPATH=/usr/bin:/bin:/usr/sbin:/sbin)execvpe_PATH_DEFPATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin(currently equal toconfstr(_CS_PATH)=_PATH_STDPATH)execvpe_PATH_DEFPATH=/usr/bin:/bin:/usr/pkg/bin:/usr/local/bin(not equal toconfstr(_CS_PATH=sysctl(..)=_PATH_STDPATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/pkg/bin:/usr/pkg/sbin:/usr/local/bin:/usr/local/sbin)See #157144 (comment) for the remaining Unix platforms and their behavior with
_PATH_DEFPATH/_PATH_BSHELL