Skip to main content

arcbox_fs/
lib.rs

1//! # arcbox-fs
2//!
3//! High-performance filesystem service for `ArcBox`.
4//!
5//! This crate implements VirtioFS-based file sharing between host and guest,
6//! providing near-native file I/O performance.
7//!
8//! ## Key Features
9//!
10//! - **Zero-copy**: Direct memory mapping when possible
11//! - **Parallel I/O**: Concurrent request handling
12//! - **Intelligent caching**: Host-side metadata and data caching
13//! - **FUSE protocol**: Compatible with standard virtiofs drivers
14//!
15//! ## Architecture
16//!
17//! ```text
18//! Guest: mount -t virtiofs arcbox /mnt/arcbox
19//!                    │
20//!                    ▼
21//! ┌─────────────────────────────────────────┐
22//! │              arcbox-fs                   │
23//! │  ┌─────────────────────────────────┐   │
24//! │  │         FuseServer               │   │
25//! │  │  - Request dispatch              │   │
26//! │  │  - Reply handling                │   │
27//! │  └─────────────────────────────────┘   │
28//! │  ┌─────────────────────────────────┐   │
29//! │  │        PassthroughFs             │   │
30//! │  │  - Direct host filesystem access │   │
31//! │  │  - File handle management        │   │
32//! │  └─────────────────────────────────┘   │
33//! └─────────────────────────────────────────┘
34//! ```
35pub mod cache;
36pub mod dispatcher;
37pub mod error;
38pub mod fuse;
39pub mod passthrough;
40pub mod server;
41
42pub use cache::{
43    AdaptiveTtlConfig, NegativeCache, NegativeCacheConfig, NegativeCacheStats, TtlRule,
44};
45pub use dispatcher::{DispatcherConfig, FuseDispatcher, RequestContext, ResponseBuilder};
46pub use error::{FsError, Result};
47pub use fuse::{FuseAttr, FuseInHeader, FuseOpcode, FuseOutHeader, StatFs};
48pub use passthrough::{DirEntry, FileType, PassthroughConfig, PassthroughFs};
49pub use server::FsServer;
50
51/// Trait for DAX (Direct Access) memory mapping between host files and guest IPA.
52///
53/// Implemented by the VMM to map/unmap host file pages into the guest's
54/// DAX window. The FUSE dispatcher calls this when handling
55/// `FUSE_SETUPMAPPING` / `FUSE_REMOVEMAPPING` requests.
56pub trait DaxMapper: Send + Sync {
57    /// Maps a host file region into the guest DAX window.
58    ///
59    /// - `host_fd`: file descriptor of the host file
60    /// - `file_offset`: byte offset within the file
61    /// - `window_offset`: byte offset within the DAX window
62    /// - `length`: mapping length in bytes (page-aligned)
63    /// - `writable`: whether the mapping is writable
64    fn setup_mapping(
65        &self,
66        host_fd: i32,
67        file_offset: u64,
68        window_offset: u64,
69        length: u64,
70        writable: bool,
71    ) -> std::result::Result<(), i32>;
72
73    /// Removes a mapping from the guest DAX window.
74    fn remove_mapping(&self, window_offset: u64, length: u64) -> std::result::Result<(), i32>;
75}
76
77/// Cache profile for a VirtioFS share.
78///
79/// Controls the FUSE entry and attribute cache timeouts reported to the
80/// guest kernel. Higher values reduce FUSE round-trips at the cost of
81/// delayed visibility of host filesystem changes.
82#[derive(Debug, Clone)]
83pub enum CacheProfile {
84    /// Static content (runtime binaries, container images).
85    /// Entry/attr timeout: 300s. Aggressive caching.
86    Static,
87    /// Dynamic content (user source files).
88    /// Entry/attr timeout: 1s. Minimal caching.
89    Dynamic,
90    /// Custom timeout values.
91    Custom {
92        entry_timeout_secs: u64,
93        attr_timeout_secs: u64,
94    },
95}
96
97impl CacheProfile {
98    /// Returns the entry timeout for FUSE responses.
99    #[must_use]
100    pub fn entry_timeout(&self) -> std::time::Duration {
101        match self {
102            Self::Static => std::time::Duration::from_secs(300),
103            Self::Dynamic => std::time::Duration::from_secs(1),
104            Self::Custom {
105                entry_timeout_secs, ..
106            } => std::time::Duration::from_secs(*entry_timeout_secs),
107        }
108    }
109
110    /// Returns the attr timeout for FUSE responses.
111    #[must_use]
112    pub fn attr_timeout(&self) -> std::time::Duration {
113        match self {
114            Self::Static => std::time::Duration::from_secs(300),
115            Self::Dynamic => std::time::Duration::from_secs(1),
116            Self::Custom {
117                attr_timeout_secs, ..
118            } => std::time::Duration::from_secs(*attr_timeout_secs),
119        }
120    }
121}
122
123/// Filesystem configuration.
124#[derive(Debug, Clone)]
125pub struct FsConfig {
126    /// Tag for virtiofs mount.
127    pub tag: String,
128    /// Host directory to share.
129    pub source: String,
130    /// Number of worker threads.
131    pub num_threads: usize,
132    /// Enable writeback caching.
133    pub writeback_cache: bool,
134    /// Cache profile controlling FUSE entry/attr timeouts.
135    /// Takes precedence over `cache_timeout` when set.
136    pub cache_profile: CacheProfile,
137    /// Cache timeout for directory entries and attributes (seconds).
138    /// Used as a fallback when `cache_profile` is not explicitly set.
139    /// Higher values reduce FUSE round-trips but delay visibility of
140    /// host filesystem changes inside the guest.
141    pub cache_timeout: u64,
142    /// Negative lookup cache TTL (seconds). Caches ENOENT results to avoid
143    /// repeated stat() calls for non-existent paths.
144    pub negative_cache_ttl: u64,
145}
146
147impl Default for FsConfig {
148    fn default() -> Self {
149        Self {
150            tag: "arcbox".to_string(),
151            source: String::new(),
152            num_threads: 4,
153            writeback_cache: true,
154            cache_profile: CacheProfile::Custom {
155                entry_timeout_secs: 10,
156                attr_timeout_secs: 10,
157            },
158            cache_timeout: 10,
159            negative_cache_ttl: 5,
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use std::time::Duration;
168
169    #[test]
170    fn test_cache_profile_static() {
171        let profile = CacheProfile::Static;
172        assert_eq!(profile.entry_timeout(), Duration::from_secs(300));
173        assert_eq!(profile.attr_timeout(), Duration::from_secs(300));
174    }
175
176    #[test]
177    fn test_cache_profile_dynamic() {
178        let profile = CacheProfile::Dynamic;
179        assert_eq!(profile.entry_timeout(), Duration::from_secs(1));
180        assert_eq!(profile.attr_timeout(), Duration::from_secs(1));
181    }
182
183    #[test]
184    fn test_cache_profile_custom() {
185        let profile = CacheProfile::Custom {
186            entry_timeout_secs: 42,
187            attr_timeout_secs: 7,
188        };
189        assert_eq!(profile.entry_timeout(), Duration::from_secs(42));
190        assert_eq!(profile.attr_timeout(), Duration::from_secs(7));
191    }
192
193    #[test]
194    fn test_fs_config_default_uses_custom_profile() {
195        let config = FsConfig::default();
196        // Default profile should use the same timeout as cache_timeout
197        assert_eq!(
198            config.cache_profile.entry_timeout(),
199            Duration::from_secs(10)
200        );
201        assert_eq!(config.cache_profile.attr_timeout(), Duration::from_secs(10));
202    }
203}