cnf_lib/environment/
toolbx.rs1use super::prelude::*;
11
12use users::{get_current_gid, get_current_uid};
13
14use std::{io::IsTerminal, path::Path};
15
16const TOOLBX_ENV: &str = "/run/.toolboxenv";
17const CONTAINER_ENV: &str = "/run/.containerenv";
18const OS_RELEASE: &str = "/etc/os-release";
19
20#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Serialize, Deserialize)]
21pub struct Toolbx {
22 name: String,
23}
24
25impl fmt::Display for Toolbx {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, "toolbx '{}'", self.name)
28 }
29}
30
31impl Toolbx {
32 pub fn new(name: Option<String>) -> Result<Toolbx, NewToolbxError> {
38 let name = match name {
39 Some(name) if !name.is_empty() => name,
40 _ => match Toolbx::default_name() {
41 Ok(toolbx_name) => toolbx_name,
42 Err(e) => return Err(NewToolbxError::UnknownDefault(e)),
43 },
44 };
45
46 let ret = Self { name: name.clone() };
51 ret.start()
52 .map_err(|e| NewToolbxError::CannotStart { source: e, name })?;
53 Ok(ret)
54 }
55
56 pub fn start(&self) -> Result<(), StartToolbxError> {
63 let output = std::process::Command::new("podman")
64 .args(["start", &self.name])
65 .output()
66 .map_err(|e| match e.kind() {
67 std::io::ErrorKind::NotFound => StartToolbxError::NeedPodman,
68 _ => StartToolbxError::IoError(e),
69 })?;
70 if output.status.success() {
71 Ok(())
73 } else {
74 let matcher = OutputMatcher::new(&output);
75 if matcher.starts_with("Error: no container with name or ID")
76 && matcher.contains("found: no such container")
77 {
78 Err(StartToolbxError::NonExistent(self.name.clone()))
79 } else {
80 Err(StartToolbxError::Podman(output))
81 }
82 }
83 }
84
85 pub fn current() -> Result<Toolbx, CurrentToolbxError> {
89 if !detect() {
90 return Err(CurrentToolbxError::NotAToolbx);
91 }
92
93 let content = std::fs::read_to_string(CONTAINER_ENV).map_err(|e| {
94 CurrentToolbxError::Environment {
95 env_file: CONTAINER_ENV.to_string(),
96 source: e,
97 }
98 })?;
99 let name = content
100 .lines()
101 .find(|line| line.contains("name=\""))
102 .ok_or_else(|| CurrentToolbxError::Name(CONTAINER_ENV.to_string()))?
103 .trim_start_matches("name=\"")
104 .trim_end_matches('"');
105
106 Ok(Toolbx {
107 name: name.to_string(),
108 })
109 }
110
111 pub fn default_name() -> Result<String, DefaultToolbxError> {
115 debug!("Determining default toolbx name via {}", OS_RELEASE);
119
120 let content =
121 std::fs::read_to_string(OS_RELEASE).map_err(|e| DefaultToolbxError::UnknownOs {
122 file: OS_RELEASE.to_string(),
123 source: e,
124 })?;
125 let id = content
126 .lines()
127 .find(|line| line.starts_with("ID="))
128 .map(|line| line.trim_start_matches("ID=").trim_matches('"'))
129 .ok_or(DefaultToolbxError::Id)?;
130 let version_id = content
131 .lines()
132 .find(|line| line.starts_with("VERSION_ID="))
133 .map(|line| line.trim_start_matches("VERSION_ID=").trim_matches('"'))
134 .ok_or(DefaultToolbxError::VersionId)?;
135
136 Ok(format!("{}-toolbox-{}", id, version_id))
137 }
138}
139
140#[async_trait]
141impl environment::IsEnvironment for Toolbx {
142 type Err = Error;
143
144 async fn exists(&self) -> bool {
145 if detect() {
146 true
147 } else if let Environment::Host(host) = environment::current() {
148 #[allow(irrefutable_let_patterns)]
151 if let Ok(mut cmd) = host
152 .execute(crate::environment::cmd!("toolbox", "--version"))
153 .await
154 {
155 cmd.stdout(std::process::Stdio::null())
156 .stderr(std::process::Stdio::null())
157 .status()
158 .await
159 .map(|status| status.success())
160 .unwrap_or(false)
161 } else {
162 false
163 }
164 } else {
165 false
166 }
167 }
168
169 async fn execute(&self, command: CommandLine) -> Result<Command, Self::Err> {
170 debug!("preparing execution: {}", command);
171 let mut cmd: Command;
172
173 match environment::current() {
174 Environment::Distrobox(_) => {
175 return Err(Error::Unimplemented(
176 "running in a toolbx from a distrobox".to_string(),
177 ));
178 }
179 Environment::Toolbx(t) => {
180 if self == &t {
181 if command.get_privileged() {
185 cmd = Command::new("sudo");
186 if !command.get_interactive() {
187 cmd.arg("-n");
188 }
189
190 cmd.arg(command.command());
191 } else {
192 cmd = Command::new(command.command());
193 }
194
195 cmd.args(command.args());
196 } else {
197 return Err(Error::Unimplemented(
198 "running in a toolbx from another toolbx".to_string(),
199 ));
200 }
201 }
202 Environment::Host(_) => {
203 cmd = Command::new("podman");
204
205 cmd.args(["exec", "-i"]);
206 cmd.arg("--user");
209 cmd.arg(format!("{}:{}", get_current_uid(), get_current_gid()));
210 cmd.arg("--workdir");
212 cmd.arg(std::env::current_dir().map_err(Error::UnknownCwd)?);
213 for var in environment::read_env_vars() {
215 cmd.args(["-e", &var]);
216 }
217
218 cmd.args(["--detach-keys", ""]);
220
221 if std::io::stdout().is_terminal() && std::io::stdin().is_terminal() {
224 cmd.arg("-t");
225 }
226
227 cmd.arg(&self.name);
229
230 if command.get_privileged() {
232 cmd.args(["sudo", "-S", "-E"]);
233 }
240
241 cmd.arg(command.command()).args(command.args());
242 }
243 #[cfg(test)]
244 Environment::Mock(_) => unimplemented!(),
245 }
246
247 trace!("full command: {:?}", cmd);
248 Ok(cmd)
249 }
250}
251
252pub fn detect() -> bool {
256 Path::new(TOOLBX_ENV).exists()
257}
258
259#[derive(Debug, ThisError)]
260pub enum StartToolbxError {
261 #[error("working with toolbx containers requires the 'podman' executable")]
262 NeedPodman,
263
264 #[error("podman exited with non-zero code: {0:#?}")]
265 Podman(std::process::Output),
266
267 #[error("no toolbx with name {0} exists")]
268 NonExistent(String),
269
270 #[error("unknown I/O error occured")]
271 IoError(#[from] std::io::Error),
272}
273
274#[derive(Debug, ThisError)]
275pub enum NewToolbxError {
276 #[error("failed to determine default toolbx name")]
277 UnknownDefault(#[from] DefaultToolbxError),
278
279 #[error("failed to start toolbx container with name '{name}'")]
280 CannotStart {
281 source: StartToolbxError,
282 name: String,
283 },
284}
285
286#[derive(Debug, ThisError)]
287pub enum CurrentToolbxError {
288 #[error("cannot read toolbx info from environment file '{env_file}'")]
289 Environment {
290 env_file: String,
291 source: std::io::Error,
292 },
293
294 #[error("program currently isn't run from a toolbx")]
295 NotAToolbx,
296
297 #[error("failed to read toolbx name from environment file '{0}'")]
298 Name(String),
299}
300
301#[derive(Debug, ThisError)]
302pub enum DefaultToolbxError {
303 #[error("failed to read OS information from '{file}'")]
304 UnknownOs {
305 file: String,
306 source: std::io::Error,
307 },
308
309 #[error("cannot determine OS ID from os-release info")]
310 Id,
311
312 #[error("cannot determine OS VERSION_ID from os-release info")]
313 VersionId,
314}
315
316#[derive(Debug, ThisError)]
317pub enum Error {
318 #[error("cannot determine current working directory")]
319 UnknownCwd(#[from] std::io::Error),
320
321 #[error("not implemented: {0}")]
322 Unimplemented(String),
323}