use super::prelude::*;
use log;
use std::{io::IsTerminal, path::Path};
const DISTROBOX_ENV: &str = "/run/.containersetupdone";
const CONTAINER_ENV: &str = "/run/.containerenv";
#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Distrobox {
name: String,
}
impl fmt::Display for Distrobox {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "distrobox '{}'", self.name)
}
}
impl Distrobox {
pub fn new(name: Option<String>) -> Result<Self, NewDistroboxError> {
let name = match name {
Some(name) if !name.is_empty() => name,
_ => "my-distrobox".to_string(),
};
let output = std::process::Command::new("podman")
.args(["start", &name])
.output()
.map_err(|e| match e.kind() {
std::io::ErrorKind::NotFound => NewDistroboxError::NeedPodman,
_ => NewDistroboxError::IoError(e),
})?;
if output.status.success() {
Ok(Self { name })
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.starts_with("Error: no container with name or ID")
&& stderr.contains("found: no such container")
{
Err(NewDistroboxError::NonExistent(name))
} else {
Err(NewDistroboxError::Podman(output))
}
}
}
pub fn current() -> Result<Self, CurrentDistroboxError> {
if !detect() {
return Err(CurrentDistroboxError::NotAToolbx);
}
let content = std::fs::read_to_string(CONTAINER_ENV).map_err(|e| {
CurrentDistroboxError::Environment {
env_file: CONTAINER_ENV.to_string(),
source: e,
}
})?;
let name = content
.lines()
.find(|line| line.contains("name=\""))
.ok_or_else(|| CurrentDistroboxError::Name(CONTAINER_ENV.to_string()))?
.trim_start_matches("name=\"")
.trim_end_matches('"');
Ok(Self {
name: name.to_string(),
})
}
}
impl environment::IsEnvironment for Distrobox {
type Err = Error;
fn exists(&self) -> bool {
if detect() {
true
} else if let Environment::Host(host) = environment::current() {
host.execute(crate::environment::cmd!("distrobox", "--version"))
.map(|mut cmd| {
async_std::task::block_on(async move {
cmd.stdout(async_process::Stdio::null())
.stderr(async_process::Stdio::null())
.status()
.await
.map(|status| status.success())
.unwrap_or(false)
})
})
.unwrap_or(false)
} else {
false
}
}
fn execute(&self, command: CommandLine) -> Result<Command, Self::Err> {
log::debug!(
"Executing command '{}' in distrobox '{}'",
command,
self.name
);
let mut cmd: Command;
match environment::current() {
Environment::Distrobox(t) => {
if self == &t {
if command.get_privileged() {
cmd = Command::new("sudo");
if !command.get_interactive() {
cmd.arg("-n");
}
cmd.arg(command.command());
} else {
cmd = Command::new(command.command());
}
cmd.args(command.args());
} else {
return Err(Error::Unimplemented(
"running in a distrobox from another distrobox".to_string(),
));
}
}
Environment::Toolbx(_) => {
return Err(Error::Unimplemented(
"running in a distrobox from a toolbx".to_string(),
));
}
Environment::Host(_) => {
cmd = Command::new("distrobox");
cmd.args(["enter", "--name", &self.name]);
if std::io::stdout().is_terminal() && std::io::stdin().is_terminal() {
cmd.arg("-T");
}
cmd.arg("--");
if command.get_privileged() {
cmd.args(["sudo", "-S", "-E"]);
}
cmd.arg(command.command()).args(command.args());
log::debug!("Calling: {:?}", cmd);
}
#[cfg(test)]
Environment::Mock(_) => unimplemented!(),
}
Ok(cmd)
}
}
pub fn detect() -> bool {
Path::new(DISTROBOX_ENV).exists()
}
#[derive(Debug, ThisError)]
pub enum NewDistroboxError {
#[error("working with toolbx containers requires the 'podman' executable")]
NeedPodman,
#[error("podman exited with non-zero code: {0:#?}")]
Podman(std::process::Output),
#[error("no toolbx with name {0} exists")]
NonExistent(String),
#[error("unknown I/O error occured")]
IoError(#[from] std::io::Error),
#[error("failed to determine default toolbx name")]
UnknownDefault(#[from] DefaultToolbxError),
}
#[derive(Debug, ThisError)]
pub enum CurrentDistroboxError {
#[error("cannot read toolbx info from environment file '{env_file}'")]
Environment {
env_file: String,
source: std::io::Error,
},
#[error("program currently isn't run from a toolbx")]
NotAToolbx,
#[error("failed to read toolbx name from environment file '{0}'")]
Name(String),
}
#[derive(Debug, ThisError)]
pub enum DefaultToolbxError {
#[error("failed to read OS information from '{file}'")]
UnknownOs {
file: String,
source: std::io::Error,
},
#[error("cannot determine OS ID from os-release info")]
Id,
#[error("cannot determine OS VERSION_ID from os-release info")]
VersionId,
}
#[derive(Debug, ThisError)]
pub enum Error {
#[error("cannot determine current working directory")]
UnknownCwd(#[from] std::io::Error),
#[error("not implemented: {0}")]
Unimplemented(String),
}