Skip to content

Commit b41bb0f

Browse files
cgwaltersJohan-Liebert1
authored andcommitted
generic_tree: Replace Rc/RefCell with flat index-based FileSystem
In trying to update to the newer fuse crate, it wants to do multithreaded stuff, and that just breaks with the `Rc` inside `FileSystem`. Similarly - I recently changed our local filesystem scanning to be async, and the `Rc` usage made it less ergonomic. There are 3 cases we care about: - Borrowed, immutable in memory tree (no interior mut needed!) - Owned &mut version - Merging/flattening two trees I think it's just more natural for us to represent the filesystem with a set of inodes, plus the recursive tree pointing to those. Assisted-by: OpenCode (Claude Opus 4) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent a1e2aab commit b41bb0f

19 files changed

Lines changed: 1514 additions & 915 deletions

File tree

Justfile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@ fmt-check:
3636
fmt:
3737
cargo fmt --all
3838

39-
# Run all checks (clippy + fmt + test)
40-
check: clippy check-feature-combos fmt-check test
39+
# Check that fuzz targets compile
40+
check-fuzz:
41+
cargo check --manifest-path crates/composefs/fuzz/Cargo.toml
42+
43+
# Run all checks (clippy + fmt + test + fuzz build)
44+
check: clippy check-feature-combos fmt-check test check-fuzz
4145

4246
# Base image for test container builds.
4347
# Override to test on a different distro, e.g.:

crates/cfsctl/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,7 @@ fn dump_file_impl(
772772
backing_path_only: bool,
773773
) -> Result<()> {
774774
let mut out = Vec::new();
775+
let nlink_map = fs.nlinks();
775776

776777
for file_path in files {
777778
let (dir, file) = fs.root.split(file_path.as_os_str())?;
@@ -787,14 +788,15 @@ fn dump_file_impl(
787788
anyhow::bail!("{} is a directory", file_path.display());
788789
}
789790

790-
dump_single_dir(&mut out, directory, file_path.clone())?
791+
dump_single_dir(&mut out, directory, &fs, &nlink_map, file_path.clone())?
791792
}
792793

793-
Inode::Leaf(leaf) => {
794+
Inode::Leaf(leaf_id, _) => {
794795
use composefs::generic_tree::LeafContent::*;
795796
use composefs::tree::RegularFile::*;
796797

797798
if backing_path_only {
799+
let leaf = fs.leaf(*leaf_id);
798800
match &leaf.content {
799801
Regular(f) => match f {
800802
Inline(..) => println!("{} inline", file_path.display()),
@@ -810,7 +812,7 @@ fn dump_file_impl(
810812
continue;
811813
}
812814

813-
dump_single_file(&mut out, leaf, file_path.clone())?
815+
dump_single_file(&mut out, *leaf_id, &fs, &nlink_map, file_path.clone())?
814816
}
815817
};
816818
}

crates/composefs-boot/src/bootloader.rs

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use anyhow::{Result, bail};
1616
use composefs::{
1717
fsverity::FsVerityHashValue,
1818
repository::Repository,
19-
tree::{Directory, FileSystem, ImageError, Inode, LeafContent, RegularFile},
19+
tree::{DirectoryRef, FileSystem, ImageError, Inode, LeafContent, RegularFile},
2020
};
2121

2222
use crate::cmdline::{make_cmdline_composefs, split_cmdline};
@@ -226,15 +226,15 @@ impl<ObjectID: FsVerityHashValue> Type1Entry<ObjectID> {
226226
pub fn load(
227227
filename: &OsStr,
228228
file: &RegularFile<ObjectID>,
229-
root: &Directory<ObjectID>,
229+
root: DirectoryRef<'_, ObjectID>,
230230
repo: &Repository<ObjectID>,
231231
) -> Result<Self> {
232232
let entry = BootLoaderEntryFile::new(from_utf8(&composefs::fs::read_file(file, repo)?)?);
233233

234234
let mut files = HashMap::new();
235235
for key in ["linux", "initrd", "efi"] {
236236
for pathname in entry.get_values(key) {
237-
let (dir, filename) = root.split(pathname.as_ref())?;
237+
let (dir, filename) = root.split_ref(pathname.as_ref())?;
238238
files.insert(Box::from(pathname), dir.get_file(filename)?.clone());
239239
}
240240
}
@@ -256,20 +256,22 @@ impl<ObjectID: FsVerityHashValue> Type1Entry<ObjectID> {
256256
/// # Returns
257257
///
258258
/// A vector of all Type1Entry objects found in /boot/loader/entries
259-
pub fn load_all(root: &Directory<ObjectID>, repo: &Repository<ObjectID>) -> Result<Vec<Self>> {
259+
pub fn load_all(fs: &FileSystem<ObjectID>, repo: &Repository<ObjectID>) -> Result<Vec<Self>> {
260260
let mut entries = vec![];
261+
let root = fs.as_dir();
261262

262-
match root.get_directory("/boot/loader/entries".as_ref()) {
263+
match root.get_directory_ref("/boot/loader/entries".as_ref()) {
263264
Ok(entries_dir) => {
264265
for (filename, inode) in entries_dir.entries() {
265266
if !filename.as_bytes().ends_with(b".conf") {
266267
continue;
267268
}
268269

269-
let Inode::Leaf(leaf) = inode else {
270+
let Inode::Leaf(leaf_id, _) = inode else {
270271
bail!("/boot/loader/entries/{filename:?} is a directory");
271272
};
272273

274+
let leaf = fs.leaf(*leaf_id);
273275
let LeafContent::Regular(file) = &leaf.content else {
274276
bail!("/boot/loader/entries/{filename:?} is not a regular file");
275277
};
@@ -336,7 +338,7 @@ impl<ObjectID: FsVerityHashValue> Type2Entry<ObjectID> {
336338
// Find UKI components, the UKI PE binary and other UKI addons,
337339
// if any, in the provided directory
338340
fn find_uki_components(
339-
dir: &Directory<ObjectID>,
341+
dir: DirectoryRef<'_, ObjectID>,
340342
entries: &mut Vec<Self>,
341343
path: &mut PathBuf,
342344
kver: &Option<Box<OsStr>>,
@@ -347,8 +349,9 @@ impl<ObjectID: FsVerityHashValue> Type2Entry<ObjectID> {
347349
// Collect all UKI extensions
348350
// Usually we'll find them in the root with directories ending in `.efi.extra.d` for kernel
349351
// specific addons. Global addons are found in `loader/addons`
350-
if let Inode::Directory(dir) = inode {
351-
Self::find_uki_components(dir, entries, path, kver)?;
352+
if let Inode::Directory(subdir) = inode {
353+
let subdir_ref = DirectoryRef::from_parts(subdir, dir.leaves());
354+
Self::find_uki_components(subdir_ref, entries, path, kver)?;
352355
path.pop();
353356
continue;
354357
}
@@ -358,10 +361,11 @@ impl<ObjectID: FsVerityHashValue> Type2Entry<ObjectID> {
358361
continue;
359362
}
360363

361-
let Inode::Leaf(leaf) = inode else {
364+
let Inode::Leaf(leaf_id, _) = inode else {
362365
bail!("{filename:?} is a directory");
363366
};
364367

368+
let leaf = dir.leaf(*leaf_id);
365369
let LeafContent::Regular(file) = &leaf.content else {
366370
bail!("{filename:?} is not a regular file");
367371
};
@@ -392,26 +396,28 @@ impl<ObjectID: FsVerityHashValue> Type2Entry<ObjectID> {
392396
/// # Returns
393397
///
394398
/// A vector of all Type2Entry objects found
395-
pub fn load_all(root: &Directory<ObjectID>) -> Result<Vec<Self>> {
399+
pub fn load_all(fs: &FileSystem<ObjectID>) -> Result<Vec<Self>> {
396400
let mut entries = vec![];
401+
let root = fs.as_dir();
397402

398-
match root.get_directory("/boot/EFI/Linux".as_ref()) {
403+
match root.get_directory_ref("/boot/EFI/Linux".as_ref()) {
399404
Ok(entries_dir) => {
400405
Self::find_uki_components(entries_dir, &mut entries, &mut PathBuf::new(), &None)?
401406
}
402407
Err(ImageError::NotFound(..)) => {}
403408
Err(other) => Err(other)?,
404409
};
405410

406-
match root.get_directory("/usr/lib/modules".as_ref()) {
411+
match root.get_directory_ref("/usr/lib/modules".as_ref()) {
407412
Ok(modules_dir) => {
408413
for (kver, inode) in modules_dir.entries() {
409414
let Inode::Directory(dir) = inode else {
410415
continue;
411416
};
412417

418+
let dir_ref = DirectoryRef::from_parts(dir, root.leaves());
413419
Self::find_uki_components(
414-
dir,
420+
dir_ref,
415421
&mut entries,
416422
&mut PathBuf::new(),
417423
&Some(Box::from(kver)),
@@ -490,20 +496,22 @@ initrd /{id}/initramfs.img
490496
/// # Returns
491497
///
492498
/// A vector of all UsrLibModulesVmlinuz entries found
493-
pub fn load_all(root: &Directory<ObjectID>) -> Result<Vec<Self>> {
499+
pub fn load_all(fs: &FileSystem<ObjectID>) -> Result<Vec<Self>> {
494500
let mut entries = vec![];
501+
let root = fs.as_dir();
495502

496-
match root.get_directory("/usr/lib/modules".as_ref()) {
503+
match root.get_directory_ref("/usr/lib/modules".as_ref()) {
497504
Ok(modules_dir) => {
498505
for (kver, inode) in modules_dir.entries() {
499506
let Inode::Directory(dir) = inode else {
500507
continue;
501508
};
502509

503-
if let Ok(vmlinuz) = dir.get_file("vmlinuz".as_ref()) {
510+
let dir_ref = DirectoryRef::from_parts(dir, root.leaves());
511+
if let Ok(vmlinuz) = dir_ref.get_file("vmlinuz".as_ref()) {
504512
// TODO: maybe initramfs should be mandatory: the kernel isn't useful
505513
// without it
506-
let initramfs = dir.get_file("initramfs.img".as_ref()).ok();
514+
let initramfs = dir_ref.get_file("initramfs.img".as_ref()).ok();
507515
let os_release = root.get_file("/usr/lib/os-release".as_ref()).ok();
508516
entries.push(Self {
509517
kver: Box::from(std::str::from_utf8(kver.as_bytes())?),
@@ -556,13 +564,13 @@ pub fn get_boot_resources<ObjectID: FsVerityHashValue>(
556564
) -> Result<Vec<BootEntry<ObjectID>>> {
557565
let mut entries = vec![];
558566

559-
for e in Type1Entry::load_all(&image.root, repo)? {
567+
for e in Type1Entry::load_all(image, repo)? {
560568
entries.push(BootEntry::Type1(e));
561569
}
562-
for e in Type2Entry::load_all(&image.root)? {
570+
for e in Type2Entry::load_all(image)? {
563571
entries.push(BootEntry::Type2(e));
564572
}
565-
for e in UsrLibModulesVmlinuz::load_all(&image.root)? {
573+
for e in UsrLibModulesVmlinuz::load_all(image)? {
566574
entries.push(BootEntry::UsrLibModulesVmLinuz(e));
567575
}
568576

0 commit comments

Comments
 (0)