Add in-memory shell snapshots to unified exec (#39957)

## What changed

- Add the under-development `shell_snapshot_v2` feature and advertise executor support through environment capabilities.
- Use executor-managed, in-memory snapshots for eligible direct `bash`, `zsh`, and `sh` login commands, while applying the configured shell environment policy and avoiding duplicate automatic startup-file effects.
- Keep file-backed snapshots available for user-shell commands and fall back to the existing execution path when in-memory snapshots are unsupported or inapplicable.

## Testing

- Cover policy filtering, snapshot reuse without snapshot files, automatic startup files, local and remote execution, and legacy user-shell snapshots.

GitOrigin-RevId: 4ad6cdf13824913ac8c393ba38b9844230677579
This commit is contained in:
jif
2026-08-21 15:24:56 +00:00
committed by copyberry
parent d446960657
commit 3882ced09c
17 changed files with 299 additions and 34 deletions
+10 -2
View File
@@ -172,9 +172,17 @@ impl ShellSnapshotCache {
.collect::<String>();
let state_variables = state_variables.join(" ");
let shell_start = prepared.command.len() - params.argv.len();
prepared.command[shell_start + 1] = "-c".to_string();
// Automatic startup files run before the restoration script and could
// reintroduce environment variables that the snapshot already filtered.
let (shell_flag, startup) = match shell_type {
ShellType::Bash => ("-pc", "set +o privileged\n"),
ShellType::Zsh => ("-fc", "setopt RCS\n"),
ShellType::Sh => ("-c", ""),
ShellType::PowerShell | ShellType::Cmd => unreachable!(),
};
prepared.command[shell_start + 1] = shell_flag.to_string();
prepared.command[shell_start + 2] = format!(
"if ! eval \"unset {state_variables}\n{state_expansion}\" >/dev/null; then printf 'failed to restore shell snapshot\\n' >&2; fi\n{}",
"{startup}if ! eval \"unset {state_variables}\n{state_expansion}\" >/dev/null; then printf 'failed to restore shell snapshot\\n' >&2; fi\n{}",
params.argv[2]
);
+33 -7
View File
@@ -103,15 +103,25 @@ async fn create_process_context(use_remote: bool) -> Result<ProcessContext> {
}
#[cfg(unix)]
#[test_case(false, false, false, "bash"; "local_pipe")]
#[test_case(false, true, false, "bash"; "local_tty")]
#[test_case(true, false, false, "bash"; "remote_pipe")]
#[test_case(true, true, false, "bash"; "remote_tty")]
#[test_case(true, false, true, "bash"; "remote_sandbox")]
#[test_case(false, false, false, "sh"; "local_sh_pipe")]
#[test_case(false, false, false, false, "bash"; "local_pipe")]
#[test_case(false, true, false, false, "bash"; "local_tty")]
#[test_case(true, false, false, false, "bash"; "remote_pipe")]
#[test_case(true, true, false, false, "bash"; "remote_tty")]
#[test_case(true, false, true, false, "bash"; "remote_sandbox")]
#[test_case(false, false, false, false, "sh"; "local_sh_pipe")]
#[test_case(false, false, false, true, "bash"; "local_bash_env")]
#[test_case(true, false, false, true, "bash"; "remote_bash_env")]
#[cfg_attr(
target_os = "macos",
test_case(false, false, false, "zsh"; "local_zsh_pipe")
test_case(false, false, false, false, "zsh"; "local_zsh_pipe")
)]
#[cfg_attr(
target_os = "macos",
test_case(false, false, false, true, "zsh"; "local_zshenv")
)]
#[cfg_attr(
target_os = "macos",
test_case(true, false, false, true, "zsh"; "remote_zshenv")
)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
// Serialize tests that launch a real exec-server process through the full CLI.
@@ -120,6 +130,7 @@ async fn shell_snapshot_v2_filters_profile_exports_and_stays_in_memory(
use_remote: bool,
tty: bool,
use_sandbox: bool,
automatic_startup: bool,
shell_name: &str,
) -> Result<()> {
if use_sandbox
@@ -133,8 +144,10 @@ async fn shell_snapshot_v2_filters_profile_exports_and_stays_in_memory(
let home = TempDir::new()?;
let cwd = PathUri::from_host_native_path(home.path())?;
let (shell_path, profile_name) = match shell_name {
"bash" if automatic_startup => ("/bin/bash", ".bash-env"),
"bash" => ("/bin/bash", ".bashrc"),
"sh" => ("/bin/sh", ".snapshot-env"),
"zsh" if automatic_startup => ("/bin/zsh", ".zshenv"),
"zsh" => ("/bin/zsh", ".zshrc"),
name => anyhow::bail!("unsupported test shell {name}"),
};
@@ -160,6 +173,12 @@ async fn shell_snapshot_v2_filters_profile_exports_and_stays_in_memory(
"printf x >> \"$HOME/captures\"\nexport PATH=\"$HOME/profile-bin:/usr/bin:/bin\"\nexport PROFILE_ALLOWED=profile\nexport PROFILE_SECRET=secret\nexport PROFILE_DENIED=denied\nprofile_helper() {{ printf helper; }}\n{shadowed_builtins}{padding}"
),
)?;
if shell_name == "zsh" && automatic_startup {
std::fs::write(
home.path().join(".zshrc"),
"export PATH=\"$HOME/profile-bin:/usr/bin:/bin\"\n",
)?;
}
let mut configured_environment = HashMap::from([(
"HOME".to_string(),
home.path().to_string_lossy().into_owned(),
@@ -170,12 +189,19 @@ async fn shell_snapshot_v2_filters_profile_exports_and_stays_in_memory(
profile_path.to_string_lossy().into_owned(),
);
}
if shell_name == "bash" && automatic_startup {
configured_environment.insert(
"BASH_ENV".to_string(),
profile_path.to_string_lossy().into_owned(),
);
}
let policy = ExecEnvPolicy {
inherit: ShellEnvironmentPolicyInherit::All,
ignore_default_excludes: false,
exclude: vec!["PROFILE_DENIED".to_string()],
r#set: configured_environment,
include_only: vec![
"BASH_ENV".to_string(),
"ENV".to_string(),
"HOME".to_string(),
"PATH".to_string(),