cnf_lib/environment/
distrobox.rs1use super::prelude::*;
13
14use std::{io::IsTerminal, path::Path};
15
16const DISTROBOX_ENV: &str = "/run/.containersetupdone";
17const CONTAINER_ENV: &str = "/run/.containerenv";
18
19#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Serialize, Deserialize)]
20pub struct Distrobox {
21 name: String,
22}
23
24impl fmt::Display for Distrobox {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 write!(f, "distrobox '{}'", self.name)
27 }
28}
29
30impl Distrobox {
31 pub fn new(name: Option<String>) -> Result<Self, NewDistroboxError> {
37 let name = match name {
38 Some(name) if !name.is_empty() => name,
39 _ => "my-distrobox".to_string(),
40 };
41
42 let ret = Self { name: name.clone() };
47 ret.start()
48 .map_err(|e| NewDistroboxError::CannotStart { source: e, name })?;
49 Ok(ret)
50 }
51
52 pub fn start(&self) -> Result<(), StartDistroboxError> {
59 let output = std::process::Command::new("podman")
60 .args(["start", &self.name])
61 .output()
62 .map_err(|e| match e.kind() {
63 std::io::ErrorKind::NotFound => StartDistroboxError::NeedPodman,
64 _ => StartDistroboxError::IoError(e),
65 })?;
66 if output.status.success() {
67 Ok(())
69 } else {
70 let matcher = OutputMatcher::new(&output);
71 if matcher.starts_with("Error: no container with name or ID")
72 && matcher.contains("found: no such container")
73 {
74 Err(StartDistroboxError::NonExistent(self.name.clone()))
75 } else {
76 Err(StartDistroboxError::Podman(output))
77 }
78 }
79 }
80
81 pub fn current() -> Result<Self, CurrentDistroboxError> {
85 if !detect() {
86 return Err(CurrentDistroboxError::NotAToolbx);
87 }
88
89 let content = std::fs::read_to_string(CONTAINER_ENV).map_err(|e| {
90 CurrentDistroboxError::Environment {
91 env_file: CONTAINER_ENV.to_string(),
92 source: e,
93 }
94 })?;
95 let name = content
96 .lines()
97 .find(|line| line.contains("name=\""))
98 .ok_or_else(|| CurrentDistroboxError::Name(CONTAINER_ENV.to_string()))?
99 .trim_start_matches("name=\"")
100 .trim_end_matches('"');
101
102 Ok(Self {
103 name: name.to_string(),
104 })
105 }
106}
107
108#[async_trait]
109impl environment::IsEnvironment for Distrobox {
110 type Err = Error;
111
112 async fn exists(&self) -> bool {
113 if detect() {
114 true
115 } else if let Environment::Host(host) = environment::current() {
116 #[allow(irrefutable_let_patterns)]
119 if let Ok(mut cmd) = host
120 .execute(crate::environment::cmd!("distrobox", "--version"))
121 .await
122 {
123 cmd.stdout(std::process::Stdio::null())
124 .stderr(std::process::Stdio::null())
125 .status()
126 .await
127 .map(|status| status.success())
128 .unwrap_or(false)
129 } else {
130 false
131 }
132 } else {
133 false
134 }
135 }
136
137 async fn execute(&self, command: CommandLine) -> Result<Command, Self::Err> {
138 debug!("preparing execution: {}", command);
139 let mut cmd: Command;
140
141 match environment::current() {
142 Environment::Distrobox(t) => {
143 if self == &t {
144 if command.get_privileged() {
148 cmd = Command::new("sudo");
149 if !command.get_interactive() {
150 cmd.arg("-n");
151 }
152
153 cmd.arg(command.command());
154 } else {
155 cmd = Command::new(command.command());
156 }
157
158 cmd.args(command.args());
159 } else {
160 return Err(Error::Unimplemented(
161 "running in a distrobox from another distrobox".to_string(),
162 ));
163 }
164 }
165 Environment::Toolbx(_) => {
166 return Err(Error::Unimplemented(
167 "running in a distrobox from a toolbx".to_string(),
168 ));
169 }
170 Environment::Host(_) => {
171 cmd = Command::new("distrobox");
172 cmd.args(["enter", "--name", &self.name]);
173
174 if std::io::stdout().is_terminal() && std::io::stdin().is_terminal() {
177 cmd.arg("-T");
178 }
179 cmd.arg("--");
180
181 if command.get_privileged() {
183 cmd.args(["sudo", "-S", "-E"]);
184 }
185
186 cmd.arg(command.command()).args(command.args());
187 }
188 #[cfg(test)]
189 Environment::Mock(_) => unimplemented!(),
190 }
191
192 trace!("full command: {:?}", cmd);
193 Ok(cmd)
194 }
195}
196
197pub fn detect() -> bool {
201 Path::new(DISTROBOX_ENV).exists()
202}
203
204#[derive(Debug, ThisError)]
205pub enum StartDistroboxError {
206 #[error("working with distrobox containers requires the 'podman' executable")]
207 NeedPodman,
208
209 #[error("podman exited with non-zero code: {0:#?}")]
210 Podman(std::process::Output),
211
212 #[error("no distrobox with name {0} exists")]
213 NonExistent(String),
214
215 #[error("unknown I/O error occured")]
216 IoError(#[from] std::io::Error),
217}
218
219#[derive(Debug, ThisError)]
220pub enum NewDistroboxError {
221 #[error("failed to determine default distrobox name")]
222 UnknownDefault(#[from] DefaultToolbxError),
223
224 #[error("failed to start distrobox container with name '{name}'")]
225 CannotStart {
226 source: StartDistroboxError,
227 name: String,
228 },
229}
230
231#[derive(Debug, ThisError)]
232pub enum CurrentDistroboxError {
233 #[error("cannot read toolbx info from environment file '{env_file}'")]
234 Environment {
235 env_file: String,
236 source: std::io::Error,
237 },
238
239 #[error("program currently isn't run from a toolbx")]
240 NotAToolbx,
241
242 #[error("failed to read toolbx name from environment file '{0}'")]
243 Name(String),
244}
245
246#[derive(Debug, ThisError)]
247pub enum DefaultToolbxError {
248 #[error("failed to read OS information from '{file}'")]
249 UnknownOs {
250 file: String,
251 source: std::io::Error,
252 },
253
254 #[error("cannot determine OS ID from os-release info")]
255 Id,
256
257 #[error("cannot determine OS VERSION_ID from os-release info")]
258 VersionId,
259}
260
261#[derive(Debug, ThisError)]
262pub enum Error {
263 #[error("cannot determine current working directory")]
264 UnknownCwd(#[from] std::io::Error),
265
266 #[error("not implemented: {0}")]
267 Unimplemented(String),
268}