secure-exec-vfs-core 0.3.1

Generic async virtual filesystem primitives
Documentation
use super::vfs::{VfsResult, VirtualFileSystem};
use std::collections::BTreeSet;

pub const DEFAULT_MAX_FILESYSTEM_BYTES: u64 = 64 * 1024 * 1024;
pub const DEFAULT_MAX_INODE_COUNT: usize = 16_384;

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct FileSystemUsage {
    pub total_bytes: u64,
    pub inode_count: usize,
}

pub trait RootFilesystemResourceLimits {
    fn max_filesystem_bytes(&self) -> Option<u64>;
    fn max_inode_count(&self) -> Option<usize>;
}

pub fn measure_filesystem_usage<F: VirtualFileSystem>(
    filesystem: &mut F,
) -> VfsResult<FileSystemUsage> {
    let mut visited = BTreeSet::new();
    measure_path_usage(filesystem, "/", &mut visited)
}

fn measure_path_usage<F: VirtualFileSystem>(
    filesystem: &mut F,
    path: &str,
    visited: &mut BTreeSet<u64>,
) -> VfsResult<FileSystemUsage> {
    let stat = filesystem.lstat(path)?;
    let mut usage = FileSystemUsage::default();

    if visited.insert(stat.ino) {
        usage.inode_count += 1;
        if !stat.is_directory {
            usage.total_bytes = usage.total_bytes.saturating_add(stat.size);
        }
    }

    if !stat.is_directory || stat.is_symbolic_link {
        return Ok(usage);
    }

    for entry in filesystem.read_dir_with_types(path)? {
        if matches!(entry.name.as_str(), "." | "..") {
            continue;
        }

        let child_path = if path == "/" {
            format!("/{}", entry.name)
        } else {
            format!("{path}/{}", entry.name)
        };
        let child_usage = measure_path_usage(filesystem, &child_path, visited)?;
        usage.total_bytes = usage.total_bytes.saturating_add(child_usage.total_bytes);
        usage.inode_count = usage.inode_count.saturating_add(child_usage.inode_count);
    }

    Ok(usage)
}