bootc_lib/
containerenv.rs

1//! Helpers for parsing the `/run/.containerenv` file generated by podman.
2
3use std::io::{BufRead, BufReader};
4
5use anyhow::Result;
6use cap_std_ext::cap_std::fs::Dir;
7use cap_std_ext::prelude::CapStdExtDirExt;
8use fn_error_context::context;
9
10/// Path is relative to container rootfs (assumed to be /)
11pub(crate) const PATH: &str = "run/.containerenv";
12
13#[derive(Debug, Default)]
14pub(crate) struct ContainerExecutionInfo {
15    pub(crate) engine: String,
16    pub(crate) name: String,
17    pub(crate) id: String,
18    pub(crate) image: String,
19    pub(crate) imageid: String,
20    pub(crate) rootless: Option<String>,
21}
22
23pub(crate) fn is_container(rootfs: &Dir) -> bool {
24    rootfs.exists(PATH)
25}
26
27/// Load and parse the `/run/.containerenv` file.
28#[context("Querying container")]
29pub(crate) fn get_container_execution_info(rootfs: &Dir) -> Result<ContainerExecutionInfo> {
30    let f = match rootfs.open_optional(PATH)? {
31        Some(f) => BufReader::new(f),
32        None => {
33            anyhow::bail!(
34                "This command must be executed inside a podman container (missing /{PATH})"
35            )
36        }
37    };
38    let mut r = ContainerExecutionInfo::default();
39    for line in f.lines() {
40        let line = line?;
41        let line = line.trim();
42        let Some((k, v)) = line.split_once('=') else {
43            continue;
44        };
45        // Assuming there's no quotes here
46        let v = v.trim_start_matches('"').trim_end_matches('"');
47        match k {
48            "engine" => r.engine = v.to_string(),
49            "name" => r.name = v.to_string(),
50            "id" => r.id = v.to_string(),
51            "image" => r.image = v.to_string(),
52            "imageid" => r.imageid = v.to_string(),
53            "rootless" => r.rootless = Some(v.to_string()),
54            _ => {}
55        }
56    }
57    Ok(r)
58}