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