Skip to main content

arcbox_virtio_fs/
device.rs

1//! `VirtioFs` device — config, queue dispatch, `VirtioDevice` impl.
2
3use std::sync::Arc;
4
5use arcbox_virtio_core::error::{Result, VirtioError};
6use arcbox_virtio_core::queue::VirtQueue;
7use arcbox_virtio_core::{QueueConfig, VirtioDevice, VirtioDeviceId, virtio_bindings};
8
9use crate::handler::FuseRequestHandler;
10use crate::request::FuseResponse;
11use crate::session::FuseSession;
12
13/// Filesystem device configuration.
14#[derive(Debug, Clone)]
15pub struct FsConfig {
16    /// Filesystem tag (mount identifier).
17    pub tag: String,
18    /// Number of request queues.
19    pub num_queues: u32,
20    /// Queue size.
21    pub queue_size: u16,
22    /// Shared directory path on host.
23    pub shared_dir: String,
24}
25
26impl Default for FsConfig {
27    fn default() -> Self {
28        Self {
29            tag: "arcbox".to_string(),
30            num_queues: 1,
31            queue_size: 1024,
32            shared_dir: String::new(),
33        }
34    }
35}
36
37/// `VirtIO` filesystem device.
38///
39/// Provides high-performance file sharing between host and guest using
40/// the FUSE protocol over virtio transport.
41pub struct VirtioFs {
42    config: FsConfig,
43    features: u64,
44    acked_features: u64,
45    /// FUSE session state.
46    session: FuseSession,
47    /// Request handler (provided by arcbox-fs).
48    handler: Option<Arc<dyn FuseRequestHandler>>,
49    /// Request queues for FUSE traffic (host-side, used by tests).
50    request_queues: Vec<VirtQueue>,
51    /// Whether the device is activated.
52    activated: bool,
53    /// Last processed avail index for request queue 1 (guest-memory path).
54    last_avail_idx_q1: u16,
55}
56
57impl VirtioFs {
58    /// Feature: Notification.
59    pub const FEATURE_NOTIFICATION: u64 = 1 << 0;
60    /// VirtIO version 1 compliance (required for modern MMIO transport).
61    pub const FEATURE_VERSION_1: u64 = 1 << virtio_bindings::virtio_config::VIRTIO_F_VERSION_1;
62
63    /// FUSE opcode for INIT.
64    pub(crate) const FUSE_INIT: u32 = 26;
65
66    /// FUSE opcode for DESTROY.
67    const FUSE_DESTROY: u32 = 38;
68
69    /// Creates a new filesystem device.
70    #[must_use]
71    pub fn new(config: FsConfig) -> Self {
72        Self {
73            config,
74            features: Self::FEATURE_VERSION_1,
75            acked_features: 0,
76            session: FuseSession::new(),
77            handler: None,
78            request_queues: Vec::new(),
79            activated: false,
80            last_avail_idx_q1: 0,
81        }
82    }
83
84    /// Creates a new filesystem device with a request handler.
85    #[must_use]
86    pub fn with_handler(config: FsConfig, handler: Arc<dyn FuseRequestHandler>) -> Self {
87        Self {
88            config,
89            features: Self::FEATURE_VERSION_1,
90            acked_features: 0,
91            session: FuseSession::new(),
92            handler: Some(handler),
93            request_queues: Vec::new(),
94            activated: false,
95            last_avail_idx_q1: 0,
96        }
97    }
98
99    /// Sets the request handler.
100    pub fn set_handler(&mut self, handler: Arc<dyn FuseRequestHandler>) {
101        self.handler = Some(handler);
102    }
103
104    /// Returns a reference to the request handler.
105    #[must_use]
106    pub fn handler(&self) -> Option<&Arc<dyn FuseRequestHandler>> {
107        self.handler.as_ref()
108    }
109
110    /// Returns a reference to the FUSE session.
111    #[must_use]
112    pub const fn session(&self) -> &FuseSession {
113        &self.session
114    }
115
116    /// Returns whether the device is activated.
117    #[must_use]
118    pub const fn is_activated(&self) -> bool {
119        self.activated
120    }
121
122    /// Returns the filesystem tag.
123    #[must_use]
124    pub fn tag(&self) -> &str {
125        &self.config.tag
126    }
127
128    /// Returns the shared directory path.
129    #[must_use]
130    pub fn shared_dir(&self) -> &str {
131        &self.config.shared_dir
132    }
133
134    /// Returns the number of queues.
135    #[must_use]
136    pub const fn num_queues(&self) -> u32 {
137        self.config.num_queues
138    }
139
140    /// Returns the queue size.
141    #[must_use]
142    pub const fn queue_size(&self) -> u16 {
143        self.config.queue_size
144    }
145
146    /// Processes a FUSE request and returns the response.
147    ///
148    /// This method is called by the VMM when a request is received from
149    /// the guest via the virtqueue.
150    ///
151    /// # Flow
152    ///
153    /// 1. Parse request opcode
154    /// 2. If `FUSE_INIT`: handle initialization handshake
155    /// 3. If `FUSE_DESTROY`: clean up session
156    /// 4. Otherwise: delegate to request handler
157    ///
158    /// # Errors
159    ///
160    /// Returns an error if the request cannot be processed.
161    pub fn process_request(&mut self, request: &[u8]) -> Result<Vec<u8>> {
162        if request.len() < 40 {
163            return Err(VirtioError::DeviceError {
164                device: "fs".to_string(),
165                message: "FUSE request too small".to_string(),
166            });
167        }
168
169        // Parse opcode from header (offset 4-7)
170        let opcode = u32::from_le_bytes([request[4], request[5], request[6], request[7]]);
171
172        // Parse unique ID for error responses
173        let unique = u64::from_le_bytes([
174            request[8],
175            request[9],
176            request[10],
177            request[11],
178            request[12],
179            request[13],
180            request[14],
181            request[15],
182        ]);
183
184        match opcode {
185            Self::FUSE_INIT => {
186                let response = self.session.handle_init(request)?;
187
188                if let Some(handler) = &self.handler {
189                    handler.on_init(&self.session);
190                }
191
192                Ok(response)
193            }
194            Self::FUSE_DESTROY => {
195                self.session.reset();
196
197                if let Some(handler) = &self.handler {
198                    handler.on_destroy();
199                }
200
201                Ok(FuseResponse::new(unique, vec![]).into_data())
202            }
203            _ => {
204                if !self.session.is_initialized() {
205                    tracing::warn!("FUSE request before INIT: opcode={}", opcode);
206                    return Ok(FuseResponse::error(unique, libc::EINVAL).into_data());
207                }
208
209                if let Some(handler) = &self.handler {
210                    handler.handle_request(request)
211                } else {
212                    // No handler configured, return ENOSYS
213                    Ok(FuseResponse::error(unique, libc::ENOSYS).into_data())
214                }
215            }
216        }
217    }
218
219    /// Processes a single request queue and writes responses into guest memory.
220    ///
221    /// Returns a list of completed descriptor heads and their response lengths.
222    pub fn process_queue(
223        &mut self,
224        queue_index: usize,
225        memory: &mut [u8],
226    ) -> Result<Vec<(u16, u32)>> {
227        // First, collect all pending requests from the queue. Releases the
228        // borrow on self.request_queues.
229        let pending_requests = {
230            let queue = self.request_queues.get_mut(queue_index).ok_or_else(|| {
231                VirtioError::NotReady(format!("request queue {queue_index} not available"))
232            })?;
233
234            let mut requests = Vec::new();
235            while let Some((head_idx, chain)) = queue.pop_avail() {
236                let mut request_data = Vec::new();
237                let mut write_buffers = Vec::new();
238
239                for desc in chain {
240                    let start = desc.addr as usize;
241                    let end = start + desc.len as usize;
242                    if end > memory.len() {
243                        return Err(VirtioError::InvalidQueue(
244                            "descriptor out of bounds".to_string(),
245                        ));
246                    }
247
248                    if desc.is_write_only() {
249                        write_buffers.push((start, desc.len as usize));
250                    } else {
251                        request_data.extend_from_slice(&memory[start..end]);
252                    }
253                }
254
255                if write_buffers.is_empty() {
256                    return Err(VirtioError::InvalidQueue(
257                        "no writable descriptors for response".to_string(),
258                    ));
259                }
260
261                requests.push((head_idx, request_data, write_buffers));
262            }
263            requests
264        };
265
266        // Process requests preserving avail ring order. Control requests
267        // (INIT/DESTROY) must go through self.process_request() for session
268        // state mutation. Normal requests can be dispatched in parallel via
269        // handler when multiple are pending, but their results are collected
270        // back into original order.
271
272        type ResponseItem = (
273            u16,
274            std::result::Result<Vec<u8>, VirtioError>,
275            Vec<(usize, usize)>,
276        );
277
278        let handler = self.handler.clone();
279        let session_initialized = self.session.is_initialized();
280
281        // Parallel dispatch is only safe when the entire batch contains
282        // normal requests (no INIT/DESTROY) with valid FUSE headers (>= 40
283        // bytes). If any control request is present, fall back to sequential
284        // processing because pre-computing handler results for normal requests
285        // around a DESTROY would violate avail ring ordering.
286        let has_control = pending_requests.iter().any(|(_, data, _)| {
287            if data.len() >= 8 {
288                let op = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
289                op == Self::FUSE_INIT || op == Self::FUSE_DESTROY
290            } else {
291                true // malformed → force sequential for proper error handling
292            }
293        });
294        let all_valid = pending_requests.iter().all(|(_, data, _)| data.len() >= 40);
295        let can_parallel = !has_control
296            && all_valid
297            && pending_requests.len() > 1
298            && session_initialized
299            && handler.is_some();
300
301        let responses: Vec<ResponseItem> = if can_parallel {
302            // All requests are normal with valid headers — safe to parallelize.
303            let handler_ref = handler.as_ref().unwrap();
304            use rayon::prelude::*;
305            pending_requests
306                .into_par_iter()
307                .map(|(head_idx, data, bufs)| {
308                    let response = handler_ref.handle_request(&data);
309                    (head_idx, response, bufs)
310                })
311                .collect()
312        } else {
313            // Sequential: control ops present, malformed headers, or single request.
314            pending_requests
315                .into_iter()
316                .map(|(head_idx, request_data, write_buffers)| {
317                    let response = self.process_request(&request_data);
318                    (head_idx, response, write_buffers)
319                })
320                .collect()
321        };
322
323        // Phase 2: Write responses into guest memory (sequential)
324        // TODO(ABX-208): Use push_used_batch() for single interrupt notification
325        let mut completions = Vec::with_capacity(responses.len());
326        for (head_idx, response_result, write_buffers) in responses {
327            let response = response_result?;
328            let mut remaining = response.as_slice();
329            let mut written = 0usize;
330
331            for (start, len) in write_buffers {
332                if remaining.is_empty() {
333                    break;
334                }
335
336                let copy_len = len.min(remaining.len());
337                memory[start..start + copy_len].copy_from_slice(&remaining[..copy_len]);
338                remaining = &remaining[copy_len..];
339                written += copy_len;
340            }
341
342            if !remaining.is_empty() {
343                return Err(VirtioError::InvalidQueue(
344                    "response buffer too small".to_string(),
345                ));
346            }
347
348            completions.push((head_idx, written as u32));
349        }
350
351        Ok(completions)
352    }
353}
354
355impl VirtioDevice for VirtioFs {
356    fn device_id(&self) -> VirtioDeviceId {
357        VirtioDeviceId::Fs
358    }
359
360    fn features(&self) -> u64 {
361        self.features
362    }
363
364    fn ack_features(&mut self, features: u64) {
365        self.acked_features = self.features & features;
366    }
367
368    fn read_config(&self, offset: u64, data: &mut [u8]) {
369        // Configuration space layout:
370        // offset 0: tag (36 bytes, null-padded)
371        // offset 36: num_request_queues (u32)
372        let mut config_data = vec![0u8; 40];
373
374        let tag_bytes = self.config.tag.as_bytes();
375        let tag_len = tag_bytes.len().min(36);
376        config_data[..tag_len].copy_from_slice(&tag_bytes[..tag_len]);
377
378        config_data[36..40].copy_from_slice(&self.config.num_queues.to_le_bytes());
379
380        let offset = offset as usize;
381        let len = data.len().min(config_data.len().saturating_sub(offset));
382        if len > 0 {
383            data[..len].copy_from_slice(&config_data[offset..offset + len]);
384        }
385    }
386
387    fn write_config(&mut self, _offset: u64, _data: &[u8]) {
388        // Filesystem config is read-only
389    }
390
391    fn activate(&mut self) -> Result<()> {
392        if self.activated {
393            return Ok(());
394        }
395
396        if self.config.shared_dir.is_empty() {
397            return Err(VirtioError::DeviceError {
398                device: "fs".to_string(),
399                message: "shared_dir not configured".to_string(),
400            });
401        }
402
403        if self.config.num_queues == 0 {
404            return Err(VirtioError::DeviceError {
405                device: "fs".to_string(),
406                message: "num_queues must be greater than 0".to_string(),
407            });
408        }
409
410        self.session.reset();
411
412        let event_idx = (self.acked_features & arcbox_virtio_core::queue::VIRTIO_F_EVENT_IDX) != 0;
413        let mut queues = Vec::with_capacity(self.config.num_queues as usize);
414        for _ in 0..self.config.num_queues {
415            let mut q = VirtQueue::new(self.config.queue_size)?;
416            q.set_event_idx(event_idx);
417            queues.push(q);
418        }
419        self.request_queues = queues;
420
421        // The FUSE_INIT handshake will happen when the guest driver sends the
422        // first request through the virtqueue.
423        self.activated = true;
424
425        tracing::info!(
426            "VirtIO-FS device activated: tag='{}', shared_dir='{}', queues={}",
427            self.config.tag,
428            self.config.shared_dir,
429            self.config.num_queues
430        );
431
432        Ok(())
433    }
434
435    fn reset(&mut self) {
436        self.acked_features = 0;
437        self.session.reset();
438        self.activated = false;
439        self.request_queues.clear();
440
441        if let Some(handler) = &self.handler {
442            handler.on_destroy();
443        }
444
445        tracing::debug!("VirtIO-FS device reset: tag='{}'", self.config.tag);
446    }
447
448    fn process_queue(
449        &mut self,
450        queue_idx: u16,
451        memory: &mut [u8],
452        queue_config: &QueueConfig,
453    ) -> Result<Vec<(u16, u32)>> {
454        // Queue 0 is the hiprio/notification queue — nothing to do for now.
455        if queue_idx == 0 {
456            return Ok(Vec::new());
457        }
458
459        if !queue_config.ready || queue_config.size == 0 {
460            return Ok(Vec::new());
461        }
462
463        // Read descriptors directly from guest memory (not the internal VirtQueue).
464        // Translate GPAs to slice offsets by subtracting gpa_base (checked to
465        // guard against a malicious guest providing a GPA below the RAM base).
466        let gpa_base = queue_config.gpa_base as usize;
467        let desc_addr = (queue_config.desc_addr as usize)
468            .checked_sub(gpa_base)
469            .ok_or_else(|| {
470                tracing::warn!(
471                    "invalid desc GPA {:#x} below ram base {:#x}",
472                    queue_config.desc_addr,
473                    gpa_base
474                );
475                VirtioError::InvalidQueue("desc GPA below ram base".into())
476            })?;
477        let avail_addr = (queue_config.avail_addr as usize)
478            .checked_sub(gpa_base)
479            .ok_or_else(|| {
480                tracing::warn!(
481                    "invalid avail GPA {:#x} below ram base {:#x}",
482                    queue_config.avail_addr,
483                    gpa_base
484                );
485                VirtioError::InvalidQueue("avail GPA below ram base".into())
486            })?;
487        let used_addr = (queue_config.used_addr as usize)
488            .checked_sub(gpa_base)
489            .ok_or_else(|| {
490                tracing::warn!(
491                    "invalid used GPA {:#x} below ram base {:#x}",
492                    queue_config.used_addr,
493                    gpa_base
494                );
495                VirtioError::InvalidQueue("used GPA below ram base".into())
496            })?;
497        let q_size = queue_config.size as usize;
498
499        if avail_addr + 4 > memory.len() {
500            return Ok(Vec::new());
501        }
502        let avail_idx = u16::from_le_bytes([memory[avail_addr + 2], memory[avail_addr + 3]]);
503
504        // Track last processed index per queue. Use a simple field for queue 1.
505        let mut current_avail = self.last_avail_idx_q1;
506        let mut completions = Vec::new();
507
508        while current_avail != avail_idx {
509            let ring_off = avail_addr + 4 + 2 * (current_avail as usize % q_size);
510            if ring_off + 2 > memory.len() {
511                break;
512            }
513            let head_idx = u16::from_le_bytes([memory[ring_off], memory[ring_off + 1]]) as usize;
514
515            // Walk descriptor chain: collect request data (read-only) and
516            // response buffer locations (write-only).
517            let mut request_data = Vec::new();
518            let mut write_bufs: Vec<(usize, usize)> = Vec::new();
519            let mut idx = head_idx;
520            for _ in 0..q_size {
521                let d_off = desc_addr + idx * 16;
522                if d_off + 16 > memory.len() {
523                    break;
524                }
525                let addr = match (u64::from_le_bytes(memory[d_off..d_off + 8].try_into().unwrap())
526                    as usize)
527                    .checked_sub(gpa_base)
528                {
529                    Some(a) => a,
530                    None => continue,
531                };
532                let len =
533                    u32::from_le_bytes(memory[d_off + 8..d_off + 12].try_into().unwrap()) as usize;
534                let flags = u16::from_le_bytes(memory[d_off + 12..d_off + 14].try_into().unwrap());
535                let next = u16::from_le_bytes(memory[d_off + 14..d_off + 16].try_into().unwrap());
536
537                if flags & arcbox_virtio_core::queue::flags::WRITE != 0 {
538                    write_bufs.push((addr, len));
539                } else if addr + len <= memory.len() {
540                    request_data.extend_from_slice(&memory[addr..addr + len]);
541                }
542
543                if flags & arcbox_virtio_core::queue::flags::NEXT == 0 {
544                    break;
545                }
546                idx = next as usize;
547            }
548
549            let response = match self.process_request(&request_data) {
550                Ok(resp) => resp,
551                Err(e) => {
552                    tracing::warn!("VirtioFS FUSE request error: {e}");
553                    let unique = if request_data.len() >= 16 {
554                        u64::from_le_bytes(request_data[8..16].try_into().unwrap())
555                    } else {
556                        0
557                    };
558                    FuseResponse::error(unique, libc::EIO).into_data()
559                }
560            };
561
562            // Write response into the write-only descriptors.
563            let mut resp_offset = 0;
564            for &(buf_addr, buf_len) in &write_bufs {
565                let remaining = response.len() - resp_offset;
566                if remaining == 0 {
567                    break;
568                }
569                let to_write = remaining.min(buf_len);
570                if buf_addr + to_write <= memory.len() {
571                    memory[buf_addr..buf_addr + to_write]
572                        .copy_from_slice(&response[resp_offset..resp_offset + to_write]);
573                }
574                resp_offset += to_write;
575            }
576
577            // Update used ring.
578            let used_idx_off = used_addr + 2;
579            let used_idx = u16::from_le_bytes([memory[used_idx_off], memory[used_idx_off + 1]]);
580            let used_entry = used_addr + 4 + ((used_idx as usize) % q_size) * 8;
581            if used_entry + 8 <= memory.len() {
582                memory[used_entry..used_entry + 4]
583                    .copy_from_slice(&(head_idx as u32).to_le_bytes());
584                memory[used_entry + 4..used_entry + 8]
585                    .copy_from_slice(&(response.len() as u32).to_le_bytes());
586                std::sync::atomic::fence(std::sync::atomic::Ordering::Release);
587                let new_used = used_idx.wrapping_add(1);
588                memory[used_idx_off..used_idx_off + 2].copy_from_slice(&new_used.to_le_bytes());
589            }
590
591            // Update avail_event for EVENT_IDX notification — only when negotiated.
592            if (self.acked_features & arcbox_virtio_core::queue::VIRTIO_F_EVENT_IDX) != 0 {
593                let avail_event_off = used_addr + 4 + 8 * q_size;
594                if avail_event_off + 2 <= memory.len() {
595                    let ae = current_avail.wrapping_add(1).to_le_bytes();
596                    memory[avail_event_off] = ae[0];
597                    memory[avail_event_off + 1] = ae[1];
598                }
599            }
600
601            completions.push((head_idx as u16, response.len() as u32));
602            current_avail = current_avail.wrapping_add(1);
603        }
604
605        self.last_avail_idx_q1 = current_avail;
606        Ok(completions)
607    }
608}
609
610#[cfg(test)]
611mod tests {
612    use super::*;
613    use crate::protocol::{
614        FUSE_ASYNC_READ, FUSE_BIG_WRITES, FUSE_KERNEL_MINOR_VERSION, FUSE_KERNEL_VERSION,
615    };
616    use arcbox_virtio_core::queue::flags;
617    use std::sync::atomic::{AtomicUsize, Ordering};
618
619    #[test]
620    fn test_fs_config_default() {
621        let config = FsConfig::default();
622        assert_eq!(config.tag, "arcbox");
623        assert_eq!(config.num_queues, 1);
624        assert_eq!(config.queue_size, 1024);
625        assert!(config.shared_dir.is_empty());
626    }
627
628    #[test]
629    fn test_fs_config_custom() {
630        let config = FsConfig {
631            tag: "myfs".to_string(),
632            num_queues: 4,
633            queue_size: 256,
634            shared_dir: "/home/user/shared".to_string(),
635        };
636        assert_eq!(config.tag, "myfs");
637        assert_eq!(config.num_queues, 4);
638        assert_eq!(config.queue_size, 256);
639        assert_eq!(config.shared_dir, "/home/user/shared");
640    }
641
642    #[test]
643    fn test_fs_config_clone() {
644        let config = FsConfig {
645            tag: "test".to_string(),
646            num_queues: 2,
647            queue_size: 512,
648            shared_dir: "/tmp".to_string(),
649        };
650        let cloned = config.clone();
651        assert_eq!(cloned.tag, "test");
652        assert_eq!(cloned.num_queues, 2);
653    }
654
655    #[test]
656    fn test_fs_new() {
657        let fs = VirtioFs::new(FsConfig::default());
658        assert_eq!(fs.tag(), "arcbox");
659        assert!(fs.shared_dir().is_empty());
660    }
661
662    #[test]
663    fn test_fs_device_id() {
664        let fs = VirtioFs::new(FsConfig::default());
665        assert_eq!(fs.device_id(), VirtioDeviceId::Fs);
666    }
667
668    #[test]
669    fn test_fs_features() {
670        let fs = VirtioFs::new(FsConfig::default());
671        assert_ne!(
672            fs.features() & (1 << virtio_bindings::virtio_config::VIRTIO_F_VERSION_1),
673            0
674        );
675    }
676
677    #[test]
678    fn test_fs_ack_features() {
679        let mut fs = VirtioFs::new(FsConfig::default());
680        fs.ack_features(VirtioFs::FEATURE_NOTIFICATION);
681        assert_eq!(fs.acked_features & VirtioFs::FEATURE_NOTIFICATION, 0);
682    }
683
684    #[test]
685    fn test_fs_read_config_tag() {
686        let config = FsConfig {
687            tag: "testfs".to_string(),
688            ..Default::default()
689        };
690        let fs = VirtioFs::new(config);
691
692        let mut data = [0u8; 36];
693        fs.read_config(0, &mut data);
694
695        assert_eq!(&data[0..6], b"testfs");
696        assert!(data[6..].iter().all(|&b| b == 0));
697    }
698
699    #[test]
700    fn test_fs_read_config_tag_long() {
701        let config = FsConfig {
702            tag: "a".repeat(50),
703            ..Default::default()
704        };
705        let fs = VirtioFs::new(config);
706
707        let mut data = [0u8; 36];
708        fs.read_config(0, &mut data);
709
710        assert!(data.iter().all(|&b| b == b'a'));
711    }
712
713    #[test]
714    fn test_fs_read_config_num_queues() {
715        let config = FsConfig {
716            num_queues: 4,
717            ..Default::default()
718        };
719        let fs = VirtioFs::new(config);
720
721        let mut data = [0u8; 4];
722        fs.read_config(36, &mut data);
723
724        let num_queues = u32::from_le_bytes(data);
725        assert_eq!(num_queues, 4);
726    }
727
728    #[test]
729    fn test_fs_read_config_partial() {
730        let fs = VirtioFs::new(FsConfig::default());
731
732        let mut data = [0u8; 10];
733        fs.read_config(35, &mut data);
734    }
735
736    #[test]
737    fn test_fs_read_config_beyond() {
738        let fs = VirtioFs::new(FsConfig::default());
739
740        let mut data = [0xFFu8; 4];
741        fs.read_config(100, &mut data);
742    }
743
744    #[test]
745    fn test_fs_write_config_noop() {
746        let config = FsConfig {
747            tag: "original".to_string(),
748            ..Default::default()
749        };
750        let mut fs = VirtioFs::new(config);
751
752        fs.write_config(0, b"newvalue");
753
754        assert_eq!(fs.tag(), "original");
755    }
756
757    #[test]
758    fn test_fs_activate() {
759        let config = FsConfig {
760            shared_dir: "/tmp".to_string(),
761            ..Default::default()
762        };
763        let mut fs = VirtioFs::new(config);
764        assert!(!fs.is_activated());
765
766        assert!(fs.activate().is_ok());
767        assert!(fs.is_activated());
768        assert_eq!(fs.request_queues.len(), 1);
769
770        // Activating again should be idempotent
771        assert!(fs.activate().is_ok());
772    }
773
774    #[test]
775    fn test_fs_activate_no_shared_dir() {
776        let mut fs = VirtioFs::new(FsConfig::default());
777        assert!(fs.activate().is_err());
778    }
779
780    #[test]
781    fn test_fs_reset() {
782        let config = FsConfig {
783            shared_dir: "/tmp".to_string(),
784            ..Default::default()
785        };
786        let mut fs = VirtioFs::new(config);
787        fs.acked_features = 0xFF;
788        fs.activate().unwrap();
789
790        fs.reset();
791
792        assert_eq!(fs.acked_features, 0);
793        assert!(!fs.is_activated());
794        assert!(!fs.session.is_initialized());
795        assert!(fs.request_queues.is_empty());
796    }
797
798    #[test]
799    fn test_fs_tag_accessor() {
800        let config = FsConfig {
801            tag: "mytag".to_string(),
802            ..Default::default()
803        };
804        let fs = VirtioFs::new(config);
805        assert_eq!(fs.tag(), "mytag");
806    }
807
808    #[test]
809    fn test_fs_shared_dir_accessor() {
810        let config = FsConfig {
811            shared_dir: "/mnt/share".to_string(),
812            ..Default::default()
813        };
814        let fs = VirtioFs::new(config);
815        assert_eq!(fs.shared_dir(), "/mnt/share");
816    }
817
818    #[test]
819    fn test_fs_feature_constants() {
820        assert_eq!(VirtioFs::FEATURE_NOTIFICATION, 1 << 0);
821    }
822
823    #[test]
824    fn test_fs_activate_creates_multiple_queues() {
825        let config = FsConfig {
826            shared_dir: "/tmp".to_string(),
827            num_queues: 2,
828            queue_size: 16,
829            ..Default::default()
830        };
831        let mut fs = VirtioFs::new(config);
832        fs.activate().unwrap();
833        assert_eq!(fs.request_queues.len(), 2);
834    }
835
836    #[test]
837    fn test_fs_process_queue_roundtrip() {
838        struct TestHandler {
839            calls: Arc<AtomicUsize>,
840        }
841
842        impl FuseRequestHandler for TestHandler {
843            fn handle_request(&self, request: &[u8]) -> Result<Vec<u8>> {
844                self.calls.fetch_add(1, Ordering::SeqCst);
845                let unique = u64::from_le_bytes([
846                    request[8],
847                    request[9],
848                    request[10],
849                    request[11],
850                    request[12],
851                    request[13],
852                    request[14],
853                    request[15],
854                ]);
855                Ok(FuseResponse::new(unique, b"ok".to_vec()).into_data())
856            }
857        }
858
859        let calls = Arc::new(AtomicUsize::new(0));
860        let handler = Arc::new(TestHandler {
861            calls: Arc::clone(&calls),
862        });
863
864        let config = FsConfig {
865            shared_dir: "/tmp".to_string(),
866            queue_size: 16,
867            ..Default::default()
868        };
869        let mut fs = VirtioFs::with_handler(config, handler);
870        fs.activate().unwrap();
871
872        let queue = fs.request_queues.get_mut(0).unwrap();
873        let mut memory = vec![0u8; 1024];
874
875        // Build FUSE_INIT request
876        let mut init_req = vec![0u8; 56];
877        init_req[0..4].copy_from_slice(&56u32.to_le_bytes());
878        init_req[4..8].copy_from_slice(&VirtioFs::FUSE_INIT.to_le_bytes());
879        init_req[8..16].copy_from_slice(&1u64.to_le_bytes());
880        init_req[40..44].copy_from_slice(&FUSE_KERNEL_VERSION.to_le_bytes());
881        init_req[44..48].copy_from_slice(&FUSE_KERNEL_MINOR_VERSION.to_le_bytes());
882        init_req[48..52].copy_from_slice(&(64 * 1024u32).to_le_bytes());
883        init_req[52..56].copy_from_slice(&(FUSE_ASYNC_READ | FUSE_BIG_WRITES).to_le_bytes());
884
885        let init_req_offset = 0usize;
886        let init_resp_offset = 128usize;
887        memory[init_req_offset..init_req_offset + init_req.len()].copy_from_slice(&init_req);
888
889        queue
890            .set_descriptor(
891                0,
892                arcbox_virtio_core::queue::Descriptor {
893                    addr: init_req_offset as u64,
894                    len: init_req.len() as u32,
895                    flags: flags::NEXT,
896                    next: 1,
897                },
898            )
899            .unwrap();
900        queue
901            .set_descriptor(
902                1,
903                arcbox_virtio_core::queue::Descriptor {
904                    addr: init_resp_offset as u64,
905                    len: 80,
906                    flags: flags::WRITE,
907                    next: 0,
908                },
909            )
910            .unwrap();
911
912        // Build a simple FUSE request that goes to the handler
913        let mut other_req = vec![0u8; 40];
914        other_req[0..4].copy_from_slice(&40u32.to_le_bytes());
915        other_req[4..8].copy_from_slice(&1u32.to_le_bytes());
916        other_req[8..16].copy_from_slice(&2u64.to_le_bytes());
917
918        let other_req_offset = 256usize;
919        let other_resp_offset = 512usize;
920        memory[other_req_offset..other_req_offset + other_req.len()].copy_from_slice(&other_req);
921
922        queue
923            .set_descriptor(
924                2,
925                arcbox_virtio_core::queue::Descriptor {
926                    addr: other_req_offset as u64,
927                    len: other_req.len() as u32,
928                    flags: flags::NEXT,
929                    next: 3,
930                },
931            )
932            .unwrap();
933        queue
934            .set_descriptor(
935                3,
936                arcbox_virtio_core::queue::Descriptor {
937                    addr: other_resp_offset as u64,
938                    len: 32,
939                    flags: flags::WRITE,
940                    next: 0,
941                },
942            )
943            .unwrap();
944
945        queue.add_avail(0).unwrap();
946        queue.add_avail(2).unwrap();
947
948        let completions = fs.process_queue(0, &mut memory).unwrap();
949        assert_eq!(completions.len(), 2);
950        assert!(fs.session.is_initialized());
951        assert_eq!(calls.load(Ordering::SeqCst), 1);
952
953        let response = &memory[other_resp_offset..other_resp_offset + 18];
954        assert_eq!(&response[16..18], b"ok");
955    }
956
957    #[test]
958    fn test_fs_process_request_too_small() {
959        let config = FsConfig {
960            shared_dir: "/tmp".to_string(),
961            ..Default::default()
962        };
963        let mut fs = VirtioFs::new(config);
964        fs.activate().unwrap();
965
966        let result = fs.process_request(&[0u8; 20]);
967        assert!(result.is_err());
968    }
969
970    #[test]
971    fn test_fs_process_request_before_init() {
972        let config = FsConfig {
973            shared_dir: "/tmp".to_string(),
974            ..Default::default()
975        };
976        let mut fs = VirtioFs::new(config);
977        fs.activate().unwrap();
978
979        // Send a non-INIT request before initialization
980        let mut request = vec![0u8; 40];
981        request[4..8].copy_from_slice(&1u32.to_le_bytes()); // LOOKUP opcode
982        request[8..16].copy_from_slice(&1u64.to_le_bytes()); // unique
983
984        let response = fs.process_request(&request).unwrap();
985
986        let error = i32::from_le_bytes([response[4], response[5], response[6], response[7]]);
987        assert_eq!(error, -libc::EINVAL);
988    }
989
990    #[test]
991    fn test_fs_process_request_no_handler() {
992        let config = FsConfig {
993            shared_dir: "/tmp".to_string(),
994            ..Default::default()
995        };
996        let mut fs = VirtioFs::new(config);
997        fs.activate().unwrap();
998
999        // First, send FUSE_INIT
1000        let mut init_request = vec![0u8; 56];
1001        init_request[4..8].copy_from_slice(&26u32.to_le_bytes()); // INIT opcode
1002        init_request[8..16].copy_from_slice(&1u64.to_le_bytes());
1003        init_request[40..44].copy_from_slice(&FUSE_KERNEL_VERSION.to_le_bytes());
1004        init_request[44..48].copy_from_slice(&FUSE_KERNEL_MINOR_VERSION.to_le_bytes());
1005        init_request[48..52].copy_from_slice(&(64 * 1024u32).to_le_bytes());
1006        init_request[52..56].copy_from_slice(&0u32.to_le_bytes());
1007
1008        fs.process_request(&init_request).unwrap();
1009        assert!(fs.session.is_initialized());
1010
1011        // Now send another request - should get ENOSYS since no handler
1012        let mut request = vec![0u8; 40];
1013        request[4..8].copy_from_slice(&1u32.to_le_bytes()); // LOOKUP opcode
1014        request[8..16].copy_from_slice(&2u64.to_le_bytes()); // unique
1015
1016        let response = fs.process_request(&request).unwrap();
1017
1018        let error = i32::from_le_bytes([response[4], response[5], response[6], response[7]]);
1019        assert_eq!(error, -libc::ENOSYS);
1020    }
1021
1022    #[test]
1023    fn test_fs_with_handler() {
1024        use std::sync::atomic::AtomicU32;
1025
1026        struct TestHandler {
1027            call_count: AtomicU32,
1028        }
1029
1030        impl FuseRequestHandler for TestHandler {
1031            fn handle_request(&self, _request: &[u8]) -> Result<Vec<u8>> {
1032                self.call_count.fetch_add(1, Ordering::SeqCst);
1033                Ok(FuseResponse::new(0, vec![]).into_data())
1034            }
1035        }
1036
1037        let handler = Arc::new(TestHandler {
1038            call_count: AtomicU32::new(0),
1039        });
1040
1041        let config = FsConfig {
1042            shared_dir: "/tmp".to_string(),
1043            ..Default::default()
1044        };
1045        let mut fs = VirtioFs::with_handler(config, handler.clone());
1046        fs.activate().unwrap();
1047
1048        // Send FUSE_INIT
1049        let mut init_request = vec![0u8; 56];
1050        init_request[4..8].copy_from_slice(&26u32.to_le_bytes());
1051        init_request[8..16].copy_from_slice(&1u64.to_le_bytes());
1052        init_request[40..44].copy_from_slice(&FUSE_KERNEL_VERSION.to_le_bytes());
1053        init_request[44..48].copy_from_slice(&FUSE_KERNEL_MINOR_VERSION.to_le_bytes());
1054        init_request[48..52].copy_from_slice(&(64 * 1024u32).to_le_bytes());
1055        init_request[52..56].copy_from_slice(&0u32.to_le_bytes());
1056
1057        fs.process_request(&init_request).unwrap();
1058
1059        // Send a regular request
1060        let mut request = vec![0u8; 40];
1061        request[4..8].copy_from_slice(&1u32.to_le_bytes());
1062        request[8..16].copy_from_slice(&2u64.to_le_bytes());
1063
1064        fs.process_request(&request).unwrap();
1065
1066        assert_eq!(handler.call_count.load(Ordering::SeqCst), 1);
1067    }
1068}