Files
codex/codex-rs/shell-command/src/shell_snapshot_exports.rs
T
Winston Howes 1530f828cb Preserve complete shell snapshot exports through filtering and replay (#43907)
## Why

Line-based export parsing can truncate multiline values or mistake their contents for declarations. Snapshot replay also needs to restore Bash options before parsing functions that use extended glob syntax.

## What changed

- Capture shell state, aliases, and complete export records with NUL-delimited boundaries, separating capture, credential preparation, and rendering.
- Apply credential and environment policy to whole exports, preserving multiline values and making credential aliases follow allowed overrides.
- Restore Bash `shopt` options before functions, including for Bash running as `sh`.
- Support non-evaluating POSIX `ENV` path expansion and preserve native environment metadata and unset exports.
- Move core and exec-server snapshot consumers to the shared capture parser. Allow capture overhead while enforcing the exec-server's 512 KiB state-and-environment limit before filtering.

## Testing

Add regression coverage for complete capture records, multiline export filtering and replay, credential alias overrides, `ENV` expansion, and `PATH` export state. Extend exec-server tests to cover Bash-backed `sh`, `extglob`, `nocasematch`, `set -u`, and large environments.

GitOrigin-RevId: 7589ab4d137529a395dcee10b5162718de24a621
2026-09-08 20:41:01 +00:00

82 lines
2.2 KiB
Rust

//! Capture native export declarations and unset POSIX exports.
//! Set POSIX values are captured in bulk for quoting in Rust.
use crate::shell_detect::ShellType;
pub(super) fn script(shell_type: ShellType) -> String {
let script = match shell_type {
ShellType::Bash => {
r#"(
while IFS= read -r __codex_snapshot_export_name; do
case "$__codex_snapshot_export_name" in
""|[0-9]*|*[!A-Za-z0-9_]*|PWD|OLDPWD) continue ;;
esac
RECORD_START
declare -xp "$__codex_snapshot_export_name" 2>/dev/null || true
RECORD_END
done < <(compgen -e)
)
"#
}
ShellType::Zsh => {
r#"(
unsetopt rcquotes
# The bundled Zsh does not include the zsh/parameter module.
for __codex_snapshot_export_name in ${(f)"$(typeset +x)"}; do
case "$__codex_snapshot_export_name" in
""|[0-9]*|*[!A-Za-z0-9_]*|PWD|OLDPWD) continue ;;
esac
case "${(tP)__codex_snapshot_export_name}" in
*readonly*) continue ;;
*export*) ;;
*) continue ;;
esac
RECORD_START
typeset -xp "$__codex_snapshot_export_name"
RECORD_END
done
)
"#
}
ShellType::Sh => {
r#"if export -p >/dev/null 2>&1; then
export -p | __codex_snapshot_command awk '
/^(export|declare -x|typeset -x) [A-Za-z_][A-Za-z0-9_]*$/ {
name=$0
sub(/^(export|declare -x|typeset -x) /, "", name)
if (name ~ /^(PWD|OLDPWD)$/) {
next
}
if (!seen[name]++) {
print name
}
}' | while IFS= read -r __codex_snapshot_export_name; do
# Only the validated identifier enters eval. Set values come from the bulk environment.
if eval '[ "${'"$__codex_snapshot_export_name"'+x}" = x ]'; then
continue
fi
# Only preserve an unset export if the native shell confirms it already exists.
if (
set +a
set -- "$__codex_snapshot_export_name" "$(export -p)"
export "$1"
[ "$2" = "$(export -p)" ]
); then
RECORD_START
printf 'export %s\n' "$__codex_snapshot_export_name"
RECORD_END
fi
done
fi
"#
}
ShellType::PowerShell | ShellType::Cmd => return String::new(),
};
script
.replace(
"RECORD_START",
"printf '%s\\0' \"$__codex_snapshot_export_name\"",
)
.replace("RECORD_END", "printf '\\0'")
}