genpac 0.1.0

Sandbox for Gentoo ebuild development using bubblewrap
// Copyright (C) 2023 Gokul Das B
// SPDX-License-Identifier: GPL-3.0-or-later
//! Program execution module
//!
//! This module deals with launching bubblewrap and then configuring the namespace for the child
//! application. Genpac then waits here till bubblewrap exits. This ensures that the terminal
//! remains available to the sandbox and doesn't get taken over by the shell. The exit code returned
//! by the sandbox is then passed as such to the shell.

use super::BWInfo;
use crate::{GlobalsFinal, LogCommand};
use anyhow::{Context, Result as AResult};
use command_fds::CommandFdExt;
use os_pipe::pipe;
use std::io::Write;
use std::os::fd::AsRawFd;
use std::process::{self, Command};

/// Setup bubblewrap and launch command with it
///
/// Based on [bwrap userns python
/// example](https://github.com/containers/bubblewrap/blob/main/demos/userns-block-fd.py).
pub fn launch(args: Vec<String>, globals: &GlobalsFinal) -> AResult<()> {
    // Pipe for getting child info from bwrap
    let (mut rinfo, winfo) = pipe()?;
    let fdinfo = winfo.as_raw_fd();

    // Pipe for blocking creation of namespace
    let (rblock, mut wblock) = pipe()?;
    let fdblock = rblock.as_raw_fd();

    // Arguments for genpac-bwrap coordination
    let pipeargs: Vec<String> = format!("--info-fd {fdinfo} --userns-block-fd {fdblock}")
        .split_whitespace()
        .map(|x| x.to_string())
        .collect();

    // Setup bwrap command with required IPC pipes
    let mut bwrap = Command::new("bwrap");
    bwrap
        .args(pipeargs)
        .args(args)
        .preserved_fds(vec![winfo.into(), rblock.into()]);

    // Log the final command configuration to the user
    bwrap.log("Bubblewrap cmd");

    // Dry run ends here
    if globals.dry_run() {
        process::exit(0);
    }

    // Fork and exec the bwrap process
    let mut child = bwrap.spawn().context("Failed to spawn bwrap")?;

    log::debug!("Spawned bubblewrap. PID: {}", child.id());

    // Get data from json channel
    let info = BWInfo::acquire(&mut rinfo)?;

    log::debug!(
        "Started command. PID: {}. Blocked for ID mapping",
        info.child_pid
    );

    // Setup user namespace <U|G>ID mapping
    let (uidmaps, gidmaps) = globals.idmaps();
    info.map_ids(uidmaps, gidmaps)?;

    log::debug!("Successfully configured userns. Unlocking command");

    // Unblock the bwrap process
    wblock.write_all(b"1")?;
    wblock.flush()?;

    // Wait till sandbox exits. Exit with the same exit code
    let code = child.wait()?.code().unwrap_or_default();
    log::debug!("Bubblewrap exited with code {code}");
    process::exit(code);
}