feat(shell-tool-mcp): add patched zsh build pipeline (#11668)

## Summary
- add `shell-tool-mcp/patches/zsh-exec-wrapper.patch` against upstream
zsh `77045ef899e53b9598bebc5a41db93a548a40ca6`
- add `zsh-linux` and `zsh-darwin` jobs to
`.github/workflows/shell-tool-mcp.yml`
- stage zsh binaries under `artifacts/vendor/<target>/zsh/<variant>/zsh`
- include zsh artifact jobs in `package.needs`
- mark staged zsh binaries executable during packaging

## Notes
- zsh source is cloned from `https://git.code.sf.net/p/zsh/code`
- workflow pins zsh commit `77045ef899e53b9598bebc5a41db93a548a40ca6`
- zsh build runs `./Util/preconfig` before `./configure`

## Validation
- parsed workflow YAML locally (`yaml-ok`)
- validated zsh patch applies cleanly with `git apply --check` on a
fresh zsh clone
This commit is contained in:
Jeremy Rose
2026-02-12 17:34:48 -08:00
committed by GitHub
parent fc073c9c5b
commit 9cf7a07281
10 changed files with 320 additions and 15 deletions

View File

@@ -329,6 +329,212 @@ jobs:
path: artifacts/** path: artifacts/**
if-no-files-found: error if-no-files-found: error
zsh-linux:
name: Build zsh (Linux) - ${{ matrix.variant }} - ${{ matrix.target }}
needs: metadata
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
container:
image: ${{ matrix.image }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
variant: ubuntu-24.04
image: ubuntu:24.04
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
variant: ubuntu-22.04
image: ubuntu:22.04
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
variant: debian-12
image: debian:12
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
variant: debian-11
image: debian:11
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
variant: centos-9
image: quay.io/centos/centos:stream9
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: ubuntu-24.04
image: arm64v8/ubuntu:24.04
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: ubuntu-22.04
image: arm64v8/ubuntu:22.04
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: ubuntu-20.04
image: arm64v8/ubuntu:20.04
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: debian-12
image: arm64v8/debian:12
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: debian-11
image: arm64v8/debian:11
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: centos-9
image: quay.io/centos/centos:stream9
steps:
- name: Install build prerequisites
shell: bash
run: |
set -euo pipefail
if command -v apt-get >/dev/null 2>&1; then
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y git build-essential bison autoconf gettext
elif command -v dnf >/dev/null 2>&1; then
dnf install -y git gcc gcc-c++ make bison autoconf gettext
elif command -v yum >/dev/null 2>&1; then
yum install -y git gcc gcc-c++ make bison autoconf gettext
else
echo "Unsupported package manager in container"
exit 1
fi
- name: Checkout repository
uses: actions/checkout@v6
- name: Build patched zsh
shell: bash
run: |
set -euo pipefail
git clone --depth 1 https://git.code.sf.net/p/zsh/code /tmp/zsh
cd /tmp/zsh
git fetch --depth 1 origin 77045ef899e53b9598bebc5a41db93a548a40ca6
git checkout 77045ef899e53b9598bebc5a41db93a548a40ca6
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/zsh-exec-wrapper.patch"
./Util/preconfig
./configure
cores="$(command -v nproc >/dev/null 2>&1 && nproc || getconf _NPROCESSORS_ONLN)"
make -j"${cores}"
dest="${GITHUB_WORKSPACE}/artifacts/vendor/${{ matrix.target }}/zsh/${{ matrix.variant }}"
mkdir -p "$dest"
cp Src/zsh "$dest/zsh"
- name: Smoke test zsh exec wrapper
shell: bash
run: |
set -euo pipefail
tmpdir="$(mktemp -d)"
cat > "$tmpdir/exec-wrapper" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
: "${CODEX_WRAPPER_LOG:?missing CODEX_WRAPPER_LOG}"
printf '%s\n' "$@" > "$CODEX_WRAPPER_LOG"
file="$1"
shift
if [[ "$#" -eq 0 ]]; then
exec "$file"
fi
arg0="$1"
shift
exec -a "$arg0" "$file" "$@"
EOF
chmod +x "$tmpdir/exec-wrapper"
CODEX_WRAPPER_LOG="$tmpdir/wrapper.log" \
EXEC_WRAPPER="$tmpdir/exec-wrapper" \
/tmp/zsh/Src/zsh -fc '/bin/echo smoke-zsh' > "$tmpdir/stdout.txt"
grep -Fx "smoke-zsh" "$tmpdir/stdout.txt"
grep -Fx "/bin/echo" "$tmpdir/wrapper.log"
- uses: actions/upload-artifact@v6
with:
name: shell-tool-mcp-zsh-${{ matrix.target }}-${{ matrix.variant }}
path: artifacts/**
if-no-files-found: error
zsh-darwin:
name: Build zsh (macOS) - ${{ matrix.variant }} - ${{ matrix.target }}
needs: metadata
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15-xlarge
target: aarch64-apple-darwin
variant: macos-15
- runner: macos-14
target: aarch64-apple-darwin
variant: macos-14
steps:
- name: Install build prerequisites
shell: bash
run: |
set -euo pipefail
if ! command -v autoconf >/dev/null 2>&1; then
brew install autoconf
fi
- name: Checkout repository
uses: actions/checkout@v6
- name: Build patched zsh
shell: bash
run: |
set -euo pipefail
git clone --depth 1 https://git.code.sf.net/p/zsh/code /tmp/zsh
cd /tmp/zsh
git fetch --depth 1 origin 77045ef899e53b9598bebc5a41db93a548a40ca6
git checkout 77045ef899e53b9598bebc5a41db93a548a40ca6
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/zsh-exec-wrapper.patch"
./Util/preconfig
./configure
cores="$(getconf _NPROCESSORS_ONLN)"
make -j"${cores}"
dest="${GITHUB_WORKSPACE}/artifacts/vendor/${{ matrix.target }}/zsh/${{ matrix.variant }}"
mkdir -p "$dest"
cp Src/zsh "$dest/zsh"
- name: Smoke test zsh exec wrapper
shell: bash
run: |
set -euo pipefail
tmpdir="$(mktemp -d)"
cat > "$tmpdir/exec-wrapper" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
: "${CODEX_WRAPPER_LOG:?missing CODEX_WRAPPER_LOG}"
printf '%s\n' "$@" > "$CODEX_WRAPPER_LOG"
file="$1"
shift
if [[ "$#" -eq 0 ]]; then
exec "$file"
fi
arg0="$1"
shift
exec -a "$arg0" "$file" "$@"
EOF
chmod +x "$tmpdir/exec-wrapper"
CODEX_WRAPPER_LOG="$tmpdir/wrapper.log" \
EXEC_WRAPPER="$tmpdir/exec-wrapper" \
/tmp/zsh/Src/zsh -fc '/bin/echo smoke-zsh' > "$tmpdir/stdout.txt"
grep -Fx "smoke-zsh" "$tmpdir/stdout.txt"
grep -Fx "/bin/echo" "$tmpdir/wrapper.log"
- uses: actions/upload-artifact@v6
with:
name: shell-tool-mcp-zsh-${{ matrix.target }}-${{ matrix.variant }}
path: artifacts/**
if-no-files-found: error
package: package:
name: Package npm module name: Package npm module
needs: needs:
@@ -336,6 +542,8 @@ jobs:
- rust-binaries - rust-binaries
- bash-linux - bash-linux
- bash-darwin - bash-darwin
- zsh-linux
- zsh-darwin
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
PACKAGE_VERSION: ${{ needs.metadata.outputs.version }} PACKAGE_VERSION: ${{ needs.metadata.outputs.version }}
@@ -409,7 +617,8 @@ jobs:
chmod +x \ chmod +x \
"$staging"/vendor/*/codex-exec-mcp-server \ "$staging"/vendor/*/codex-exec-mcp-server \
"$staging"/vendor/*/codex-execve-wrapper \ "$staging"/vendor/*/codex-execve-wrapper \
"$staging"/vendor/*/bash/*/bash "$staging"/vendor/*/bash/*/bash \
"$staging"/vendor/*/zsh/*/zsh
- name: Create npm tarball - name: Create npm tarball
shell: bash shell: bash

View File

@@ -2,7 +2,7 @@
This crate contains the code for two executables: This crate contains the code for two executables:
- `codex-exec-mcp-server` is an MCP server that provides a tool named `shell` that runs a shell command inside a sandboxed instance of Bash. Every resulting `execve(2)` call made within Bash is intercepted and run via the executable defined by the `BASH_EXEC_WRAPPER` environment variable within the Bash process. In practice, `BASH_EXEC_WRAPPER` is set to `codex-execve-wrapper`. - `codex-exec-mcp-server` is an MCP server that provides a tool named `shell` that runs a shell command inside a sandboxed shell process. Every resulting `execve(2)` call made within that shell is intercepted and run via the executable defined by the `EXEC_WRAPPER` environment variable within the shell process. In practice, `EXEC_WRAPPER` is set to `codex-execve-wrapper`.
- `codex-execve-wrapper` is the executable that takes the arguments to the `execve(2)` call and "escalates" it to the MCP server via a shared file descriptor (specified by the `CODEX_ESCALATE_SOCKET` environment variable) for consideration. Based on the [Codex `.rules`](https://developers.openai.com/codex/local-config#rules-preview), the MCP server replies with one of: - `codex-execve-wrapper` is the executable that takes the arguments to the `execve(2)` call and "escalates" it to the MCP server via a shared file descriptor (specified by the `CODEX_ESCALATE_SOCKET` environment variable) for consideration. Based on the [Codex `.rules`](https://developers.openai.com/codex/local-config#rules-preview), the MCP server replies with one of:
- `Run`: `codex-execve-wrapper` should invoke `execve(2)` on itself to run the original command within Bash - `Run`: `codex-execve-wrapper` should invoke `execve(2)` on itself to run the original command within Bash
- `Escalate`: forward the file descriptors of the current process to the MCP server so the command can be run faithfully outside the sandbox. Because the MCP server will have the original FDs for `stdout` and `stderr`, it can write those directly. When the process completes, the MCP server forwards the exit code to `codex-execve-wrapper` so that it exits in a consistent manner. - `Escalate`: forward the file descriptors of the current process to the MCP server so the command can be run faithfully outside the sandbox. Because the MCP server will have the original FDs for `stdout` and `stderr`, it can write those directly. When the process completes, the MCP server forwards the exit code to `codex-execve-wrapper` so that it exits in a consistent manner.
@@ -10,7 +10,7 @@ This crate contains the code for two executables:
## Patched Bash ## Patched Bash
We carry a small patch to `execute_cmd.c` (see `patches/bash-exec-wrapper.patch`) that adds support for `BASH_EXEC_WRAPPER`. The original commit message is “add support for BASH_EXEC_WRAPPER” and the patch applies cleanly to `a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b` from https://github.com/bminor/bash. To rebuild manually: We carry a small patch to `execute_cmd.c` (see `patches/bash-exec-wrapper.patch`) that adds support for `EXEC_WRAPPER`. The original commit message is “add support for BASH_EXEC_WRAPPER” and the patch applies cleanly to `a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b` from https://github.com/bminor/bash. To rebuild manually:
```bash ```bash
git clone https://github.com/bminor/bash git clone https://github.com/bminor/bash

View File

@@ -5,11 +5,12 @@ use std::os::fd::OwnedFd;
use anyhow::Context as _; use anyhow::Context as _;
use crate::posix::escalate_protocol::BASH_EXEC_WRAPPER_ENV_VAR;
use crate::posix::escalate_protocol::ESCALATE_SOCKET_ENV_VAR; use crate::posix::escalate_protocol::ESCALATE_SOCKET_ENV_VAR;
use crate::posix::escalate_protocol::EXEC_WRAPPER_ENV_VAR;
use crate::posix::escalate_protocol::EscalateAction; use crate::posix::escalate_protocol::EscalateAction;
use crate::posix::escalate_protocol::EscalateRequest; use crate::posix::escalate_protocol::EscalateRequest;
use crate::posix::escalate_protocol::EscalateResponse; use crate::posix::escalate_protocol::EscalateResponse;
use crate::posix::escalate_protocol::LEGACY_BASH_EXEC_WRAPPER_ENV_VAR;
use crate::posix::escalate_protocol::SuperExecMessage; use crate::posix::escalate_protocol::SuperExecMessage;
use crate::posix::escalate_protocol::SuperExecResult; use crate::posix::escalate_protocol::SuperExecResult;
use crate::posix::socket::AsyncDatagramSocket; use crate::posix::socket::AsyncDatagramSocket;
@@ -38,7 +39,7 @@ pub(crate) async fn run(file: String, argv: Vec<String>) -> anyhow::Result<i32>
.filter(|(k, _)| { .filter(|(k, _)| {
!matches!( !matches!(
k.as_str(), k.as_str(),
ESCALATE_SOCKET_ENV_VAR | BASH_EXEC_WRAPPER_ENV_VAR ESCALATE_SOCKET_ENV_VAR | EXEC_WRAPPER_ENV_VAR | LEGACY_BASH_EXEC_WRAPPER_ENV_VAR
) )
}) })
.collect(); .collect();

View File

@@ -8,8 +8,11 @@ use serde::Serialize;
/// 'exec-server escalate' reads this to find the inherited FD for the escalate socket. /// 'exec-server escalate' reads this to find the inherited FD for the escalate socket.
pub(super) const ESCALATE_SOCKET_ENV_VAR: &str = "CODEX_ESCALATE_SOCKET"; pub(super) const ESCALATE_SOCKET_ENV_VAR: &str = "CODEX_ESCALATE_SOCKET";
/// The patched bash uses this to wrap exec() calls. /// Patched shells use this to wrap exec() calls.
pub(super) const BASH_EXEC_WRAPPER_ENV_VAR: &str = "BASH_EXEC_WRAPPER"; pub(super) const EXEC_WRAPPER_ENV_VAR: &str = "EXEC_WRAPPER";
/// Compatibility alias for older patched bash builds.
pub(super) const LEGACY_BASH_EXEC_WRAPPER_ENV_VAR: &str = "BASH_EXEC_WRAPPER";
/// The client sends this to the server to request an exec() call. /// The client sends this to the server to request an exec() call.
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]

View File

@@ -15,11 +15,12 @@ use codex_core::sandboxing::SandboxPermissions;
use tokio::process::Command; use tokio::process::Command;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use crate::posix::escalate_protocol::BASH_EXEC_WRAPPER_ENV_VAR;
use crate::posix::escalate_protocol::ESCALATE_SOCKET_ENV_VAR; use crate::posix::escalate_protocol::ESCALATE_SOCKET_ENV_VAR;
use crate::posix::escalate_protocol::EXEC_WRAPPER_ENV_VAR;
use crate::posix::escalate_protocol::EscalateAction; use crate::posix::escalate_protocol::EscalateAction;
use crate::posix::escalate_protocol::EscalateRequest; use crate::posix::escalate_protocol::EscalateRequest;
use crate::posix::escalate_protocol::EscalateResponse; use crate::posix::escalate_protocol::EscalateResponse;
use crate::posix::escalate_protocol::LEGACY_BASH_EXEC_WRAPPER_ENV_VAR;
use crate::posix::escalate_protocol::SuperExecMessage; use crate::posix::escalate_protocol::SuperExecMessage;
use crate::posix::escalate_protocol::SuperExecResult; use crate::posix::escalate_protocol::SuperExecResult;
use crate::posix::escalation_policy::EscalationPolicy; use crate::posix::escalation_policy::EscalationPolicy;
@@ -63,7 +64,11 @@ impl EscalateServer {
client_socket.as_raw_fd().to_string(), client_socket.as_raw_fd().to_string(),
); );
env.insert( env.insert(
BASH_EXEC_WRAPPER_ENV_VAR.to_string(), EXEC_WRAPPER_ENV_VAR.to_string(),
self.execve_wrapper.to_string_lossy().to_string(),
);
env.insert(
LEGACY_BASH_EXEC_WRAPPER_ENV_VAR.to_string(),
self.execve_wrapper.to_string_lossy().to_string(), self.execve_wrapper.to_string_lossy().to_string(),
); );

View File

@@ -33,9 +33,6 @@ pub async fn create_transport<P>(
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
let mcp_executable = codex_utils_cargo_bin::cargo_bin("codex-exec-mcp-server")?;
let execve_wrapper = codex_utils_cargo_bin::cargo_bin("codex-execve-wrapper")?;
// `bash` is a test resource rather than a binary target, so we must use // `bash` is a test resource rather than a binary target, so we must use
// `find_resource!` to locate it instead of `cargo_bin()`. // `find_resource!` to locate it instead of `cargo_bin()`.
let bash = find_resource!("../suite/bash")?; let bash = find_resource!("../suite/bash")?;
@@ -51,8 +48,24 @@ where
.await?; .await?;
assert!(status.success(), "dotslash fetch failed: {status:?}"); assert!(status.success(), "dotslash fetch failed: {status:?}");
create_transport_with_shell_path(codex_home, dotslash_cache, bash).await
}
pub async fn create_transport_with_shell_path<P, Q, R>(
codex_home: P,
dotslash_cache: Q,
shell_path: R,
) -> anyhow::Result<TokioChildProcess>
where
P: AsRef<Path>,
Q: AsRef<Path>,
R: AsRef<Path>,
{
let mcp_executable = codex_utils_cargo_bin::cargo_bin("codex-exec-mcp-server")?;
let execve_wrapper = codex_utils_cargo_bin::cargo_bin("codex-execve-wrapper")?;
let transport = TokioChildProcess::new(Command::new(&mcp_executable).configure(|cmd| { let transport = TokioChildProcess::new(Command::new(&mcp_executable).configure(|cmd| {
cmd.arg("--bash").arg(bash); cmd.arg("--bash").arg(shell_path.as_ref());
cmd.arg("--execve").arg(&execve_wrapper); cmd.arg("--execve").arg(&execve_wrapper);
cmd.env("CODEX_HOME", codex_home.as_ref()); cmd.env("CODEX_HOME", codex_home.as_ref());
cmd.env("DOTSLASH_CACHE", dotslash_cache.as_ref()); cmd.env("DOTSLASH_CACHE", dotslash_cache.as_ref());

View File

@@ -10,6 +10,7 @@ use anyhow::ensure;
use codex_exec_server::ExecResult; use codex_exec_server::ExecResult;
use exec_server_test_support::InteractiveClient; use exec_server_test_support::InteractiveClient;
use exec_server_test_support::create_transport; use exec_server_test_support::create_transport;
use exec_server_test_support::create_transport_with_shell_path;
use exec_server_test_support::notify_readable_sandbox; use exec_server_test_support::notify_readable_sandbox;
use exec_server_test_support::write_default_execpolicy; use exec_server_test_support::write_default_execpolicy;
use maplit::hashset; use maplit::hashset;
@@ -54,7 +55,46 @@ prefix_rule(
let dotslash_cache_temp_dir = TempDir::new()?; let dotslash_cache_temp_dir = TempDir::new()?;
let dotslash_cache = dotslash_cache_temp_dir.path(); let dotslash_cache = dotslash_cache_temp_dir.path();
let transport = create_transport(codex_home.as_ref(), dotslash_cache).await?; let transport = create_transport(codex_home.as_ref(), dotslash_cache).await?;
run_accept_elicitation_for_prompt_rule_with_transport(transport).await
}
/// Verify the same prompt/escalation flow works when the server is launched
/// with a patched zsh binary.
///
/// Set CODEX_TEST_ZSH_PATH to enable this test locally or in CI.
#[tokio::test(flavor = "current_thread")]
async fn accept_elicitation_for_prompt_rule_with_zsh() -> Result<()> {
let Some(zsh_path) = std::env::var_os("CODEX_TEST_ZSH_PATH") else {
eprintln!("skipping zsh test: CODEX_TEST_ZSH_PATH is not set");
return Ok(());
};
let zsh_path = PathBuf::from(zsh_path);
let codex_home = TempDir::new()?;
write_default_execpolicy(
r#"
# Create a rule with `decision = "prompt"` to exercise the elicitation flow.
prefix_rule(
pattern = ["git", "init"],
decision = "prompt",
match = [
"git init ."
],
)
"#,
codex_home.as_ref(),
)
.await?;
let dotslash_cache_temp_dir = TempDir::new()?;
let dotslash_cache = dotslash_cache_temp_dir.path();
let transport =
create_transport_with_shell_path(codex_home.as_ref(), dotslash_cache, &zsh_path).await?;
run_accept_elicitation_for_prompt_rule_with_transport(transport).await
}
async fn run_accept_elicitation_for_prompt_rule_with_transport(
transport: rmcp::transport::TokioChildProcess,
) -> Result<()> {
// Create an MCP client that approves expected elicitation messages. // Create an MCP client that approves expected elicitation messages.
let project_root = TempDir::new()?; let project_root = TempDir::new()?;
let project_root_path = project_root.path().canonicalize().unwrap(); let project_root_path = project_root.path().canonicalize().unwrap();

View File

@@ -99,7 +99,7 @@ The Codex harness (used by the CLI and the VS Code extension) sends such request
This package wraps the `codex-exec-mcp-server` binary and its helpers so that the shell MCP can be invoked via `npx -y @openai/codex-shell-tool-mcp`. It bundles: This package wraps the `codex-exec-mcp-server` binary and its helpers so that the shell MCP can be invoked via `npx -y @openai/codex-shell-tool-mcp`. It bundles:
- `codex-exec-mcp-server` and `codex-execve-wrapper` built for macOS (arm64, x64) and Linux (musl arm64, musl x64). - `codex-exec-mcp-server` and `codex-execve-wrapper` built for macOS (arm64, x64) and Linux (musl arm64, musl x64).
- A patched Bash that honors `BASH_EXEC_WRAPPER`, built for multiple glibc baselines (Ubuntu 24.04/22.04/20.04, Debian 12/11, CentOS-like 9) and macOS (15/14/13). - A patched Bash that honors `EXEC_WRAPPER`, built for multiple glibc baselines (Ubuntu 24.04/22.04/20.04, Debian 12/11, CentOS-like 9) and macOS (15/14/13).
- A launcher (`bin/mcp-server.js`) that picks the correct binaries for the current `process.platform` / `process.arch`, specifying `--execve` and `--bash` for the MCP, as appropriate. - A launcher (`bin/mcp-server.js`) that picks the correct binaries for the current `process.platform` / `process.arch`, specifying `--execve` and `--bash` for the MCP, as appropriate.
See [the README in the Codex repo](https://github.com/openai/codex/blob/main/codex-rs/exec-server/README.md) for details. See [the README in the Codex repo](https://github.com/openai/codex/blob/main/codex-rs/exec-server/README.md) for details.

View File

@@ -6,7 +6,7 @@ index 070f5119..d20ad2b9 100644
char sample[HASH_BANG_BUFSIZ]; char sample[HASH_BANG_BUFSIZ];
size_t larray; size_t larray;
+ char* exec_wrapper = getenv("BASH_EXEC_WRAPPER"); + char* exec_wrapper = getenv("EXEC_WRAPPER");
+ if (exec_wrapper && *exec_wrapper && !whitespace (*exec_wrapper)) + if (exec_wrapper && *exec_wrapper && !whitespace (*exec_wrapper))
+ { + {
+ char *orig_command = command; + char *orig_command = command;

View File

@@ -0,0 +1,34 @@
diff --git a/Src/exec.c b/Src/exec.c
index 27bca11..baea760 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -507,7 +507,9 @@ zexecve(char *pth, char **argv, char **newenvp)
{
int eno;
static char buf[PATH_MAX * 2+1];
- char **eep;
+ char **eep, **exec_argv;
+ char *orig_pth = pth;
+ char *exec_wrapper;
unmetafy(pth, NULL);
for (eep = argv; *eep; eep++)
@@ -526,8 +528,17 @@ zexecve(char *pth, char **argv, char **newenvp)
if (newenvp == NULL)
newenvp = environ;
+ exec_argv = argv;
+ if ((exec_wrapper = getenv("EXEC_WRAPPER")) &&
+ *exec_wrapper && !inblank(*exec_wrapper)) {
+ exec_argv = argv - 2;
+ exec_argv[0] = exec_wrapper;
+ exec_argv[1] = orig_pth;
+ pth = exec_wrapper;
+ }
winch_unblock();
- execve(pth, argv, newenvp);
+ execve(pth, exec_argv, newenvp);
+ pth = orig_pth;
/* If the execve returns (which in general shouldn't happen), *
* then check for an errno equal to ENOEXEC. This errno is set *