Skip to content

Commit 4acb5cd

Browse files
committed
chore(zsh): add patched-zsh patch artifact and build script
1 parent 9ecd1e9 commit 4acb5cd

File tree

2 files changed

+325
-5
lines changed

2 files changed

+325
-5
lines changed

scripts/build-patched-zsh.sh

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Build the Codex-patched zsh used by shell-tool-mcp.
5+
#
6+
# Defaults are intentionally override-friendly:
7+
# CODEX_REPO Codex repo root containing shell-tool-mcp/patches
8+
# ZSH_SRC_DIR Working clone location for zsh source
9+
# INSTALL_PREFIX Install prefix for make install
10+
# JOBS Parallel jobs for make
11+
# ZSH_REMOTE Upstream zsh remote
12+
# INSTALL_DOCS Set to 1 to run full `make install` (includes manpages)
13+
14+
ZSH_COMMIT="77045ef899e53b9598bebc5a41db93a548a40ca6"
15+
ZSH_REMOTE="${ZSH_REMOTE:-https://git.code.sf.net/p/zsh/code}"
16+
INSTALL_PREFIX="${INSTALL_PREFIX:-$HOME/.local/codex-zsh-${ZSH_COMMIT:0:7}}"
17+
18+
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
19+
20+
resolve_codex_repo() {
21+
if [[ -n "${CODEX_REPO:-}" ]]; then
22+
printf '%s\n' "$CODEX_REPO"
23+
return
24+
fi
25+
26+
# Most reliable when this script is run from inside the codex repo.
27+
local from_script
28+
from_script="$(cd -- "$SCRIPT_DIR/.." && pwd)"
29+
if [[ -f "$from_script/shell-tool-mcp/patches/zsh-exec-wrapper.patch" ]]; then
30+
printf '%s\n' "$from_script"
31+
return
32+
fi
33+
34+
# Common local workspace layouts.
35+
if [[ -f "$HOME/repos/codex/shell-tool-mcp/patches/zsh-exec-wrapper.patch" ]]; then
36+
printf '%s\n' "$HOME/repos/codex"
37+
return
38+
fi
39+
40+
if [[ -f "$HOME/code/codex/shell-tool-mcp/patches/zsh-exec-wrapper.patch" ]]; then
41+
printf '%s\n' "$HOME/code/codex"
42+
return
43+
fi
44+
45+
echo "Could not locate codex repo. Set CODEX_REPO=/path/to/codex." >&2
46+
exit 1
47+
}
48+
49+
resolve_zsh_src_dir() {
50+
if [[ -n "${ZSH_SRC_DIR:-}" ]]; then
51+
printf '%s\n' "$ZSH_SRC_DIR"
52+
return
53+
fi
54+
55+
if [[ -d "$HOME/repos/zsh/.git" ]]; then
56+
printf '%s\n' "$HOME/repos/zsh"
57+
return
58+
fi
59+
60+
if [[ -d "$HOME/code/zsh/.git" ]]; then
61+
printf '%s\n' "$HOME/code/zsh"
62+
return
63+
fi
64+
65+
# Fallback for users without an existing clone.
66+
printf '%s\n' "$HOME/src/zsh-code"
67+
}
68+
69+
CODEX_REPO="$(resolve_codex_repo)"
70+
ZSH_SRC_DIR="$(resolve_zsh_src_dir)"
71+
PATCH_FILE="$CODEX_REPO/shell-tool-mcp/patches/zsh-exec-wrapper.patch"
72+
73+
if [[ ! -f "$PATCH_FILE" ]]; then
74+
echo "Patch file not found: $PATCH_FILE" >&2
75+
exit 1
76+
fi
77+
78+
if [[ -z "${JOBS:-}" ]]; then
79+
if command -v nproc >/dev/null 2>&1; then
80+
JOBS="$(nproc)"
81+
elif command -v sysctl >/dev/null 2>&1; then
82+
JOBS="$(sysctl -n hw.ncpu)"
83+
else
84+
JOBS=4
85+
fi
86+
fi
87+
88+
mkdir -p "$(dirname -- "$ZSH_SRC_DIR")"
89+
90+
if [[ ! -d "$ZSH_SRC_DIR/.git" ]]; then
91+
git clone "$ZSH_REMOTE" "$ZSH_SRC_DIR"
92+
fi
93+
94+
git -C "$ZSH_SRC_DIR" fetch --depth 1 origin "$ZSH_COMMIT"
95+
git -C "$ZSH_SRC_DIR" checkout --detach -f "$ZSH_COMMIT"
96+
git -C "$ZSH_SRC_DIR" reset --hard "$ZSH_COMMIT"
97+
98+
if git -C "$ZSH_SRC_DIR" apply --reverse --check "$PATCH_FILE" >/dev/null 2>&1; then
99+
echo "Patch already applied: $PATCH_FILE"
100+
else
101+
git -C "$ZSH_SRC_DIR" apply --check "$PATCH_FILE"
102+
git -C "$ZSH_SRC_DIR" apply "$PATCH_FILE"
103+
fi
104+
105+
(
106+
cd "$ZSH_SRC_DIR"
107+
./Util/preconfig
108+
./configure --prefix="$INSTALL_PREFIX"
109+
make -j"$JOBS"
110+
if [[ "${INSTALL_DOCS:-0}" == "1" ]]; then
111+
make install
112+
else
113+
make install.bin
114+
fi
115+
)
116+
117+
cat <<OUT
118+
119+
Built patched zsh successfully.
120+
Binary:
121+
$INSTALL_PREFIX/bin/zsh
122+
123+
Quick checks:
124+
$INSTALL_PREFIX/bin/zsh --version
125+
$INSTALL_PREFIX/bin/zsh -fc '/bin/echo smoke-zsh'
126+
OUT
Lines changed: 199 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,228 @@
11
diff --git a/Src/exec.c b/Src/exec.c
2-
index 27bca11..baea760 100644
2+
index 27bca110c..4e5000be3 100644
33
--- a/Src/exec.c
44
+++ b/Src/exec.c
5-
@@ -507,7 +507,9 @@ zexecve(char *pth, char **argv, char **newenvp)
5+
@@ -507,7 +507,12 @@ zexecve(char *pth, char **argv, char **newenvp)
66
{
77
int eno;
88
static char buf[PATH_MAX * 2+1];
99
- char **eep;
10-
+ char **eep, **exec_argv;
10+
+ char **eep, **exec_argv, **wrapper_envp;
1111
+ char *orig_pth = pth;
1212
+ char *exec_wrapper;
13+
+ char wrapper_origin_buf[64];
14+
+ int wrapper_origin_len;
15+
+ int wrapper_envc, wrapper_origin_idx;
1316

1417
unmetafy(pth, NULL);
1518
for (eep = argv; *eep; eep++)
16-
@@ -526,8 +528,17 @@ zexecve(char *pth, char **argv, char **newenvp)
19+
@@ -526,8 +531,51 @@ zexecve(char *pth, char **argv, char **newenvp)
1720

1821
if (newenvp == NULL)
1922
newenvp = environ;
2023
+ exec_argv = argv;
24+
+ /* Use the command env by default; wrapper mode may replace this with an
25+
+ * explicitly rebuilt envp so origin metadata is guaranteed to be present
26+
+ * even when execve does not use the process-global environ. */
27+
+ wrapper_envp = newenvp;
2128
+ if ((exec_wrapper = getenv("EXEC_WRAPPER")) &&
2229
+ *exec_wrapper && !inblank(*exec_wrapper)) {
30+
+ /* zexecve callers provide spare argv slots before argv[0] for
31+
+ * interpreter dispatch; reuse those slots to trampoline through the
32+
+ * wrapper binary while preserving the original target path. */
2333
+ exec_argv = argv - 2;
2434
+ exec_argv[0] = exec_wrapper;
2535
+ exec_argv[1] = orig_pth;
36+
+ wrapper_origin_len = sprintf(wrapper_origin_buf,
37+
+ "CODEX_ZSH_EXEC_BRIDGE_WRAPPER_ORIGIN=%d",
38+
+ zsh_exec_wrapper_origin);
39+
+ if (wrapper_origin_len > 0 &&
40+
+ wrapper_origin_len < (int)sizeof(wrapper_origin_buf)) {
41+
+ wrapper_envc = 0;
42+
+ wrapper_origin_idx = -1;
43+
+ for (eep = newenvp; *eep; eep++) {
44+
+ if (strncmp(*eep, "CODEX_ZSH_EXEC_BRIDGE_WRAPPER_ORIGIN=",
45+
+ sizeof("CODEX_ZSH_EXEC_BRIDGE_WRAPPER_ORIGIN=") - 1) == 0)
46+
+ wrapper_origin_idx = wrapper_envc;
47+
+ wrapper_envc++;
48+
+ }
49+
+ wrapper_envp = zalloc((wrapper_envc + 2) * sizeof(char *));
50+
+ if (wrapper_origin_idx >= 0) {
51+
+ for (wrapper_envc = 0; newenvp[wrapper_envc]; wrapper_envc++)
52+
+ wrapper_envp[wrapper_envc] =
53+
+ wrapper_envc == wrapper_origin_idx ?
54+
+ wrapper_origin_buf : newenvp[wrapper_envc];
55+
+ wrapper_envp[wrapper_envc] = NULL;
56+
+ } else {
57+
+ for (wrapper_envc = 0; newenvp[wrapper_envc]; wrapper_envc++)
58+
+ wrapper_envp[wrapper_envc] = newenvp[wrapper_envc];
59+
+ wrapper_envp[wrapper_envc++] = wrapper_origin_buf;
60+
+ wrapper_envp[wrapper_envc] = NULL;
61+
+ }
62+
+ }
2663
+ pth = exec_wrapper;
2764
+ }
2865
winch_unblock();
2966
- execve(pth, argv, newenvp);
30-
+ execve(pth, exec_argv, newenvp);
67+
+ execve(pth, exec_argv, wrapper_envp);
3168
+ pth = orig_pth;
3269

3370
/* If the execve returns (which in general shouldn't happen), *
3471
* then check for an errno equal to ENOEXEC. This errno is set *
72+
diff --git a/Src/init.c b/Src/init.c
73+
index 20b8cc7fd..b6d5c9c9a 100644
74+
--- a/Src/init.c
75+
+++ b/Src/init.c
76+
@@ -59,6 +59,9 @@ int underscoreused;
77+
/**/
78+
int sourcelevel;
79+
80+
+/**/
81+
+int zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_USER_COMMAND;
82+
+
83+
/* the shell tty fd */
84+
85+
/**/
86+
@@ -1450,14 +1453,25 @@ init_signals(void)
87+
void
88+
run_init_scripts(void)
89+
{
90+
+ int old_origin;
91+
+
92+
noerrexit = NOERREXIT_EXIT | NOERREXIT_RETURN | NOERREXIT_SIGNAL;
93+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_USER_COMMAND;
94+
95+
if (EMULATION(EMULATE_KSH|EMULATE_SH)) {
96+
- if (islogin)
97+
+ if (islogin) {
98+
+ old_origin = zsh_exec_wrapper_origin;
99+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
100+
source("/etc/profile");
101+
+ zsh_exec_wrapper_origin = old_origin;
102+
+ }
103+
if (unset(PRIVILEGED)) {
104+
- if (islogin)
105+
+ if (islogin) {
106+
+ old_origin = zsh_exec_wrapper_origin;
107+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
108+
sourcehome(".profile");
109+
+ zsh_exec_wrapper_origin = old_origin;
110+
+ }
111+
112+
if (interact) {
113+
noerrs = 2;
114+
@@ -1467,16 +1481,26 @@ run_init_scripts(void)
115+
if (!parsestr(&s)) {
116+
singsub(&s);
117+
noerrs = 0;
118+
+ old_origin = zsh_exec_wrapper_origin;
119+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_RC_STARTUP;
120+
source(s);
121+
+ zsh_exec_wrapper_origin = old_origin;
122+
}
123+
}
124+
noerrs = 0;
125+
}
126+
- } else
127+
+ } else {
128+
+ old_origin = zsh_exec_wrapper_origin;
129+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
130+
source("/etc/suid_profile");
131+
+ zsh_exec_wrapper_origin = old_origin;
132+
+ }
133+
} else {
134+
#ifdef GLOBAL_ZSHENV
135+
+ old_origin = zsh_exec_wrapper_origin;
136+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_RC_STARTUP;
137+
source(GLOBAL_ZSHENV);
138+
+ zsh_exec_wrapper_origin = old_origin;
139+
#endif
140+
141+
if (isset(RCS) && unset(PRIVILEGED))
142+
@@ -1492,33 +1516,61 @@ run_init_scripts(void)
143+
}
144+
}
145+
146+
+ old_origin = zsh_exec_wrapper_origin;
147+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_RC_STARTUP;
148+
sourcehome(".zshenv");
149+
+ zsh_exec_wrapper_origin = old_origin;
150+
}
151+
if (islogin) {
152+
#ifdef GLOBAL_ZPROFILE
153+
- if (isset(RCS) && isset(GLOBALRCS))
154+
+ if (isset(RCS) && isset(GLOBALRCS)) {
155+
+ old_origin = zsh_exec_wrapper_origin;
156+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
157+
source(GLOBAL_ZPROFILE);
158+
+ zsh_exec_wrapper_origin = old_origin;
159+
+ }
160+
#endif
161+
- if (isset(RCS) && unset(PRIVILEGED))
162+
+ if (isset(RCS) && unset(PRIVILEGED)) {
163+
+ old_origin = zsh_exec_wrapper_origin;
164+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
165+
sourcehome(".zprofile");
166+
+ zsh_exec_wrapper_origin = old_origin;
167+
+ }
168+
}
169+
if (interact) {
170+
#ifdef GLOBAL_ZSHRC
171+
- if (isset(RCS) && isset(GLOBALRCS))
172+
+ if (isset(RCS) && isset(GLOBALRCS)) {
173+
+ old_origin = zsh_exec_wrapper_origin;
174+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_RC_STARTUP;
175+
source(GLOBAL_ZSHRC);
176+
+ zsh_exec_wrapper_origin = old_origin;
177+
+ }
178+
#endif
179+
- if (isset(RCS) && unset(PRIVILEGED))
180+
+ if (isset(RCS) && unset(PRIVILEGED)) {
181+
+ old_origin = zsh_exec_wrapper_origin;
182+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_RC_STARTUP;
183+
sourcehome(".zshrc");
184+
+ zsh_exec_wrapper_origin = old_origin;
185+
+ }
186+
}
187+
if (islogin) {
188+
#ifdef GLOBAL_ZLOGIN
189+
- if (isset(RCS) && isset(GLOBALRCS))
190+
+ if (isset(RCS) && isset(GLOBALRCS)) {
191+
+ old_origin = zsh_exec_wrapper_origin;
192+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
193+
source(GLOBAL_ZLOGIN);
194+
+ zsh_exec_wrapper_origin = old_origin;
195+
+ }
196+
#endif
197+
- if (isset(RCS) && unset(PRIVILEGED))
198+
+ if (isset(RCS) && unset(PRIVILEGED)) {
199+
+ old_origin = zsh_exec_wrapper_origin;
200+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
201+
sourcehome(".zlogin");
202+
+ zsh_exec_wrapper_origin = old_origin;
203+
+ }
204+
}
205+
}
206+
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_USER_COMMAND;
207+
noerrexit = 0;
208+
nohistsave = 0;
209+
}
210+
diff --git a/Src/zsh.h b/Src/zsh.h
211+
index 5bda04e88..c0750cf45 100644
212+
--- a/Src/zsh.h
213+
+++ b/Src/zsh.h
214+
@@ -2215,6 +2215,14 @@ enum source_return {
215+
SOURCE_ERROR = 2
216+
};
217+
218+
+enum exec_wrapper_origin {
219+
+ EXEC_WRAPPER_ORIGIN_USER_COMMAND = 0,
220+
+ EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP = 1,
221+
+ EXEC_WRAPPER_ORIGIN_RC_STARTUP = 2
222+
+};
223+
+
224+
+extern int zsh_exec_wrapper_origin;
225+
+
226+
enum noerrexit_bits {
227+
/* Suppress ERR_EXIT and traps: global */
228+
NOERREXIT_EXIT = 1,

0 commit comments

Comments
 (0)