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}