use std::future::Future; use std::path::Path; use std::path::PathBuf; use codex_core::CODEX_APPLY_PATCH_ARG1; #[cfg(unix)] use std::os::unix::fs::symlink; use tempfile::TempDir; const LINUX_SANDBOX_ARG0: &str = "codex-linux-sandbox"; const APPLY_PATCH_ARG0: &str = "apply_patch"; const MISSPELLED_APPLY_PATCH_ARG0: &str = "applypatch"; /// 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. Use [`dotenvy::from_path`] and [`dotenvy::dotenv`] to modify the /// environment before creating any threads. /// 2. Construct a Tokio multi-thread runtime. /// 3. Derive the path to the current executable (so children can re-invoke the /// sandbox) when running on Linux. /// 4. Execute the provided async `main_fn` inside that runtime, forwarding any /// error. Note that `main_fn` receives `codex_linux_sandbox_exe: /// Option`, as an argument, which is generally needed as part of /// constructing [`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(main_fn: F) -> anyhow::Result<()> where F: FnOnce(Option) -> Fut, Fut: Future>, { // 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 == 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_APPLY_PATCH_ARG1 { let patch_arg = args.next().and_then(|s| s.to_str().map(|s| s.to_owned())); let exit_code = match patch_arg { Some(patch_arg) => { let mut stdout = std::io::stdout(); let mut stderr = std::io::stderr(); match codex_apply_patch::apply_patch(&patch_arg, &mut stdout, &mut stderr) { Ok(()) => 0, Err(_) => 1, } } None => { eprintln!("Error: {CODEX_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(); // 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 = match prepend_path_entry_for_apply_patch() { Ok(path_entry) => Some(path_entry), Err(err) => { // It is possible that Codex will proceed successfully even if // updating the PATH fails, so warn the user and move on. eprintln!("WARNING: proceeding, even though we could not update PATH: {err}"); None } }; // Regular invocation – create a Tokio runtime and execute the provided // async entry-point. let runtime = tokio::runtime::Runtime::new()?; runtime.block_on(async move { let codex_linux_sandbox_exe: Option = if cfg!(target_os = "linux") { std::env::current_exe().ok() } else { None }; main_fn(codex_linux_sandbox_exe).await }) } const ILLEGAL_ENV_VAR_PREFIX: &str = "CODEX_"; /// Load env vars from ~/.codex/.env and `$(pwd)/.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) = codex_core::config::find_codex_home() && let Ok(iter) = dotenvy::from_path_iter(codex_home.join(".env")) { set_filtered(iter); } if let Ok(iter) = dotenvy::dotenv_iter() { set_filtered(iter); } } /// Helper to set vars from a dotenvy iterator while filtering out `CODEX_` keys. fn set_filtered(iter: I) where I: IntoIterator>, { 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 "secret" --codex-run-as-apply-patch flag. /// /// This temporary directory is prepended to the PATH environment variable so /// that `apply_patch` can be on the PATH without requiring the user to /// install a separate `apply_patch` executable, simplifying the deployment of /// Codex CLI. /// /// IMPORTANT: This function modifies the PATH environment variable, so it MUST /// be called before multiple threads are spawned. fn prepend_path_entry_for_apply_patch() -> std::io::Result { let temp_dir = TempDir::new()?; let path = temp_dir.path(); for filename in &[APPLY_PATCH_ARG0, MISSPELLED_APPLY_PATCH_ARG0] { let exe = std::env::current_exe()?; #[cfg(unix)] { let link = path.join(filename); symlink(&exe, &link)?; } #[cfg(windows)] { let batch_script = path.join(format!("{filename}.bat")); std::fs::write( &batch_script, format!( r#"@echo off "{}" {CODEX_APPLY_PATCH_ARG1} %* "#, exe.display() ), )?; } } #[cfg(unix)] const PATH_SEPARATOR: &str = ":"; #[cfg(windows)] const PATH_SEPARATOR: &str = ";"; let path_element = path.display(); let updated_path_env_var = match std::env::var("PATH") { Ok(existing_path) => { format!("{path_element}{PATH_SEPARATOR}{existing_path}") } Err(_) => { format!("{path_element}") } }; unsafe { std::env::set_var("PATH", updated_path_env_var); } Ok(temp_dir) }