ostree_ext/
bootabletree.rs

1//! Helper functions for bootable OSTrees.
2
3use std::path::Path;
4
5use anyhow::Result;
6use camino::Utf8Path;
7use camino::Utf8PathBuf;
8use cap_std::fs::Dir;
9use cap_std_ext::cap_std;
10use ostree::gio;
11use ostree::prelude::*;
12
13const MODULES: &str = "usr/lib/modules";
14const VMLINUZ: &str = "vmlinuz";
15
16/// Find the kernel modules directory in a bootable OSTree commit.
17/// The target directory will have a `vmlinuz` file representing the kernel binary.
18pub fn find_kernel_dir(
19    root: &gio::File,
20    cancellable: Option<&gio::Cancellable>,
21) -> Result<Option<gio::File>> {
22    let moddir = root.resolve_relative_path(MODULES);
23    let e = moddir.enumerate_children(
24        "standard::name",
25        gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS,
26        cancellable,
27    )?;
28    let mut r = None;
29    for child in e.clone() {
30        let child = &child?;
31        if child.file_type() != gio::FileType::Directory {
32            continue;
33        }
34        let childpath = e.child(child);
35        let vmlinuz = childpath.child(VMLINUZ);
36        if !vmlinuz.query_exists(cancellable) {
37            continue;
38        }
39        if r.replace(childpath).is_some() {
40            anyhow::bail!("Found multiple subdirectories in {}", MODULES);
41        }
42    }
43    Ok(r)
44}
45
46fn read_dir_optional(
47    d: &Dir,
48    p: impl AsRef<Path>,
49) -> std::io::Result<Option<cap_std::fs::ReadDir>> {
50    match d.read_dir(p.as_ref()) {
51        Ok(r) => Ok(Some(r)),
52        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
53        Err(e) => Err(e),
54    }
55}
56
57/// Find the kernel modules directory in checked out directory tree.
58/// The target directory will have a `vmlinuz` file representing the kernel binary.
59pub fn find_kernel_dir_fs(root: &Dir) -> Result<Option<Utf8PathBuf>> {
60    let mut r = None;
61    let entries = if let Some(entries) = read_dir_optional(root, MODULES)? {
62        entries
63    } else {
64        return Ok(None);
65    };
66    for child in entries {
67        let child = &child?;
68        if !child.file_type()?.is_dir() {
69            continue;
70        }
71        let name = child.file_name();
72        let name = if let Some(n) = name.to_str() {
73            n
74        } else {
75            continue;
76        };
77        let mut pbuf = Utf8Path::new(MODULES).to_owned();
78        pbuf.push(name);
79        pbuf.push(VMLINUZ);
80        if !root.try_exists(&pbuf)? {
81            continue;
82        }
83        pbuf.pop();
84        if r.replace(pbuf).is_some() {
85            anyhow::bail!("Found multiple subdirectories in {}", MODULES);
86        }
87    }
88    Ok(r)
89}
90
91#[cfg(test)]
92mod test {
93    use super::*;
94    use cap_std_ext::{cap_std, cap_tempfile};
95
96    #[test]
97    fn test_find_kernel_dir_fs() -> Result<()> {
98        let td = cap_tempfile::tempdir(cap_std::ambient_authority())?;
99
100        // Verify the empty case
101        assert!(find_kernel_dir_fs(&td).unwrap().is_none());
102        let moddir = Utf8Path::new("usr/lib/modules");
103        td.create_dir_all(moddir)?;
104        assert!(find_kernel_dir_fs(&td).unwrap().is_none());
105
106        let kpath = moddir.join("5.12.8-32.aarch64");
107        td.create_dir_all(&kpath)?;
108        td.write(kpath.join("vmlinuz"), "some kernel")?;
109        let kpath2 = moddir.join("5.13.7-44.aarch64");
110        td.create_dir_all(&kpath2)?;
111        td.write(kpath2.join("foo.ko"), "some kmod")?;
112
113        assert_eq!(
114            find_kernel_dir_fs(&td)
115                .unwrap()
116                .unwrap()
117                .file_name()
118                .unwrap(),
119            kpath.file_name().unwrap()
120        );
121
122        Ok(())
123    }
124}