ostree_ext/
container_utils.rs

1//! Helpers for interacting with containers at runtime.
2
3use std::io;
4use std::io::Read;
5use std::path::Path;
6
7use anyhow::Result;
8use ocidir::cap_std::fs::Dir;
9use ostree::glib;
10
11use crate::keyfileext::KeyFileExt;
12
13/// The relative path to the stamp file which signals this is an ostree-booted system.
14pub const OSTREE_BOOTED: &str = "run/ostree-booted";
15
16// See https://github.com/coreos/rpm-ostree/pull/3285#issuecomment-999101477
17// For compatibility with older ostree, we stick this in /sysroot where
18// it will be ignored.
19const V0_REPO_CONFIG: &str = "/sysroot/config";
20const V1_REPO_CONFIG: &str = "/sysroot/ostree/repo/config";
21
22/// Attempts to detect if the current process is running inside a container.
23/// This looks for the `container` environment variable or the presence
24/// of Docker or podman's more generic `/run/.containerenv`.
25/// This is a best-effort function, as there is not a 100% reliable way
26/// to determine this.
27pub fn running_in_container() -> bool {
28    if std::env::var_os("container").is_some() {
29        return true;
30    }
31    // https://stackoverflow.com/questions/20010199/how-to-determine-if-a-process-runs-inside-lxc-docker
32    for p in ["/run/.containerenv", "/.dockerenv"] {
33        if std::path::Path::new(p).exists() {
34            return true;
35        }
36    }
37    false
38}
39
40// https://docs.rs/openat-ext/0.1.10/openat_ext/trait.OpenatDirExt.html#tymethod.open_file_optional
41// https://users.rust-lang.org/t/why-i-use-anyhow-error-even-in-libraries/68592
42pub(crate) fn open_optional(path: impl AsRef<Path>) -> std::io::Result<Option<std::fs::File>> {
43    match std::fs::File::open(path.as_ref()) {
44        Ok(r) => Ok(Some(r)),
45        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
46        Err(e) => Err(e),
47    }
48}
49
50/// Returns `true` if the current root filesystem has an ostree repository in `bare-split-xattrs` mode.
51/// This will be the case in a running ostree-native container.
52pub fn is_bare_split_xattrs() -> Result<bool> {
53    if let Some(configf) = open_optional(V1_REPO_CONFIG)
54        .transpose()
55        .or_else(|| open_optional(V0_REPO_CONFIG).transpose())
56    {
57        let configf = configf?;
58        let mut bufr = std::io::BufReader::new(configf);
59        let mut s = String::new();
60        bufr.read_to_string(&mut s)?;
61        let kf = glib::KeyFile::new();
62        kf.load_from_data(&s, glib::KeyFileFlags::NONE)?;
63        let r = if let Some(mode) = kf.optional_string("core", "mode")? {
64            mode == crate::tar::BARE_SPLIT_XATTRS_MODE
65        } else {
66            false
67        };
68        Ok(r)
69    } else {
70        Ok(false)
71    }
72}
73
74/// Returns true if the system appears to have been booted via ostree.
75/// This accesses global state in /run.
76pub fn ostree_booted() -> io::Result<bool> {
77    Path::new(&format!("/{OSTREE_BOOTED}")).try_exists()
78}
79
80/// Returns true if the target root appears to have been booted via ostree.
81pub fn is_ostree_booted_in(rootfs: &Dir) -> io::Result<bool> {
82    rootfs.try_exists(OSTREE_BOOTED)
83}
84
85/// Returns `true` if the current booted filesystem appears to be an ostree-native container.
86///
87/// This just invokes [`is_bare_split_xattrs`] and [`running_in_container`].
88pub fn is_ostree_container() -> Result<bool> {
89    let is_container_ostree = is_bare_split_xattrs()?;
90    let running_in_systemd = std::env::var_os("INVOCATION_ID").is_some();
91    // If we have a container-ostree repo format, then we'll assume we're
92    // running in a container unless there's strong evidence not (we detect
93    // we're part of a systemd unit or are in a booted ostree system).
94    let maybe_container = running_in_container() || (!running_in_systemd && !ostree_booted()?);
95    Ok(is_container_ostree && maybe_container)
96}
97
98/// Returns an error unless the current filesystem is an ostree-based container
99///
100/// This just wraps [`is_ostree_container`].
101pub fn require_ostree_container() -> Result<()> {
102    if !is_ostree_container()? {
103        anyhow::bail!("Not in an ostree-based container environment");
104    }
105    Ok(())
106}