Download codex-rs/arg0/src/lib.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 28.5 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/arg0/src/lib.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/arg0/src/lib.rs
-
curl -L -o lib.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/arg0/src/lib.rs
28.5 kB
| use std::ffi::OsString; | |
| use std::fs::File; | |
| use std::future::Future; | |
| use std::path::Path; | |
| use std::path::PathBuf; | |
| use codex_apply_patch::CODEX_CORE_APPLY_PATCH_ARG1; | |
| use codex_async_utils::THREAD_STACK_SIZE_BYTES; | |
| use codex_exec_server::CODEX_ARG0_EXEC_HELPER_ARG1; | |
| use codex_exec_server::CODEX_FS_HELPER_ARG1; | |
| use codex_install_context::InstallContext; | |
| use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0; | |
| use codex_utils_home_dir::find_codex_home; | |
| use codex_windows_sandbox::CODEX_WINDOWS_SANDBOX_ARG1; | |
| use std::os::unix::fs::symlink; | |
| use tempfile::TempDir; | |
| const APPLY_PATCH_ARG0: &str = "apply_patch"; | |
| const MISSPELLED_APPLY_PATCH_ARG0: &str = "applypatch"; | |
| const EXECVE_WRAPPER_ARG0: &str = "codex-execve-wrapper"; | |
| const LOCK_FILENAME: &str = ".lock"; | |
| pub struct Arg0DispatchPaths { | |
| /// Stable path to the current Codex executable for child re-execs. | |
| /// | |
| /// Prefer this over [`std::env::current_exe()`] in code that may run under | |
| /// a test harness, where `current_exe()` can point at the harness binary | |
| /// instead of the real Codex CLI. | |
| pub codex_self_exe: Option<PathBuf>, | |
| pub codex_linux_sandbox_exe: Option<PathBuf>, | |
| pub main_execve_wrapper_exe: Option<PathBuf>, | |
| } | |
| /// Keeps the per-session PATH entry alive and locked for the process lifetime. | |
| pub struct Arg0PathEntryGuard { | |
| _temp_dir: TempDir, | |
| _lock_file: File, | |
| paths: Arg0DispatchPaths, | |
| } | |
| impl Arg0PathEntryGuard { | |
| fn new(temp_dir: TempDir, lock_file: File, paths: Arg0DispatchPaths) -> Self { | |
| Self { | |
| _temp_dir: temp_dir, | |
| _lock_file: lock_file, | |
| paths, | |
| } | |
| } | |
| pub fn paths(&self) -> &Arg0DispatchPaths { | |
| &self.paths | |
| } | |
| } | |
| pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> { | |
| // Determine if we were invoked via the special alias. | |
| let mut args = std::env::args_os(); | |
| let argv0 = args.next().unwrap_or_default(); | |
| let exe_name = Path::new(&argv0) | |
| .file_name() | |
| .and_then(|s| s.to_str()) | |
| .unwrap_or(""); | |
| if exe_name == EXECVE_WRAPPER_ARG0 { | |
| let mut args = std::env::args(); | |
| let _ = args.next(); | |
| let file = match args.next() { | |
| Some(file) => file, | |
| None => std::process::exit(1), | |
| }; | |
| let argv = args.collect::<Vec<_>>(); | |
| let runtime = match tokio::runtime::Builder::new_current_thread() | |
| .enable_all() | |
| .build() | |
| { | |
| Ok(runtime) => runtime, | |
| Err(_) => std::process::exit(1), | |
| }; | |
| let exit_code = runtime.block_on( | |
| codex_shell_escalation::run_shell_escalation_execve_wrapper(file, argv), | |
| ); | |
| match exit_code { | |
| Ok(exit_code) => std::process::exit(exit_code), | |
| Err(_) => std::process::exit(1), | |
| } | |
| } | |
| if exe_name == CODEX_LINUX_SANDBOX_ARG0 { | |
| // Safety: [`run_main`] never returns. | |
| codex_linux_sandbox::run_main(); | |
| } else if exe_name == APPLY_PATCH_ARG0 || exe_name == MISSPELLED_APPLY_PATCH_ARG0 { | |
| codex_apply_patch::main(); | |
| } | |
| let argv1 = args.next().unwrap_or_default(); | |
| if argv1 == codex_sandboxing::CODEX_WINDOWS_MXC_ARG1 { | |
| codex_sandboxing::run_windows_mxc_main(); | |
| } | |
| if argv1 == CODEX_ARG0_EXEC_HELPER_ARG1 { | |
| codex_exec_server::run_arg0_exec_helper_main(); | |
| } | |
| if argv1 == CODEX_FS_HELPER_ARG1 { | |
| codex_exec_server::run_fs_helper_main(); | |
| } | |
| if argv1 == CODEX_WINDOWS_SANDBOX_ARG1 { | |
| codex_windows_sandbox::run_windows_sandbox_wrapper_main(); | |
| } | |
| if argv1 == CODEX_CORE_APPLY_PATCH_ARG1 { | |
| let patch_arg = args.next().and_then(|s| s.to_str().map(str::to_owned)); | |
| let exit_code = match patch_arg { | |
| Some(patch_arg) => { | |
| let mut stdout = std::io::stdout(); | |
| let mut stderr = std::io::stderr(); | |
| let cwd = match codex_utils_absolute_path::AbsolutePathBuf::current_dir() { | |
| Ok(cwd) => cwd, | |
| Err(_) => std::process::exit(1), | |
| }; | |
| let runtime = match tokio::runtime::Builder::new_current_thread() | |
| .enable_all() | |
| .build() | |
| { | |
| Ok(runtime) => runtime, | |
| Err(_) => std::process::exit(1), | |
| }; | |
| let cwd = cwd.into(); | |
| let update_file_mode = codex_apply_patch::apply_patch_file_update_mode_from_env(); | |
| match runtime.block_on(codex_apply_patch::apply_patch_with_options( | |
| &patch_arg, | |
| codex_apply_patch::ApplyPatchOptions { | |
| update_file_mode, | |
| ..Default::default() | |
| }, | |
| &cwd, | |
| &mut stdout, | |
| &mut stderr, | |
| codex_exec_server::LOCAL_FS.as_ref(), | |
| /*sandbox*/ None, | |
| )) { | |
| Ok(_) => 0, | |
| Err(_) => 1, | |
| } | |
| } | |
| None => { | |
| eprintln!("Error: {CODEX_CORE_APPLY_PATCH_ARG1} requires a UTF-8 PATCH argument."); | |
| 1 | |
| } | |
| }; | |
| std::process::exit(exit_code); | |
| } | |
| // This modifies the environment, which is not thread-safe, so do this | |
| // before creating any threads/the Tokio runtime. | |
| load_dotenv(); | |
| let (path_entry_guard, updated_path_env_var) = prepare_path_env_var_with_aliases( | |
| InstallContext::current(), | |
| std::env::var_os("PATH"), | |
| prepare_path_entry_for_codex_aliases, | |
| ); | |
| if let Some(updated_path_env_var) = updated_path_env_var { | |
| // It is safe to call set_var() because our process is single-threaded at | |
| // this point in its execution. | |
| unsafe { | |
| std::env::set_var("PATH", updated_path_env_var); | |
| } | |
| } | |
| path_entry_guard | |
| } | |
| fn prepare_path_env_var_with_aliases( | |
| install_context: &InstallContext, | |
| existing_path: Option<OsString>, | |
| prepare_aliases: impl FnOnce(Option<OsString>) -> std::io::Result<(Arg0PathEntryGuard, OsString)>, | |
| ) -> (Option<Arg0PathEntryGuard>, Option<OsString>) { | |
| let package_path = path_env_with_package_path_dir(install_context, existing_path.clone()); | |
| let path_for_aliases = package_path.clone().or(existing_path); | |
| match prepare_aliases(path_for_aliases) { | |
| Ok((path_entry, updated_path_env_var)) => (Some(path_entry), Some(updated_path_env_var)), | |
| Err(err) => { | |
| // It is possible that Codex will proceed successfully even if | |
| // creating helper aliases fails, so warn the user and move on. | |
| eprintln!("WARNING: proceeding, even though we could not create PATH aliases: {err}"); | |
| (None, package_path) | |
| } | |
| } | |
| } | |
| /// While we want to deploy the Codex CLI as a single executable for simplicity, | |
| /// we also want to expose some of its functionality as distinct CLIs, so we use | |
| /// the "arg0 trick" to determine which CLI to dispatch. This effectively allows | |
| /// us to simulate deploying multiple executables as a single binary on Mac and | |
| /// Linux (but not Windows). | |
| /// | |
| /// When the current executable is invoked through the hard-link or alias named | |
| /// `codex-linux-sandbox` we *directly* execute | |
| /// [`codex_linux_sandbox::run_main`] (which never returns). Otherwise we: | |
| /// | |
| /// 1. Load `.env` values from `~/.codex/.env` before creating any threads. | |
| /// 2. Spawn a main runtime thread with a controlled stack size. | |
| /// 3. Construct a Tokio multi-thread runtime. | |
| /// 4. Capture the current executable path and derive the | |
| /// `codex-linux-sandbox` helper path (falling back to the current | |
| /// executable if needed) so children can re-invoke the sandbox when running | |
| /// on Linux. | |
| /// 5. Execute the provided async `main_fn` inside that runtime, forwarding any | |
| /// error. Note that `main_fn` receives [`Arg0DispatchPaths`], which | |
| /// contains the helper executable paths needed to construct | |
| /// [`codex_core::config::Config`]. | |
| /// | |
| /// This function should be used to wrap any `main()` function in binary crates | |
| /// in this workspace that depends on these helper CLIs. | |
| pub fn arg0_dispatch_or_else<F, Fut>(main_fn: F) -> anyhow::Result<()> | |
| where | |
| F: FnOnce(Arg0DispatchPaths) -> Fut + Send + 'static, | |
| Fut: Future<Output = anyhow::Result<()>>, | |
| { | |
| // Retain the TempDir so it exists for the lifetime of the invocation of | |
| // this executable. Admittedly, we could invoke `keep()` on it, but it | |
| // would be nice to avoid leaving temporary directories behind, if possible. | |
| let path_entry_guard = arg0_dispatch(); | |
| let current_exe = std::env::current_exe().ok(); | |
| // Regular invocation. Run the async entry point on a thread with the same | |
| // stack budget as Tokio workers; `Runtime::block_on` otherwise runs the | |
| // top-level future on the caller's OS stack. | |
| let handle = std::thread::Builder::new() | |
| .name("codex-main".to_string()) | |
| .stack_size(THREAD_STACK_SIZE_BYTES) | |
| .spawn(move || { | |
| let runtime = build_runtime()?; | |
| runtime.block_on(run_main_with_arg0_guard( | |
| path_entry_guard, | |
| current_exe, | |
| main_fn, | |
| )) | |
| })?; | |
| match handle.join() { | |
| Ok(result) => result, | |
| Err(payload) => std::panic::resume_unwind(payload), | |
| } | |
| } | |
| async fn run_main_with_arg0_guard<F, Fut>( | |
| path_entry_guard: Option<Arg0PathEntryGuard>, | |
| current_exe: Option<PathBuf>, | |
| main_fn: F, | |
| ) -> anyhow::Result<()> | |
| where | |
| F: FnOnce(Arg0DispatchPaths) -> Fut, | |
| Fut: Future<Output = anyhow::Result<()>>, | |
| { | |
| let paths = Arg0DispatchPaths { | |
| codex_self_exe: current_exe.clone(), | |
| codex_linux_sandbox_exe: if cfg!(target_os = "linux") { | |
| linux_sandbox_exe_path(path_entry_guard.as_ref(), current_exe) | |
| } else { | |
| None | |
| }, | |
| main_execve_wrapper_exe: path_entry_guard | |
| .as_ref() | |
| .and_then(|path_entry| path_entry.paths().main_execve_wrapper_exe.clone()), | |
| }; | |
| let result = main_fn(paths).await; | |
| // Keep the arg0 tempdir guard alive until the async entry point finishes; | |
| // runtime paths above can point at aliases inside that directory. | |
| drop(path_entry_guard); | |
| result | |
| } | |
| fn linux_sandbox_exe_path( | |
| path_entry_guard: Option<&Arg0PathEntryGuard>, | |
| current_exe: Option<PathBuf>, | |
| ) -> Option<PathBuf> { | |
| // Prefer the `codex-linux-sandbox` alias when available so callers can | |
| // re-exec through a path whose basename still triggers arg0 dispatch on | |
| // bubblewrap builds that do not support `--argv0`. | |
| path_entry_guard | |
| .and_then(|path_entry| path_entry.paths().codex_linux_sandbox_exe.clone()) | |
| .or(current_exe) | |
| } | |
| fn build_runtime() -> anyhow::Result<tokio::runtime::Runtime> { | |
| let mut builder = tokio::runtime::Builder::new_multi_thread(); | |
| builder.enable_all(); | |
| builder.thread_stack_size(THREAD_STACK_SIZE_BYTES); | |
| Ok(builder.build()?) | |
| } | |
| const ILLEGAL_ENV_VAR_PREFIX: &str = "CODEX_"; | |
| /// Load env vars from ~/.codex/.env. | |
| /// | |
| /// Security: Do not allow `.env` files to create or modify any variables | |
| /// with names starting with `CODEX_`. | |
| fn load_dotenv() { | |
| if let Ok(codex_home) = find_codex_home() | |
| && let Ok(iter) = dotenvy::from_path_iter(codex_home.join(".env")) | |
| { | |
| set_filtered(iter); | |
| } | |
| } | |
| /// Helper to set vars from a dotenvy iterator while filtering out `CODEX_` keys. | |
| fn set_filtered<I>(iter: I) | |
| where | |
| I: IntoIterator<Item = Result<(String, String), dotenvy::Error>>, | |
| { | |
| for (key, value) in iter.into_iter().flatten() { | |
| if !key.to_ascii_uppercase().starts_with(ILLEGAL_ENV_VAR_PREFIX) { | |
| // It is safe to call set_var() because our process is | |
| // single-threaded at this point in its execution. | |
| unsafe { std::env::set_var(&key, &value) }; | |
| } | |
| } | |
| } | |
| /// Creates a temporary directory with either: | |
| /// | |
| /// - UNIX: `apply_patch` symlink to the current executable | |
| /// - WINDOWS: `apply_patch.bat` batch script to invoke the current executable | |
| /// with the hidden `--codex-run-as-apply-patch` flag. | |
| /// | |
| /// Returns the temporary directory guard and the PATH value that prepends the | |
| /// temporary directory so `apply_patch` can be on the PATH without requiring the | |
| /// user to install a separate executable, simplifying the deployment of Codex | |
| /// CLI. | |
| /// Note: In debug builds the temp-dir guard is disabled to ease local testing. | |
| /// | |
| /// IMPORTANT: Callers must update PATH before multiple threads are spawned. | |
| fn prepare_path_entry_for_codex_aliases( | |
| existing_path: Option<OsString>, | |
| ) -> std::io::Result<(Arg0PathEntryGuard, OsString)> { | |
| let codex_home = find_codex_home()?; | |
| { | |
| // Guard against placing helpers in system temp directories outside debug builds. | |
| let temp_root = std::env::temp_dir(); | |
| if codex_home.starts_with(&temp_root) { | |
| return Err(std::io::Error::new( | |
| std::io::ErrorKind::InvalidInput, | |
| format!( | |
| "Refusing to create helper binaries under temporary dir {temp_root:?} (codex_home: {codex_home:?})" | |
| ), | |
| )); | |
| } | |
| } | |
| std::fs::create_dir_all(&codex_home)?; | |
| // Use a CODEX_HOME-scoped temp root to avoid cluttering the top-level directory. | |
| let temp_root = codex_home.join("tmp").join("arg0"); | |
| std::fs::create_dir_all(&temp_root)?; | |
| { | |
| use std::os::unix::fs::PermissionsExt; | |
| // Ensure only the current user can access the temp directory. | |
| std::fs::set_permissions(&temp_root, std::fs::Permissions::from_mode(0o700))?; | |
| } | |
| // Best-effort cleanup of stale per-session dirs. Ignore failures so startup proceeds. | |
| if let Err(err) = janitor_cleanup(&temp_root) { | |
| eprintln!("WARNING: failed to clean up stale arg0 temp dirs: {err}"); | |
| } | |
| let temp_dir = tempfile::Builder::new() | |
| .prefix("codex-arg0") | |
| .tempdir_in(&temp_root)?; | |
| let path = temp_dir.path(); | |
| let lock_path = path.join(LOCK_FILENAME); | |
| let lock_file = File::options() | |
| .read(true) | |
| .write(true) | |
| .create(true) | |
| .truncate(false) | |
| .open(&lock_path)?; | |
| lock_file.try_lock()?; | |
| for filename in &[ | |
| APPLY_PATCH_ARG0, | |
| MISSPELLED_APPLY_PATCH_ARG0, | |
| CODEX_LINUX_SANDBOX_ARG0, | |
| EXECVE_WRAPPER_ARG0, | |
| ] { | |
| let exe = std::env::current_exe()?; | |
| { | |
| let link = path.join(filename); | |
| symlink(&exe, &link)?; | |
| } | |
| { | |
| let batch_script = path.join(format!("{filename}.bat")); | |
| let exe = windows_batch_executable_path(&exe, path); | |
| std::fs::write( | |
| &batch_script, | |
| format!( | |
| r#"@echo off | |
| "{exe}" {CODEX_CORE_APPLY_PATCH_ARG1} %* | |
| "#, | |
| ), | |
| )?; | |
| } | |
| } | |
| let updated_path_env_var = path_env_with_entry(path, existing_path); | |
| let paths = Arg0DispatchPaths { | |
| codex_self_exe: std::env::current_exe().ok(), | |
| codex_linux_sandbox_exe: { | |
| { | |
| Some(path.join(CODEX_LINUX_SANDBOX_ARG0)) | |
| } | |
| { | |
| None | |
| } | |
| }, | |
| main_execve_wrapper_exe: { | |
| { | |
| Some(path.join(EXECVE_WRAPPER_ARG0)) | |
| } | |
| { | |
| None | |
| } | |
| }, | |
| }; | |
| Ok(( | |
| Arg0PathEntryGuard::new(temp_dir, lock_file, paths), | |
| updated_path_env_var, | |
| )) | |
| } | |
| fn windows_batch_executable_path(executable: &Path, alias_directory: &Path) -> String { | |
| pathdiff::diff_paths(executable, alias_directory) | |
| .filter(|relative_path| relative_path.is_relative()) | |
| .map(|relative_path| format!("%~dp0{}", relative_path.display())) | |
| .unwrap_or_else(|| executable.display().to_string()) | |
| } | |
| fn path_env_with_package_path_dir( | |
| install_context: &InstallContext, | |
| existing_path: Option<OsString>, | |
| ) -> Option<OsString> { | |
| let path_dir = install_context | |
| .package_layout | |
| .as_ref() | |
| .and_then(|package_layout| package_layout.path_dir.as_ref())?; | |
| Some(path_env_with_entry(path_dir.as_path(), existing_path)) | |
| } | |
| fn path_env_with_entry(path_entry: &Path, existing_path: Option<OsString>) -> OsString { | |
| const PATH_SEPARATOR: &str = ":"; | |
| const PATH_SEPARATOR: &str = ";"; | |
| let capacity = path_entry.as_os_str().len() | |
| + existing_path | |
| .as_ref() | |
| .map_or(0, |existing_path| 1 + existing_path.len()); | |
| let mut path_env_var = OsString::with_capacity(capacity); | |
| path_env_var.push(path_entry); | |
| if let Some(existing_path) = existing_path { | |
| path_env_var.push(PATH_SEPARATOR); | |
| path_env_var.push(existing_path); | |
| } | |
| path_env_var | |
| } | |
| fn janitor_cleanup(temp_root: &Path) -> std::io::Result<()> { | |
| let entries = match std::fs::read_dir(temp_root) { | |
| Ok(entries) => entries, | |
| Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()), | |
| Err(err) => return Err(err), | |
| }; | |
| for entry in entries.flatten() { | |
| let path = entry.path(); | |
| if !path.is_dir() { | |
| continue; | |
| } | |
| // Skip the directory if locking fails or the lock is currently held. | |
| let Some(_lock_file) = try_lock_dir(&path)? else { | |
| continue; | |
| }; | |
| match std::fs::remove_dir_all(&path) { | |
| Ok(()) => {} | |
| // Expected TOCTOU race: directory can disappear after read_dir/lock checks. | |
| Err(err) if err.kind() == std::io::ErrorKind::NotFound => continue, | |
| Err(err) => return Err(err), | |
| } | |
| } | |
| Ok(()) | |
| } | |
| fn try_lock_dir(dir: &Path) -> std::io::Result<Option<File>> { | |
| let lock_path = dir.join(LOCK_FILENAME); | |
| let lock_file = match File::options().read(true).write(true).open(&lock_path) { | |
| Ok(file) => file, | |
| Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), | |
| Err(err) => return Err(err), | |
| }; | |
| match lock_file.try_lock() { | |
| Ok(()) => Ok(Some(lock_file)), | |
| Err(std::fs::TryLockError::WouldBlock) => Ok(None), | |
| Err(err) => Err(err.into()), | |
| } | |
| } | |
| mod tests { | |
| use super::Arg0DispatchPaths; | |
| use super::Arg0PathEntryGuard; | |
| use super::LOCK_FILENAME; | |
| use super::janitor_cleanup; | |
| use super::linux_sandbox_exe_path; | |
| use super::run_main_with_arg0_guard; | |
| use anyhow::ensure; | |
| use codex_install_context::CodexPackageLayout; | |
| use codex_install_context::InstallContext; | |
| use codex_install_context::InstallMethod; | |
| use codex_utils_absolute_path::AbsolutePathBuf; | |
| use pretty_assertions::assert_eq; | |
| use std::fs; | |
| use std::fs::File; | |
| use std::os::windows::process::CommandExt; | |
| use std::path::Path; | |
| use std::path::PathBuf; | |
| use tempfile::TempDir; | |
| struct PackagePathTestFixture { | |
| _temp_dir: TempDir, | |
| arg0_dir: PathBuf, | |
| existing_dir: PathBuf, | |
| install_context: InstallContext, | |
| path_dir: AbsolutePathBuf, | |
| } | |
| fn create_lock(dir: &Path) -> std::io::Result<File> { | |
| let lock_path = dir.join(LOCK_FILENAME); | |
| File::options() | |
| .read(true) | |
| .write(true) | |
| .create(true) | |
| .truncate(false) | |
| .open(lock_path) | |
| } | |
| fn package_path_test_fixture() -> anyhow::Result<PackagePathTestFixture> { | |
| let temp_dir = TempDir::new()?; | |
| let arg0_dir = temp_dir.path().join("arg0"); | |
| let package_dir = temp_dir.path().join("package"); | |
| let bin_dir = package_dir.join("bin"); | |
| let path_dir = package_dir.join("codex-path"); | |
| let existing_dir = temp_dir.path().join("existing-bin"); | |
| fs::create_dir_all(&arg0_dir)?; | |
| fs::create_dir_all(&bin_dir)?; | |
| fs::create_dir_all(&path_dir)?; | |
| fs::create_dir_all(&existing_dir)?; | |
| let path_dir = AbsolutePathBuf::from_absolute_path(path_dir.canonicalize()?)?; | |
| let install_context = InstallContext { | |
| method: InstallMethod::Other, | |
| package_layout: Some(CodexPackageLayout { | |
| package_dir: AbsolutePathBuf::from_absolute_path(package_dir.canonicalize()?)?, | |
| bin_dir: AbsolutePathBuf::from_absolute_path(bin_dir.canonicalize()?)?, | |
| resources_dir: None, | |
| path_dir: Some(path_dir.clone()), | |
| }), | |
| }; | |
| Ok(PackagePathTestFixture { | |
| _temp_dir: temp_dir, | |
| arg0_dir, | |
| existing_dir, | |
| install_context, | |
| path_dir, | |
| }) | |
| } | |
| fn windows_batch_alias_preserves_unicode_executable_paths() -> anyhow::Result<()> { | |
| let root = TempDir::new()?; | |
| let profile = root.path().join("用户"); | |
| let alias_directory = profile.join(".codex").join("tmp").join("arg0"); | |
| let executable_directory = profile.join("bin"); | |
| fs::create_dir_all(&alias_directory)?; | |
| fs::create_dir_all(&executable_directory)?; | |
| let system_root = std::env::var_os("SystemRoot") | |
| .ok_or_else(|| anyhow::anyhow!("missing Windows system root"))?; | |
| let command_shell = PathBuf::from(system_root).join("System32").join("cmd.exe"); | |
| let executable = executable_directory.join("cmd.exe"); | |
| fs::copy(&command_shell, &executable)?; | |
| let batch_path = alias_directory.join("apply_patch.bat"); | |
| let executable_path = super::windows_batch_executable_path(&executable, &alias_directory); | |
| fs::write( | |
| &batch_path, | |
| format!("@echo off\r\n\"{executable_path}\" /d /c exit 37\r\n"), | |
| )?; | |
| let output = std::process::Command::new(command_shell) | |
| .args(["/d", "/c"]) | |
| .raw_arg(format!("chcp 437>nul & call \"{}\"", batch_path.display())) | |
| .output()?; | |
| assert_eq!(output.status.code(), Some(37)); | |
| Ok(()) | |
| } | |
| fn windows_batch_alias_preserves_cross_volume_executable_paths() { | |
| assert_eq!( | |
| super::windows_batch_executable_path( | |
| Path::new(r"D:\Tools\codex.exe"), | |
| Path::new(r"C:\Users\person\.codex\tmp\arg0"), | |
| ), | |
| r"D:\Tools\codex.exe", | |
| ); | |
| } | |
| fn linux_sandbox_exe_path_prefers_codex_linux_sandbox_alias() -> std::io::Result<()> { | |
| let temp_dir = TempDir::new()?; | |
| let lock_file = create_lock(temp_dir.path())?; | |
| let alias_path = temp_dir.path().join("codex-linux-sandbox"); | |
| let path_entry = Arg0PathEntryGuard::new( | |
| temp_dir, | |
| lock_file, | |
| Arg0DispatchPaths { | |
| codex_self_exe: Some(PathBuf::from("/usr/bin/codex")), | |
| codex_linux_sandbox_exe: Some(alias_path.clone()), | |
| main_execve_wrapper_exe: None, | |
| }, | |
| ); | |
| assert_eq!( | |
| linux_sandbox_exe_path(Some(&path_entry), Some(PathBuf::from("/usr/bin/codex"))), | |
| Some(alias_path), | |
| ); | |
| Ok(()) | |
| } | |
| fn path_env_can_prepend_package_path_before_arg0_alias_dir() -> anyhow::Result<()> { | |
| let fixture = package_path_test_fixture()?; | |
| let package_path = super::path_env_with_package_path_dir( | |
| &fixture.install_context, | |
| Some(fixture.existing_dir.as_os_str().to_owned()), | |
| ) | |
| .expect("package path dir should update PATH"); | |
| let updated_path = super::path_env_with_entry(&fixture.arg0_dir, Some(package_path)); | |
| assert_eq!( | |
| std::env::split_paths(&updated_path).collect::<Vec<_>>(), | |
| vec![ | |
| fixture.arg0_dir, | |
| fixture.path_dir.as_path().to_path_buf(), | |
| fixture.existing_dir | |
| ], | |
| ); | |
| Ok(()) | |
| } | |
| fn package_path_survives_arg0_alias_setup_failure() -> anyhow::Result<()> { | |
| let fixture = package_path_test_fixture()?; | |
| let (path_entry_guard, updated_path_env_var) = super::prepare_path_env_var_with_aliases( | |
| &fixture.install_context, | |
| Some(fixture.existing_dir.as_os_str().to_owned()), | |
| |path_for_aliases| { | |
| assert_eq!( | |
| std::env::split_paths( | |
| &path_for_aliases.expect("package PATH should be passed to alias setup") | |
| ) | |
| .collect::<Vec<_>>(), | |
| vec![ | |
| fixture.path_dir.as_path().to_path_buf(), | |
| fixture.existing_dir.clone() | |
| ], | |
| ); | |
| Err(std::io::Error::other("alias setup failed")) | |
| }, | |
| ); | |
| assert!(path_entry_guard.is_none()); | |
| let updated_path_env_var = | |
| updated_path_env_var.expect("package PATH should survive alias setup failure"); | |
| assert_eq!( | |
| std::env::split_paths(&updated_path_env_var).collect::<Vec<_>>(), | |
| vec![ | |
| fixture.path_dir.as_path().to_path_buf(), | |
| fixture.existing_dir | |
| ], | |
| ); | |
| Ok(()) | |
| } | |
| fn run_main_with_arg0_guard_keeps_aliases_alive_until_main_returns() -> anyhow::Result<()> { | |
| let temp_dir = TempDir::new()?; | |
| let alias_path = temp_dir.path().join("codex-helper-alias"); | |
| fs::write(&alias_path, b"")?; | |
| let lock_file = create_lock(temp_dir.path())?; | |
| let path_entry = Arg0PathEntryGuard::new( | |
| temp_dir, | |
| lock_file, | |
| Arg0DispatchPaths { | |
| codex_self_exe: Some(PathBuf::from("/usr/bin/codex")), | |
| codex_linux_sandbox_exe: Some(alias_path.clone()), | |
| main_execve_wrapper_exe: Some(alias_path), | |
| }, | |
| ); | |
| super::build_runtime()?.block_on(run_main_with_arg0_guard( | |
| /*path_entry_guard*/ Some(path_entry), | |
| Some(PathBuf::from("/usr/bin/codex")), | |
| |paths| async move { | |
| let alias_path = paths | |
| .codex_linux_sandbox_exe | |
| .or(paths.main_execve_wrapper_exe) | |
| .expect("unix dispatch should create at least one alias path"); | |
| ensure!( | |
| alias_path.exists(), | |
| "alias path disappeared before main future was polled: {}", | |
| alias_path.display() | |
| ); | |
| tokio::task::yield_now().await; | |
| ensure!( | |
| alias_path.exists(), | |
| "alias path disappeared while main future was running: {}", | |
| alias_path.display() | |
| ); | |
| Ok(()) | |
| }, | |
| )) | |
| } | |
| fn janitor_skips_dirs_without_lock_file() -> std::io::Result<()> { | |
| let root = tempfile::tempdir()?; | |
| let dir = root.path().join("no-lock"); | |
| fs::create_dir(&dir)?; | |
| janitor_cleanup(root.path())?; | |
| assert!(dir.exists()); | |
| Ok(()) | |
| } | |
| fn janitor_skips_dirs_with_held_lock() -> std::io::Result<()> { | |
| let root = tempfile::tempdir()?; | |
| let dir = root.path().join("locked"); | |
| fs::create_dir(&dir)?; | |
| let lock_file = create_lock(&dir)?; | |
| lock_file.try_lock()?; | |
| janitor_cleanup(root.path())?; | |
| assert!(dir.exists()); | |
| Ok(()) | |
| } | |
| fn janitor_removes_dirs_with_unlocked_lock() -> std::io::Result<()> { | |
| let root = tempfile::tempdir()?; | |
| let dir = root.path().join("stale"); | |
| fs::create_dir(&dir)?; | |
| create_lock(&dir)?; | |
| janitor_cleanup(root.path())?; | |
| assert!(!dir.exists()); | |
| Ok(()) | |
| } | |
| } | |