use std::io::{IsTerminal, Write};
use clap::Args;
use microsandbox::sandbox::ExecOutput;
#[derive(Debug, Args)]
pub struct ShellArgs {
pub name: String,
#[arg(long)]
pub shell: Option<String>,
#[arg(short = 'u', long)]
pub user: Option<String>,
#[arg(short, long)]
pub quiet: bool,
#[arg(last = true)]
pub command: Vec<String>,
}
const MAX_STDIN_SCRIPT_SIZE: usize = 1024 * 1024;
pub async fn run(args: ShellArgs) -> anyhow::Result<()> {
let sandbox = super::resolve_and_start(&args.name, args.quiet).await?;
let interactive = std::io::stdin().is_terminal();
let shell = args
.shell
.as_deref()
.or(sandbox.config().shell.as_deref())
.unwrap_or("/bin/sh");
if interactive {
let script = if args.command.is_empty() {
None
} else {
Some(args.command.join(" "))
};
let exit_code = if let Some(ref script) = script {
sandbox
.attach_with(shell, |a| {
let mut a = a.args(["-c", script.as_str()]);
if let Some(ref user) = args.user {
a = a.user(user);
}
a
})
.await?
} else {
sandbox
.attach_with(shell, |a| {
let mut a = a;
if let Some(ref user) = args.user {
a = a.user(user);
}
a
})
.await?
};
super::maybe_stop(&sandbox).await;
if exit_code != 0 {
std::process::exit(exit_code);
}
} else {
let script = if args.command.is_empty() {
let buf = tokio::task::spawn_blocking(|| {
use std::io::Read;
let mut buf = Vec::new();
std::io::stdin()
.take(MAX_STDIN_SCRIPT_SIZE as u64)
.read_to_end(&mut buf)?;
String::from_utf8(buf).map_err(|_| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "stdin is not valid UTF-8")
})
})
.await??;
if buf.trim().is_empty() {
super::maybe_stop(&sandbox).await;
return Ok(());
}
buf
} else {
args.command.join(" ")
};
let output: ExecOutput = sandbox
.exec_with(shell, |e| {
let mut e = e.args(["-c", &script]);
if let Some(ref user) = args.user {
e = e.user(user);
}
e
})
.await?;
std::io::stdout().write_all(output.stdout_bytes())?;
std::io::stderr().write_all(output.stderr_bytes())?;
super::maybe_stop(&sandbox).await;
if !output.status().success {
std::process::exit(output.status().code);
}
}
Ok(())
}