#![cfg(target_os = "linux")]
use color_eyre::eyre;
use color_eyre::Result;
use libc::{c_int, c_void, prctl, PR_CAPBSET_DROP, sethostname};
use log::{debug, trace, warn};
use nix::sched::{unshare, CloneFlags};
use nix::unistd::{fork, geteuid, getuid, getgid, ForkResult, Gid, Uid, Pid};
use std::fs;
use std::os::fd::OwnedFd;
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
use std::ptr;
use std::panic::Location;
use crate::dirs;
use crate::mount::*;
use crate::models::{IsolateMode, ProcessCreationConfig, UnifiedChildContext, NamespaceStrategy};
use crate::run::RunOptions;
use crate::idmap::{IdMapSync, check_user_namespace_support, execute_idmap_for_parent, wait_for_idmap_sync};
fn convert_host_path_to_guest_path(host_path: &Path, env_root: &Path) -> PathBuf {
if let Ok(stripped) = host_path.strip_prefix(env_root) {
Path::new("/").join(stripped)
} else {
host_path.to_path_buf()
}
}
fn unshare_namespaces_with_idmap(
clone_flags: CloneFlags,
uid: Uid,
gid: Gid,
opt_user: &Option<String>,
allow_setgroups: bool,
) -> Result<()> {
if let Err(e) = check_user_namespace_support() {
warn!("User namespace check failed: {}", e);
}
debug!("unshare_namespaces_with_idmap called: clone_flags={:?}, contains CLONE_NEWUSER={}",
clone_flags, clone_flags.contains(CloneFlags::CLONE_NEWUSER));
if clone_flags.contains(CloneFlags::CLONE_NEWUSER) {
debug!("unshare_namespaces_with_idmap: calling unshare_with_user_ns_and_idmap");
unshare_with_user_ns_and_idmap(clone_flags, uid, gid, opt_user, allow_setgroups)
} else {
debug!("unshare_namespaces_with_idmap: calling unshare_namespaces_simple");
unshare_namespaces_simple(clone_flags)
}
}
fn unshare_with_user_ns_and_idmap(
clone_flags: CloneFlags,
uid: Uid,
gid: Gid,
opt_user: &Option<String>,
allow_setgroups: bool,
) -> Result<()> {
use nix::unistd::{pipe, read, write};
let (unshare_read, unshare_write) = pipe()?;
let (idmap_read, idmap_write) = pipe()?;
const SYNC_BYTE: u8 = 0x69;
match unsafe { fork() } {
Ok(ForkResult::Parent { child }) => {
drop(unshare_read);
drop(idmap_write);
unshare_with_error_handling(clone_flags)?;
trace!("Parent: successfully created namespaces");
set_mount_propagation_private_if_needed(clone_flags)?;
write(&unshare_write, &[SYNC_BYTE])?;
trace!("Parent: signaled helper to write ID maps");
let mut buf = [0u8; 1];
read(&idmap_read, &mut buf)?;
if buf[0] != SYNC_BYTE {
return Err(eyre::eyre!("Invalid sync byte from helper"));
}
trace!("Parent: ID mapping completed");
drop(unshare_write);
drop(idmap_read);
match nix::sys::wait::waitpid(child, None) {
Ok(nix::sys::wait::WaitStatus::Exited(_, 0)) => Ok(()),
Ok(status) => Err(eyre::eyre!("ID mapping helper failed: {:?}", status)),
Err(e) => Err(eyre::eyre!("Failed to wait for helper: {}", e)),
}
}
Ok(ForkResult::Child) => {
drop(unshare_write);
drop(idmap_read);
let mut buf = [0u8; 1];
read(&unshare_read, &mut buf)?;
if buf[0] != SYNC_BYTE {
std::process::exit(1);
}
trace!("Helper: parent signaled unshare complete, writing ID maps");
match execute_idmap_for_parent(uid, gid, opt_user, allow_setgroups) {
Ok(()) => {
let _ = write(&idmap_write, &[SYNC_BYTE]);
drop(unshare_read);
drop(idmap_write);
std::process::exit(0);
}
Err(e) => {
warn!("Helper: ID mapping failed: {}", e);
std::process::exit(1);
}
}
}
Err(e) => Err(eyre::eyre!("Failed to fork helper: {}", e)),
}
}
fn set_mount_propagation_private_if_needed(clone_flags: CloneFlags) -> Result<()> {
debug!("set_mount_propagation_private_if_needed called: clone_flags={:?}, contains CLONE_NEWNS={}",
clone_flags, clone_flags.contains(CloneFlags::CLONE_NEWNS));
if clone_flags.contains(CloneFlags::CLONE_NEWNS) {
use nix::mount::{mount, MsFlags};
let flags = MsFlags::MS_REC | MsFlags::MS_PRIVATE | MsFlags::from_bits_truncate(libc::MS_SILENT);
debug!("Setting mount propagation to private with flags: {:?}", flags);
mount(Some("none"), "/", Some(""), flags, Some(""))
.map_err(|e| eyre::eyre!("Failed to set private mount propagation immediately after unshare: {}", e))?;
debug!("Set mount propagation to private immediately after creating mount namespace");
} else {
debug!("SKIP set mount propagation to private (CLONE_NEWNS not in clone_flags)");
}
Ok(())
}
fn unshare_namespaces_simple(clone_flags: CloneFlags) -> Result<()> {
debug!("unshare_namespaces_simple called: clone_flags={:?}", clone_flags);
unshare_with_error_handling(clone_flags)?;
debug!("Successfully created namespaces via unshare");
set_mount_propagation_private_if_needed(clone_flags)?;
Ok(())
}
fn basic_namespace_flags() -> CloneFlags {
let mut flags = CloneFlags::CLONE_NEWNS;
if !geteuid().is_root() {
flags |= CloneFlags::CLONE_NEWUSER;
}
flags
}
fn full_namespace_flags() -> CloneFlags {
let mut flags = basic_namespace_flags() | CloneFlags::CLONE_NEWPID;
flags |= CloneFlags::CLONE_NEWUTS | CloneFlags::CLONE_NEWIPC | CloneFlags::CLONE_NEWNET;
flags |= CloneFlags::CLONE_NEWCGROUP;
flags
}
fn needs_uid_mapping(namespace_flags: CloneFlags) -> bool {
namespace_flags.contains(CloneFlags::CLONE_NEWUSER) && !geteuid().is_root()
}
pub fn determine_process_config(env_root: &Path, run_options: &RunOptions) -> ProcessCreationConfig {
use crate::models::{IsolateMode, NamespaceStrategy, ProcessCreationConfig};
let is_brew_at_prefix = crate::run::is_brew_environment(env_root) && is_env_at_homebrew_prefix(env_root);
if is_brew_at_prefix {
log::debug!("Brew environment at HOMEBREW_PREFIX, skipping namespace isolation");
}
let isolate_mode = run_options.effective_sandbox.isolate_mode.unwrap_or(IsolateMode::Env);
let skip_namespace_isolation = run_options.skip_namespace_isolation || is_brew_at_prefix;
let namespace_strategy = if skip_namespace_isolation {
NamespaceStrategy::Unshare
} else {
run_options.effective_sandbox.namespace_strategy.unwrap_or(NamespaceStrategy::Clone)
};
log::debug!("determine_process_config: isolate_mode={:?}, skip_namespace_isolation={}, namespace_strategy={:?}",
isolate_mode, skip_namespace_isolation, namespace_strategy);
let namespace_flags = if skip_namespace_isolation {
CloneFlags::empty()
} else {
match (isolate_mode, namespace_strategy) {
(IsolateMode::Fs, NamespaceStrategy::Clone) => {
full_namespace_flags()
}
(_, NamespaceStrategy::Clone) => {
basic_namespace_flags()
}
(_, NamespaceStrategy::Unshare) => {
basic_namespace_flags()
}
}
};
let needs_uid_mapping = needs_uid_mapping(namespace_flags);
let mut mount_spec_strings = Vec::new();
let mut effective_isolate_mode = isolate_mode;
let is_brew_env = crate::run::is_brew_environment(env_root);
if !run_options.skip_namespace_isolation {
match isolate_mode {
IsolateMode::Env => {
match env_mount_spec_strings(env_root, run_options, is_brew_env) {
Some(specs) => mount_spec_strings.extend(specs),
None => {
log::warn!("Env mode not available for brew environment, falling back to Fs mode");
effective_isolate_mode = IsolateMode::Fs;
mount_spec_strings.extend(fs_mount_spec_strings(env_root, is_brew_env));
}
}
}
IsolateMode::Fs => mount_spec_strings.extend(fs_mount_spec_strings(env_root, is_brew_env)),
IsolateMode::Vm => {
mount_spec_strings.push("make-rprivate://".to_string());
mount_spec_strings.extend(crate::mount_specs::build_vm_mount_policy(run_options));
}
}
if effective_isolate_mode != IsolateMode::Vm {
mount_spec_strings.extend(run_options.effective_sandbox.mount_specs.iter().cloned());
}
if effective_isolate_mode != IsolateMode::Vm && !run_options.chdir_to_env_root {
if let Ok(cwd) = std::env::current_dir() {
let cwd_str = cwd.to_string_lossy();
if cwd.is_absolute() && cwd.exists() {
trace!("{:?} mode: adding bind mount for cwd: {}", effective_isolate_mode, cwd_str);
mount_spec_strings.push(format!("{}://{}", cwd_str, cwd_str));
}
}
}
}
let mut working_dir = None;
if effective_isolate_mode != IsolateMode::Vm && !run_options.chdir_to_env_root {
if let Ok(cwd) = std::env::current_dir() {
if cwd.is_absolute() && cwd.exists() {
working_dir = Some(cwd);
}
}
}
ProcessCreationConfig {
namespace_strategy,
isolate_mode: effective_isolate_mode,
namespace_flags,
needs_uid_mapping,
mount_spec_strings,
working_dir,
}
}
pub fn build_unified_context(
env_root: &Path,
run_options: &RunOptions,
config: &ProcessCreationConfig,
command: PathBuf,
args: Vec<String>,
stdin_read_fd: Option<i32>,
vm_daemon_ready_fd: Option<OwnedFd>,
) -> Result<UnifiedChildContext> {
let uid = getuid();
let gid = getgid();
let euid = geteuid();
let user = run_options.user.clone();
let mount_specs = crate::mount::parse_mount_specs(
&config.mount_spec_strings.iter().map(|s| s.as_str()).collect::<Vec<_>>()
);
let mut run_options = run_options.clone();
run_options.host_uid = Some(uid.as_raw());
run_options.host_gid = Some(gid.as_raw());
run_options.working_dir = config.working_dir.clone();
let is_brew_env = crate::run::is_brew_environment(env_root);
if is_brew_env {
let usr_local_bin_path = env_root.join("usr/local/bin");
let bin_path = env_root.join("bin");
let ebin_path = env_root.join("ebin");
let current_path = run_options.env_vars.get("PATH")
.cloned()
.unwrap_or_else(|| std::env::var("PATH").unwrap_or_default());
let homebrew_prefix = crate::brew_pkg::prefix::preferred_path();
let mut new_path = String::new();
if usr_local_bin_path.exists() {
new_path.push_str(&format!("{}/usr/local/bin:", homebrew_prefix.display()));
debug!("Brew: Added usr/local/bin to PATH: {}/usr/local/bin", homebrew_prefix.display());
}
if bin_path.exists() {
new_path.push_str(&format!("{}/bin:", homebrew_prefix.display()));
debug!("Brew: Added bin to PATH: {}/bin", homebrew_prefix.display());
}
if ebin_path.exists() {
new_path.push_str(&format!("{}/ebin:", homebrew_prefix.display()));
debug!("Brew: Added ebin to PATH: {}/ebin", homebrew_prefix.display());
}
let env_root_str = env_root.to_string_lossy();
let converted_path = current_path.replace(&*env_root_str, &homebrew_prefix.to_string_lossy().to_string());
new_path.push_str(&converted_path);
debug!("Brew: PATH with system paths preserved: {}", new_path);
run_options.env_vars.insert("PATH".to_string(), new_path);
}
Ok(UnifiedChildContext {
env_root: env_root.to_path_buf(),
run_options,
command,
args,
stdin_read_fd,
isolate_mode: config.isolate_mode,
is_brew_env,
sync_read_fd: None,
mount_specs,
uid,
gid,
euid,
user,
vm_socket_path: None,
vm_daemon_ready_fd,
})
}
pub fn create_process_with_namespaces(
config: &ProcessCreationConfig,
context: UnifiedChildContext,
) -> Result<Pid> {
let id_sync = if config.needs_uid_mapping {
let target_pid = if config.namespace_strategy == NamespaceStrategy::Clone {
Pid::from_raw(0)
} else {
nix::unistd::getpid()
};
Some(IdMapSync::new(target_pid)?)
} else {
None
};
let mut context = context;
if let Some(ref sync) = id_sync {
context.sync_read_fd = Some(sync.read_fd().try_clone()?);
}
match config.namespace_strategy {
NamespaceStrategy::Unshare => {
log::debug!("create_process_with_namespaces: using Unshare strategy");
create_process_via_unshare(config, context, id_sync)
}
NamespaceStrategy::Clone => {
log::debug!("create_process_with_namespaces: using Clone strategy");
create_process_via_clone(config, context, id_sync)
}
}
}
fn create_process_via_clone(
config: &ProcessCreationConfig,
context: UnifiedChildContext,
mut id_sync: Option<IdMapSync>,
) -> Result<Pid> {
const STACK_SIZE: usize = 1024 * 1024;
let stack = vec![0u8; STACK_SIZE];
let stack_top = unsafe { stack.as_ptr().add(STACK_SIZE) as *mut c_void };
let raw_flags = config.namespace_flags.bits() as u64 | libc::SIGCHLD as u64;
let uid = context.uid;
let gid = context.gid;
let user = context.user.clone();
let context_ptr = Box::into_raw(Box::new(context));
unsafe {
let pid = libc::clone(
unified_child_main as extern "C" fn(*mut c_void) -> c_int,
stack_top,
raw_flags as c_int,
context_ptr as *mut c_void,
ptr::null_mut::<c_int>(),
ptr::null_mut::<c_int>(),
ptr::null_mut::<c_int>(),
);
if pid < 0 {
drop(Box::from_raw(context_ptr));
return Err(eyre::eyre!(
"Failed to clone process: {}",
std::io::Error::last_os_error()
));
}
let child_pid = Pid::from_raw(pid);
if let Some(ref mut sync) = id_sync {
sync.set_target_pid(child_pid);
let allow_setgroups = config.isolate_mode == IsolateMode::Vm;
sync.perform_mapping_and_signal(uid, gid, &user, allow_setgroups)?;
}
Ok(child_pid)
}
}
fn create_process_via_unshare(
config: &ProcessCreationConfig,
context: UnifiedChildContext,
_id_sync: Option<IdMapSync>,
) -> Result<Pid> {
let clone_flags = config.namespace_flags;
if !clone_flags.is_empty() {
let allow_setgroups = config.isolate_mode == IsolateMode::Vm;
unshare_namespaces_with_idmap(clone_flags, context.uid, context.gid, &context.user, allow_setgroups)?;
}
let context = context;
child_mount_and_exec(Box::new(context))?;
Ok(Pid::from_raw(0))
}
extern "C" fn unified_child_main(arg: *mut c_void) -> c_int {
unsafe {
let context = Box::from_raw(arg as *mut UnifiedChildContext);
match child_setup_with_namespaces(context) {
Ok(()) => 0,
Err(e) => {
debug!(
"Failed in child setup: {} (sandbox pivot_root/mount/exec path)",
e
);
eprintln!("Failed in child setup: {}", e);
1
}
}
}
}
fn child_setup_with_namespaces(context: Box<UnifiedChildContext>) -> Result<()> {
if let Some(ref sync_fd) = context.sync_read_fd {
wait_for_idmap_sync(sync_fd)?;
trace!("Child: parent completed ID mapping");
}
child_mount_and_exec(context)
}
fn ensure_mount_propagation_private() -> Result<()> {
use nix::mount::{mount, MsFlags};
use nix::errno::Errno;
let flags = MsFlags::MS_REC | MsFlags::MS_PRIVATE | MsFlags::from_bits_truncate(libc::MS_SILENT);
debug!("ensure_mount_propagation_private: attempting to set private propagation with flags: {:?}", flags);
match mount(Some("none"), "/", Some(""), flags, Some("")) {
Ok(()) => {
Ok(())
}
Err(e) => {
match e {
Errno::EINVAL => {
debug!("ensure_mount_propagation_private: mount() returned EINVAL, assuming already in private propagation or invalid flags");
Ok(())
}
Errno::EPERM => {
warn!("ensure_mount_propagation_private: failed with EPERM - cannot set private propagation, mount operations may leak!");
Ok(())
}
Errno::EACCES => {
warn!("ensure_mount_propagation_private: failed with EACCES - cannot access mount point");
Err(eyre::eyre!("Cannot ensure private mount propagation: EACCES"))
}
_ => {
warn!("ensure_mount_propagation_private: failed to set private propagation: {} (error: {:?})", e, e);
Err(eyre::eyre!("Cannot ensure private mount propagation: {}", e))
}
}
}
}
}
fn child_mount_and_exec(mut context: Box<UnifiedChildContext>) -> Result<()> {
ensure_mount_propagation_private()?;
crate::mount::mount_batch_specs(&context.mount_specs, &context.env_root, context.isolate_mode)?;
setup_isolate_mode(&mut context)?;
if context.isolate_mode == IsolateMode::Vm {
return Ok(());
}
prepare_and_execute_command(
&context.command,
&context.args,
&context.run_options.env_vars,
context.run_options.chdir_to_env_root,
)
}
fn setup_isolate_mode(context: &mut UnifiedChildContext) -> Result<()> {
match context.isolate_mode {
IsolateMode::Env => Ok(()),
IsolateMode::Fs => setup_fs_sandbox(context),
IsolateMode::Vm => setup_vm_sandbox(context),
}
}
fn setup_fs_sandbox(context: &mut UnifiedChildContext) -> Result<()> {
perform_fs_sandbox_tasks(context)?;
if let Some(ref working_dir) = context.run_options.working_dir {
trace!("Fs sandbox: restoring working directory to {}", working_dir.display());
if let Err(e) = std::env::set_current_dir(working_dir) {
warn!("Failed to restore working directory to {}: {}. Continuing with /.", working_dir.display(), e);
}
}
let guest_command = convert_host_path_to_guest_path(&context.command, &context.env_root);
if guest_command != context.command {
trace!("Fs sandbox: adjusting command path from {} to {} after pivot", context.command.display(), guest_command.display());
context.command = guest_command;
}
if context.is_brew_env {
let cmd_path = PathBuf::from(&context.command);
debug!("setup_fs_sandbox: attempting to resolve brew command symlink: {}", cmd_path.display());
match std::fs::canonicalize(&cmd_path) {
Ok(resolved) => {
if resolved != cmd_path {
debug!("Fs sandbox: resolved brew command symlink from {} to {}", cmd_path.display(), resolved.display());
context.command = resolved;
} else {
debug!("Fs sandbox: command path unchanged after canonicalize: {}", cmd_path.display());
}
}
Err(e) => {
debug!("Fs sandbox: failed to canonicalize command path {}: {}", cmd_path.display(), e);
}
}
}
Ok(())
}
fn setup_vm_sandbox(context: &UnifiedChildContext) -> Result<()> {
let guest_command = convert_host_path_to_guest_path(&context.command, &context.env_root);
crate::vmm::try_vmm_backends(
&context.env_root,
&context.run_options,
&guest_command,
context.vm_socket_path.as_deref(),
&context.run_options.vmm_order,
context.vm_daemon_ready_fd.as_ref(),
)
}
fn perform_fs_sandbox_tasks(context: &UnifiedChildContext) -> Result<()> {
let oldroot = context.env_root.join("oldroot");
fs::create_dir_all(&oldroot)
.map_err(|e| eyre::eyre!("Failed to create oldroot directory: {}", e))?;
setup_sandbox_dev_tree(&context.env_root)?;
pivot_into_sandbox_and_drop_caps(&context.env_root, &oldroot, context.euid)?;
Ok(())
}
fn drop_all_capabilities() {
for cap in 0..=40 {
unsafe {
let _ = prctl(PR_CAPBSET_DROP, cap as libc::c_ulong, 0, 0, 0);
}
}
trace!("Dropped all capability bounding set entries");
}
fn setup_sandbox_dev_tree(env_root: &Path) -> Result<()> {
crate::mount::ensure_dev_symlinks(&env_root.join("dev"))
}
fn pivot_into_sandbox_and_drop_caps(new_root_base: &Path, oldroot: &Path, _euid: Uid) -> Result<()> {
unsafe {
let hostname = b"sandbox\0";
if sethostname(hostname.as_ptr() as *const _, hostname.len() - 1) < 0 {
warn!("Failed to set hostname: {}. Continuing.", std::io::Error::last_os_error());
}
}
pivot_to_sandbox(new_root_base, oldroot)?;
match unshare_with_error_handling(CloneFlags::CLONE_NEWUSER) {
Ok(()) => {
debug!("Clone child: entered nested user namespace (dropped capabilities)");
drop_all_capabilities();
}
Err(e) => warn!("Failed to create nested user namespace after pivot: {}. Continuing.", e),
}
Ok(())
}
fn prepare_and_execute_command(command: &Path, args: &[String], env_vars: &std::collections::HashMap<String, String>, chdir_to_env_root: bool) -> Result<()> {
if chdir_to_env_root {
if let Err(e) = std::env::set_current_dir("/") {
return Err(eyre::eyre!("Failed to change dir to /: {}", e));
}
}
let mut env_vars = env_vars.clone();
env_vars.insert("LC_ALL".to_string(), "C".to_string());
env_vars.insert("LANG".to_string(), "C".to_string());
env_vars.insert("LC_CTYPE".to_string(), "C".to_string());
env_vars.insert("LC_COLLATE".to_string(), "C".to_string());
debug!("Clone child executing: {} {:?}", command.display(), args);
let err = std::process::Command::new(command)
.args(args)
.envs(&env_vars)
.exec();
Err(eyre::eyre!("Failed to execute command: {}", err))
}
fn env_mount_spec_strings(env_root: &Path, _run_options: &RunOptions, is_brew_env: bool) -> Option<Vec<String>> {
use nix::unistd::{getuid, geteuid};
let uid = getuid();
let euid = geteuid();
let mut specs = Vec::new();
if is_brew_env {
let homebrew_prefix = crate::brew_pkg::prefix::preferred();
let hb_prefix_path = Path::new(homebrew_prefix);
if !hb_prefix_path.exists() {
log::warn!("HOMEBREW_PREFIX directory {} does not exist. Falling back to IsolateMode::Fs for brew environment.", homebrew_prefix);
return None;
}
specs.push("make-rprivate://".to_string());
specs.push(format!("{}://{}", env_root.display(), homebrew_prefix.trim_start_matches('/')));
log::debug!("Brew environment: mounting {} to {}", env_root.display(), homebrew_prefix);
return Some(specs);
}
specs.push("make-rprivate://".to_string());
match crate::mount::mount_traditional_host_compatibility(env_root) {
Ok(mut cspecs) => specs.append(&mut cspecs),
Err(e) => warn!("Failed to generate traditional layout compatibility mounts: {}", e),
}
match crate::mount::mount_opt_epkg_isolation(euid, uid, env_root) {
Ok(mut ospecs) => specs.append(&mut ospecs),
Err(e) => warn!("Failed to generate /opt/epkg isolation mounts: {}", e),
}
specs.extend(crate::mount::MOUNT_SPECS_ENV.iter().map(|s| s.to_string()));
let home_epkg_path = dirs().home_epkg.display().to_string();
let home_epkg_mount_spec = format!("{}://{}", home_epkg_path, home_epkg_path);
debug!("env_mount_spec_strings: adding home_epkg mount spec: {}", home_epkg_mount_spec);
specs.push(home_epkg_mount_spec);
if !uid.is_root() {
specs.push("@/root://root".to_string());
}
specs.push("/etc/hosts://etc/hosts:try".to_string());
specs.push("/etc/resolv.conf://etc/resolv.conf:try".to_string());
Some(specs)
}
fn is_env_at_homebrew_prefix(env_root: &Path) -> bool {
let homebrew_prefix = crate::brew_pkg::prefix::preferred();
let hb_path = std::path::Path::new(homebrew_prefix);
match env_root.canonicalize() {
Ok(canonical_env) => {
match hb_path.canonicalize() {
Ok(canonical_hb) => canonical_env == canonical_hb,
Err(_) => env_root == hb_path,
}
}
Err(_) => env_root == hb_path,
}
}
fn fs_mount_spec_strings(env_root: &Path, is_brew_env: bool) -> Vec<String> {
let mut specs: Vec<String> = Vec::new();
specs.insert(0, "make-rprivate://:silent".to_string());
specs.extend(crate::mount::pseudo_fs_mount_spec_strings().iter().map(|s| s.to_string()));
if is_brew_env {
specs.push(format!("{}:ro,try", dirs().opt_epkg.display()));
crate::mount_specs::add_epkg_bin_dir_mount(&mut specs);
} else {
crate::mount_specs::add_epkg_mount_specs(&mut specs);
}
if is_brew_env {
let homebrew_prefix = crate::brew_pkg::prefix::preferred();
let hb_inside_env = env_root.join(homebrew_prefix.trim_start_matches('/'));
if hb_inside_env.exists() {
log::debug!("HOMEBREW_PREFIX symlinks already exist at {}", hb_inside_env.display());
} else {
log::warn!("HOMEBREW_PREFIX symlinks missing at {} (environment may be corrupted)", hb_inside_env.display());
}
}
specs
}
#[track_caller]
fn unshare_with_error_handling(clone_flags: CloneFlags) -> Result<()> {
unshare(clone_flags).map_err(|e| {
let location = Location::caller();
eyre::eyre!("unshare() failed at {}:{}: {}: {}", location.file(), location.line(), e, e.desc())
})
}