Skip to main content

arcbox_virtio_fs/device/
mod.rs

1//! `VirtioFs` device — config, queue dispatch, `VirtioDevice` impl.
2
3mod queue;
4mod virtio_device;
5
6#[cfg(test)]
7mod tests;
8
9use std::sync::Arc;
10
11use arcbox_virtio_core::error::{Result, VirtioError};
12use arcbox_virtio_core::queue::VirtQueue;
13use arcbox_virtio_core::virtio_bindings;
14
15use crate::handler::FuseRequestHandler;
16use crate::request::FuseResponse;
17use crate::session::FuseSession;
18
19/// Filesystem device configuration.
20#[derive(Debug, Clone)]
21pub struct FsConfig {
22    /// Filesystem tag (mount identifier).
23    pub tag: String,
24    /// Number of request queues.
25    pub num_queues: u32,
26    /// Queue size.
27    pub queue_size: u16,
28    /// Shared directory path on host.
29    pub shared_dir: String,
30}
31
32impl Default for FsConfig {
33    fn default() -> Self {
34        Self {
35            tag: "arcbox".to_string(),
36            num_queues: 1,
37            queue_size: 1024,
38            shared_dir: String::new(),
39        }
40    }
41}
42
43/// `VirtIO` filesystem device.
44///
45/// Provides high-performance file sharing between host and guest using
46/// the FUSE protocol over virtio transport.
47pub struct VirtioFs {
48    config: FsConfig,
49    features: u64,
50    acked_features: u64,
51    /// FUSE session state.
52    session: FuseSession,
53    /// Request handler (provided by arcbox-fs).
54    handler: Option<Arc<dyn FuseRequestHandler>>,
55    /// Request queues for FUSE traffic (host-side, used by tests).
56    request_queues: Vec<VirtQueue>,
57    /// Whether the device is activated.
58    activated: bool,
59    /// Last processed avail index for request queue 1 (guest-memory path).
60    last_avail_idx_q1: u16,
61}
62
63impl VirtioFs {
64    /// Feature: Notification.
65    pub const FEATURE_NOTIFICATION: u64 = 1 << 0;
66    /// VirtIO version 1 compliance (required for modern MMIO transport).
67    pub const FEATURE_VERSION_1: u64 = 1 << virtio_bindings::virtio_config::VIRTIO_F_VERSION_1;
68
69    /// FUSE opcode for INIT.
70    pub(crate) const FUSE_INIT: u32 = 26;
71
72    /// FUSE opcode for DESTROY.
73    const FUSE_DESTROY: u32 = 38;
74
75    /// Creates a new filesystem device.
76    #[must_use]
77    pub fn new(config: FsConfig) -> Self {
78        Self {
79            config,
80            features: Self::FEATURE_VERSION_1,
81            acked_features: 0,
82            session: FuseSession::new(),
83            handler: None,
84            request_queues: Vec::new(),
85            activated: false,
86            last_avail_idx_q1: 0,
87        }
88    }
89
90    /// Creates a new filesystem device with a request handler.
91    #[must_use]
92    pub fn with_handler(config: FsConfig, handler: Arc<dyn FuseRequestHandler>) -> Self {
93        Self {
94            config,
95            features: Self::FEATURE_VERSION_1,
96            acked_features: 0,
97            session: FuseSession::new(),
98            handler: Some(handler),
99            request_queues: Vec::new(),
100            activated: false,
101            last_avail_idx_q1: 0,
102        }
103    }
104
105    /// Sets the request handler.
106    pub fn set_handler(&mut self, handler: Arc<dyn FuseRequestHandler>) {
107        self.handler = Some(handler);
108    }
109
110    /// Returns a reference to the request handler.
111    #[must_use]
112    pub fn handler(&self) -> Option<&Arc<dyn FuseRequestHandler>> {
113        self.handler.as_ref()
114    }
115
116    /// Returns a reference to the FUSE session.
117    #[must_use]
118    pub const fn session(&self) -> &FuseSession {
119        &self.session
120    }
121
122    /// Returns whether the device is activated.
123    #[must_use]
124    pub const fn is_activated(&self) -> bool {
125        self.activated
126    }
127
128    /// Returns the filesystem tag.
129    #[must_use]
130    pub fn tag(&self) -> &str {
131        &self.config.tag
132    }
133
134    /// Returns the shared directory path.
135    #[must_use]
136    pub fn shared_dir(&self) -> &str {
137        &self.config.shared_dir
138    }
139
140    /// Returns the number of queues.
141    #[must_use]
142    pub const fn num_queues(&self) -> u32 {
143        self.config.num_queues
144    }
145
146    /// Returns the queue size.
147    #[must_use]
148    pub const fn queue_size(&self) -> u16 {
149        self.config.queue_size
150    }
151
152    /// Processes a FUSE request and returns the response.
153    ///
154    /// This method is called by the VMM when a request is received from
155    /// the guest via the virtqueue.
156    ///
157    /// # Flow
158    ///
159    /// 1. Parse request opcode
160    /// 2. If `FUSE_INIT`: handle initialization handshake
161    /// 3. If `FUSE_DESTROY`: clean up session
162    /// 4. Otherwise: delegate to request handler
163    ///
164    /// # Errors
165    ///
166    /// Returns an error if the request cannot be processed.
167    pub fn process_request(&mut self, request: &[u8]) -> Result<Vec<u8>> {
168        if request.len() < 40 {
169            return Err(VirtioError::DeviceError {
170                device: "fs".to_string(),
171                message: "FUSE request too small".to_string(),
172            });
173        }
174
175        // Parse opcode from header (offset 4-7)
176        let opcode = u32::from_le_bytes([request[4], request[5], request[6], request[7]]);
177
178        // Parse unique ID for error responses
179        let unique = u64::from_le_bytes([
180            request[8],
181            request[9],
182            request[10],
183            request[11],
184            request[12],
185            request[13],
186            request[14],
187            request[15],
188        ]);
189
190        match opcode {
191            Self::FUSE_INIT => {
192                let response = self.session.handle_init(request)?;
193
194                if let Some(handler) = &self.handler {
195                    handler.on_init(&self.session);
196                }
197
198                Ok(response)
199            }
200            Self::FUSE_DESTROY => {
201                self.session.reset();
202
203                if let Some(handler) = &self.handler {
204                    handler.on_destroy();
205                }
206
207                Ok(FuseResponse::new(unique, vec![]).into_data())
208            }
209            _ => {
210                if !self.session.is_initialized() {
211                    tracing::warn!("FUSE request before INIT: opcode={}", opcode);
212                    return Ok(FuseResponse::error(unique, libc::EINVAL).into_data());
213                }
214
215                if let Some(handler) = &self.handler {
216                    handler.handle_request(request)
217                } else {
218                    // No handler configured, return ENOSYS
219                    Ok(FuseResponse::error(unique, libc::ENOSYS).into_data())
220                }
221            }
222        }
223    }
224}