1use std::sync::Arc;
11
12use arcbox_virtio::fs::{FuseRequestHandler, FuseSession};
13
14use crate::FsConfig;
15use crate::dispatcher::{DispatcherConfig, FuseDispatcher};
16use crate::error::{FsError, Result};
17use crate::passthrough::PassthroughFs;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ServerState {
22 Created,
24 Running,
26 Stopped,
28}
29
30pub struct FsServer {
36 config: FsConfig,
37 state: ServerState,
38 fs: Option<Arc<PassthroughFs>>,
40 dispatcher: Option<FuseDispatcher>,
42}
43
44impl FsServer {
45 #[must_use]
47 pub fn new(config: FsConfig) -> Self {
48 Self {
49 config,
50 state: ServerState::Created,
51 fs: None,
52 dispatcher: None,
53 }
54 }
55
56 #[must_use]
58 pub const fn state(&self) -> ServerState {
59 self.state
60 }
61
62 #[must_use]
64 pub fn tag(&self) -> &str {
65 &self.config.tag
66 }
67
68 #[must_use]
70 pub fn source(&self) -> &str {
71 &self.config.source
72 }
73
74 pub fn start(&mut self) -> Result<()> {
82 if self.state == ServerState::Running {
83 return Ok(());
84 }
85
86 let fs = PassthroughFs::new(&self.config.source)
88 .map_err(|e| FsError::not_found(format!("Failed to initialize filesystem: {}", e)))?;
89 let fs = Arc::new(fs);
90
91 let dispatcher_config = DispatcherConfig {
93 entry_timeout: self.config.cache_timeout,
94 attr_timeout: self.config.cache_timeout,
95 };
96 let dispatcher = FuseDispatcher::new(Arc::clone(&fs), dispatcher_config);
97
98 self.fs = Some(fs);
99 self.dispatcher = Some(dispatcher);
100 self.state = ServerState::Running;
101
102 tracing::info!(
103 "Started filesystem server: tag='{}', source='{}'",
104 self.config.tag,
105 self.config.source
106 );
107
108 Ok(())
109 }
110
111 pub fn stop(&mut self) -> Result<()> {
119 if self.state != ServerState::Running {
120 return Ok(());
121 }
122
123 self.dispatcher = None;
124 self.fs = None;
125 self.state = ServerState::Stopped;
126
127 tracing::info!("Stopped filesystem server: tag='{}'", self.config.tag);
128
129 Ok(())
130 }
131
132 pub fn handle_request(&self, request: &[u8]) -> Result<Vec<u8>> {
141 let dispatcher = self
142 .dispatcher
143 .as_ref()
144 .ok_or_else(|| FsError::Fuse("Server not started".to_string()))?;
145
146 dispatcher.dispatch(request)
147 }
148
149 #[must_use]
151 pub fn filesystem(&self) -> Option<&Arc<PassthroughFs>> {
152 self.fs.as_ref()
153 }
154
155 #[must_use]
157 pub fn dispatcher(&self) -> Option<&FuseDispatcher> {
158 self.dispatcher.as_ref()
159 }
160}
161
162impl FuseRequestHandler for FsServer {
167 fn handle_request(&self, request: &[u8]) -> arcbox_virtio::Result<Vec<u8>> {
168 self.handle_request(request)
169 .map_err(|e| arcbox_virtio::VirtioError::DeviceError {
170 device: "fs".to_string(),
171 message: e.to_string(),
172 })
173 }
174
175 fn on_init(&self, session: &FuseSession) {
176 tracing::info!(
177 "FsServer received FUSE_INIT: version {}.{}, max_readahead={}, max_write={}",
178 session.major(),
179 session.minor(),
180 session.max_readahead(),
181 session.max_write()
182 );
183 }
184
185 fn on_destroy(&self) {
186 tracing::info!("FsServer received FUSE_DESTROY");
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use tempfile::TempDir;
194
195 #[test]
196 fn test_server_lifecycle() {
197 let temp = TempDir::new().unwrap();
198 let config = FsConfig {
199 tag: "test".to_string(),
200 source: temp.path().to_string_lossy().to_string(),
201 num_threads: 1,
202 writeback_cache: false,
203 cache_timeout: 1,
204 };
205
206 let mut server = FsServer::new(config);
207 assert_eq!(server.state(), ServerState::Created);
208 assert!(server.filesystem().is_none());
209
210 server.start().unwrap();
212 assert_eq!(server.state(), ServerState::Running);
213 assert!(server.filesystem().is_some());
214 assert!(server.dispatcher().is_some());
215
216 server.stop().unwrap();
218 assert_eq!(server.state(), ServerState::Stopped);
219 assert!(server.filesystem().is_none());
220 }
221
222 #[test]
223 fn test_server_handle_request() {
224 use crate::fuse::{
225 FUSE_KERNEL_MINOR_VERSION, FUSE_KERNEL_VERSION, FuseInHeader, FuseInitIn, FuseOpcode,
226 };
227 use std::mem::size_of;
228
229 let temp = TempDir::new().unwrap();
230 let config = FsConfig {
231 tag: "test".to_string(),
232 source: temp.path().to_string_lossy().to_string(),
233 num_threads: 1,
234 writeback_cache: false,
235 cache_timeout: 1,
236 };
237
238 let mut server = FsServer::new(config);
239 server.start().unwrap();
240
241 let init_in = FuseInitIn {
243 major: FUSE_KERNEL_VERSION,
244 minor: FUSE_KERNEL_MINOR_VERSION,
245 max_readahead: 128 * 1024,
246 flags: 0,
247 };
248
249 let header = FuseInHeader {
250 len: (FuseInHeader::SIZE + size_of::<FuseInitIn>()) as u32,
251 opcode: FuseOpcode::Init as u32,
252 unique: 1,
253 nodeid: 0,
254 uid: 0,
255 gid: 0,
256 pid: 0,
257 padding: 0,
258 };
259
260 let mut request = Vec::new();
261 request.extend_from_slice(unsafe {
262 std::slice::from_raw_parts(&header as *const _ as *const u8, FuseInHeader::SIZE)
263 });
264 request.extend_from_slice(unsafe {
265 std::slice::from_raw_parts(&init_in as *const _ as *const u8, size_of::<FuseInitIn>())
266 });
267
268 let response = server.handle_request(&request).unwrap();
270 assert!(!response.is_empty());
271 }
272
273 #[test]
274 fn test_server_not_started_error() {
275 let config = FsConfig {
276 tag: "test".to_string(),
277 source: "/tmp".to_string(),
278 num_threads: 1,
279 writeback_cache: false,
280 cache_timeout: 1,
281 };
282
283 let server = FsServer::new(config);
284 let result = server.handle_request(&[]);
285 assert!(result.is_err());
286 }
287}