virtio_p9/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![expect(missing_docs)]
5#![forbid(unsafe_code)]
6#![cfg(any(windows, target_os = "linux"))]
7
8pub mod resolver;
9
10use async_trait::async_trait;
11use guestmem::GuestMemory;
12use plan9::Plan9FileSystem;
13use std::sync::Arc;
14use virtio::DeviceTraits;
15use virtio::LegacyVirtioDevice;
16use virtio::VirtioQueueCallbackWork;
17use virtio::VirtioQueueWorkerContext;
18use virtio::VirtioState;
19
20const VIRTIO_DEVICE_TYPE_9P_TRANSPORT: u16 = 9;
21
22const VIRTIO_9P_F_MOUNT_TAG: u64 = 1;
23
24pub struct VirtioPlan9Device {
25    fs: Arc<Plan9FileSystem>,
26    tag: Vec<u8>,
27    memory: GuestMemory,
28}
29
30impl VirtioPlan9Device {
31    pub fn new(tag: &str, fs: Plan9FileSystem, memory: GuestMemory) -> VirtioPlan9Device {
32        // The tag uses the same format as 9p protocol strings (2 byte length followed by string).
33        let length = tag.len() + size_of::<u16>();
34
35        // Round the length up to a multiple of 4 to make the read function simpler.
36        let length = (length + 3) & !3;
37        let mut tag_buffer = vec![0u8; length];
38
39        // Write a string preceded by a two byte length.
40        {
41            use std::io::Write;
42            let mut cursor = std::io::Cursor::new(&mut tag_buffer);
43            cursor.write_all(&(tag.len() as u16).to_le_bytes()).unwrap();
44            cursor.write_all(tag.as_bytes()).unwrap();
45        }
46
47        VirtioPlan9Device {
48            fs: Arc::new(fs),
49            tag: tag_buffer,
50            memory,
51        }
52    }
53}
54
55impl LegacyVirtioDevice for VirtioPlan9Device {
56    fn traits(&self) -> DeviceTraits {
57        DeviceTraits {
58            device_id: VIRTIO_DEVICE_TYPE_9P_TRANSPORT,
59            device_features: VIRTIO_9P_F_MOUNT_TAG,
60            max_queues: 1,
61            device_register_length: self.tag.len() as u32,
62            ..Default::default()
63        }
64    }
65
66    fn read_registers_u32(&self, offset: u16) -> u32 {
67        assert!(self.tag.len() % 4 == 0);
68        assert!(offset % 4 == 0);
69
70        let offset = offset as usize;
71        if offset < self.tag.len() {
72            u32::from_le_bytes(
73                self.tag[offset..offset + 4]
74                    .try_into()
75                    .expect("Incorrect length"),
76            )
77        } else {
78            0
79        }
80    }
81
82    fn write_registers_u32(&mut self, offset: u16, val: u32) {
83        tracing::warn!(offset, val, "[VIRTIO 9P] Unknown write",);
84    }
85
86    fn get_work_callback(&mut self, index: u16) -> Box<dyn VirtioQueueWorkerContext + Send> {
87        assert!(index == 0);
88        Box::new(VirtioPlan9Worker {
89            mem: self.memory.clone(),
90            fs: self.fs.clone(),
91        })
92    }
93
94    fn state_change(&mut self, _: &VirtioState) {}
95}
96
97struct VirtioPlan9Worker {
98    mem: GuestMemory,
99    fs: Arc<Plan9FileSystem>,
100}
101
102#[async_trait]
103impl VirtioQueueWorkerContext for VirtioPlan9Worker {
104    async fn process_work(&mut self, work: anyhow::Result<VirtioQueueCallbackWork>) -> bool {
105        if let Err(err) = work {
106            tracing::error!(err = err.as_ref() as &dyn std::error::Error, "queue error");
107            return false;
108        }
109        let mut work = work.unwrap();
110        // Make a copy of the incoming message.
111        let mut message = vec![0; work.get_payload_length(false) as usize];
112        if let Err(e) = work.read(&self.mem, &mut message) {
113            tracing::error!(
114                error = &e as &dyn std::error::Error,
115                "[VIRTIO 9P] Failed to read guest memory"
116            );
117            return false;
118        }
119
120        // Allocate a temporary buffer for the response.
121        let mut response = vec![9; work.get_payload_length(true) as usize];
122        if let Ok(size) = self.fs.process_message(&message, &mut response) {
123            // Write out the response.
124            if let Err(e) = work.write(&self.mem, &response[0..size]) {
125                tracing::error!(
126                    error = &e as &dyn std::error::Error,
127                    "[VIRTIO 9P] Failed to write guest memory"
128                );
129                return false;
130            }
131
132            work.complete(size as u32);
133        }
134        true
135    }
136}