use std::ffi::OsString;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
#[cfg(target_os = "macos")]
use super::jail::resolve_with_policy;
#[cfg(not(target_os = "macos"))]
use super::jail::validate_policy;
use super::jail::{Backend, JailPolicy, RealEnv, ResolveEnv, select_jailer};
#[cfg(target_os = "macos")]
use super::pretooluse::REASON_PROFILE_WRITE_FAILED;
#[cfg(target_os = "macos")]
use super::pretooluse::write_seatbelt_profile;
#[cfg(not(target_os = "macos"))]
use super::pretooluse::{REASON_NO_BWRAP, have_bwrap};
const CMD_JAIL_PREFIX: &str = "jail-prefix";
const ARGV_NUL_DELIM: u8 = 0;
const REASON_EXTRA_RW_UNRESOLVABLE: &str = "extra-rw-unresolvable";
#[cfg(not(target_os = "macos"))]
const REASON_DIR_UNRESOLVABLE: &str = "dir-unresolvable";
#[cfg(not(target_os = "macos"))]
const REASON_UNSAFE_EXTRA_RW: &str = "unsafe-extra-rw";
const REASON_NO_BACKEND: &str = "no-jail-backend";
#[cfg(target_os = "macos")]
const REASON_MAIN_ROOT_REQUIRED: &str = "main-root-required";
pub(crate) fn run_jail_prefix(
dir: &Path,
main_root: Option<&Path>,
out: &Path,
network: bool,
extra_rw: &[PathBuf],
) -> anyhow::Result<()> {
let env = RealEnv {
main_root: main_root.map(Path::to_path_buf).unwrap_or_default(),
};
let mut extra_rw_canon = Vec::with_capacity(extra_rw.len());
for grant in extra_rw {
let real = env.realpath(grant).map_err(|_e| {
anyhow::anyhow!(
"{CMD_JAIL_PREFIX}: {REASON_EXTRA_RW_UNRESOLVABLE}: {}",
grant.display()
)
})?;
extra_rw_canon.push(real);
}
let policy = JailPolicy {
extra_rw: extra_rw_canon,
network,
};
let prefix = resolve_prefix(dir, main_root, &policy, &env)?;
write_prefix(out, &prefix)
}
#[cfg(not(target_os = "macos"))]
fn resolve_prefix(
dir: &Path,
_main_root: Option<&Path>,
policy: &JailPolicy,
env: &RealEnv,
) -> anyhow::Result<Vec<OsString>> {
let dir_real = env.realpath(dir).map_err(|_e| {
anyhow::anyhow!(
"{CMD_JAIL_PREFIX}: {REASON_DIR_UNRESOLVABLE}: {}",
dir.display()
)
})?;
validate_policy(policy, &dir_real)
.map_err(|e| anyhow::anyhow!("{CMD_JAIL_PREFIX}: {REASON_UNSAFE_EXTRA_RW}: {e:?}"))?;
if !have_bwrap() {
anyhow::bail!("{CMD_JAIL_PREFIX}: {REASON_NO_BWRAP}");
}
let jailer = select_jailer(&Backend::Bwrap)
.ok_or_else(|| anyhow::anyhow!("{CMD_JAIL_PREFIX}: {REASON_NO_BACKEND}"))?;
Ok(jailer.wrap_argv(&dir_real, policy))
}
#[cfg(target_os = "macos")]
fn resolve_prefix(
dir: &Path,
main_root: Option<&Path>,
policy: &JailPolicy,
env: &RealEnv,
) -> anyhow::Result<Vec<OsString>> {
let main_root = main_root
.ok_or_else(|| anyhow::anyhow!("{CMD_JAIL_PREFIX}: {REASON_MAIN_ROOT_REQUIRED}"))?;
let topo = env
.worktree_topology(dir)
.map_err(|e| anyhow::anyhow!("{CMD_JAIL_PREFIX}: {}", e.reason()))?;
let resolved = resolve_with_policy(policy, &topo, main_root, env)
.map_err(|e| anyhow::anyhow!("{CMD_JAIL_PREFIX}: {}", e.reason()))?;
write_seatbelt_profile(&resolved)
.map_err(|_e| anyhow::anyhow!("{CMD_JAIL_PREFIX}: {REASON_PROFILE_WRITE_FAILED}"))?;
let jailer = select_jailer(&Backend::Seatbelt(resolved.clone()))
.ok_or_else(|| anyhow::anyhow!("{CMD_JAIL_PREFIX}: {REASON_NO_BACKEND}"))?;
Ok(jailer.wrap_argv(&resolved.wt, policy))
}
fn write_prefix(out: &Path, prefix: &[OsString]) -> anyhow::Result<()> {
let mut buf: Vec<u8> = Vec::new();
for (i, tok) in prefix.iter().enumerate() {
if i > 0 {
buf.push(ARGV_NUL_DELIM);
}
buf.extend_from_slice(tok.as_bytes());
}
#[expect(clippy::disallowed_methods, reason = "runtime jail-prefix argv sink")]
std::fs::write(out, &buf)
.map_err(|e| anyhow::anyhow!("{CMD_JAIL_PREFIX}: write {}: {e}", out.display()))?;
Ok(())
}