plan9/
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
8mod fid;
9mod protocol;
10
11pub use lx::Error;
12
13use fid::*;
14use lxutil::LxVolume;
15use parking_lot::RwLock;
16use protocol::*;
17use std::collections::HashMap;
18use std::collections::hash_map::Entry;
19use std::str;
20use std::sync::Arc;
21use std::sync::atomic::AtomicU32;
22use std::sync::atomic::Ordering;
23
24const MINIMUM_REQUEST_BUFFER_SIZE: u32 = 4096;
25const MAXIMUM_REQUEST_BUFFER_SIZE: u32 = 256 * 1024;
26
27// The maximum size of an IO request (0 means no limit).
28const IO_UNIT: u32 = 0;
29
30pub struct Plan9FileSystem {
31    negotiated_size: AtomicU32,
32    fids: RwLock<HashMap<u32, Arc<dyn Fid>>>,
33    root: Arc<LxVolume>,
34    debug: bool,
35}
36
37impl Plan9FileSystem {
38    pub fn new(root_path: &str, debug: bool) -> lx::Result<Plan9FileSystem> {
39        let root = Arc::new(LxVolume::new(root_path)?);
40        Ok(Plan9FileSystem {
41            negotiated_size: AtomicU32::new(0),
42            fids: RwLock::new(HashMap::new()),
43            root,
44            debug,
45        })
46    }
47
48    // Process a message received from virtio.
49    pub fn process_message(&self, message: &[u8], response: &mut [u8]) -> lx::Result<usize> {
50        let mut reader = SliceReader::new(message);
51        let header = reader.header()?;
52
53        let mut writer = SliceWriter::new(response);
54        if let Err(errno) = self.handle_message(&header, reader, &mut writer) {
55            writer.reset();
56            writer.u32(errno.value() as u32)?;
57            writer.header(MESSAGE_RLERROR, header.tag)?;
58        } else {
59            writer.header(header.message_type + 1, header.tag)?;
60        }
61
62        let size = writer.size();
63        if self.debug {
64            Self::log_response(&response[..size]);
65        }
66
67        Ok(size)
68    }
69
70    // Dispatch a message to the correct handle function.
71    pub fn handle_message(
72        &self,
73        header: &Header,
74        reader: SliceReader<'_>,
75        response: &mut SliceWriter<'_>,
76    ) -> lx::Result<()> {
77        let msg = Plan9Message::read(header.message_type, reader)?;
78        if self.debug {
79            tracing::info!(
80                message_type = header.message_type,
81                tag = header.tag,
82                ?msg,
83                "[9P] message",
84            );
85        }
86
87        match msg {
88            Plan9Message::Tlopen(m) => self.handle_lopen(m, response),
89            Plan9Message::Tlcreate(m) => self.handle_lcreate(m, response),
90            Plan9Message::Tgetattr(m) => self.handle_get_attr(m, response),
91            // Setattr is not supported but returns success to unblock many scenarios.
92            Plan9Message::Tsetattr(_) => self.handle_ignored("Tsetattr"),
93            Plan9Message::Treaddir(m) => self.handle_read_dir(m, response),
94            Plan9Message::Tmkdir(m) => self.handle_mkdir(m, response),
95            Plan9Message::Tunlinkat(m) => self.handle_unlinkat(m),
96            Plan9Message::Tversion(m) => self.handle_version(m, response),
97            Plan9Message::Tattach(m) => self.handle_attach(m, response),
98            Plan9Message::Twalk(m) => self.handle_walk(m, response),
99            Plan9Message::Tread(m) => self.handle_read(m, response),
100            Plan9Message::Twrite(m) => self.handle_write(m, response),
101            Plan9Message::Tclunk(m) => self.handle_clunk(m),
102            _ => {
103                tracing::warn!(message_type = header.message_type, "Unhandled message type");
104                Err(Error::ENOTSUP)
105            }
106        }
107    }
108
109    pub fn handle_ignored(&self, msg: &str) -> lx::Result<()> {
110        if self.debug {
111            tracing::warn!(msg, "[9P] Ignored message");
112        }
113
114        Ok(())
115    }
116
117    pub fn handle_version(
118        &self,
119        message: Tversion<'_>,
120        response: &mut SliceWriter<'_>,
121    ) -> lx::Result<()> {
122        let old_size = self.negotiated_size.load(Ordering::SeqCst);
123
124        if message.msize < MINIMUM_REQUEST_BUFFER_SIZE {
125            return Err(Error::ENOTSUP);
126        }
127
128        if message.version != PROTOCOL_VERSION {
129            return Err(Error::ENOTSUP);
130        }
131
132        let negotiated_size = std::cmp::min(message.msize, MAXIMUM_REQUEST_BUFFER_SIZE);
133
134        // Renegotiation is allowed, particularly because this implementation doesn't really use
135        // the size for anything since it's virtio only, but still prevent multiple changes at once.
136        self.negotiated_size
137            .compare_exchange(
138                old_size,
139                negotiated_size,
140                Ordering::SeqCst,
141                Ordering::SeqCst,
142            )
143            .map_err(|_| Error::EINVAL)?;
144
145        response.u32(negotiated_size)?;
146        response.string(message.version)?;
147        Ok(())
148    }
149
150    pub fn handle_attach(
151        &self,
152        message: Tattach<'_>,
153        response: &mut SliceWriter<'_>,
154    ) -> lx::Result<()> {
155        // Create the fid for the root.
156        let (file, qid) = File::new(Arc::clone(&self.root), message.n_uname)?;
157        self.emplace_fid(message.fid, Arc::new(file))?;
158        response.qid(&qid)?;
159        Ok(())
160    }
161
162    pub fn handle_clunk(&self, message: Tclunk<'_>) -> lx::Result<()> {
163        match self.remove_fid(message.fid) {
164            Some(item) => item.clunk(),
165            None => Err(Error::EINVAL),
166        }
167    }
168
169    pub fn handle_get_attr(
170        &self,
171        message: Tgetattr<'_>,
172        response: &mut SliceWriter<'_>,
173    ) -> lx::Result<()> {
174        let file = self.lookup_fid(message.fid)?;
175        let (qid, stat) = file.get_attr()?;
176
177        // Mask is just echoed back; all fields are valid.
178        response.u64(message.request_mask)?;
179        response.qid(&qid)?;
180        response.u32(stat.mode)?;
181        response.u32(stat.uid)?;
182        response.u32(stat.gid)?;
183        response.u64(stat.link_count as u64)?;
184        response.u64(stat.device_nr_special)?;
185        response.u64(stat.file_size)?;
186        response.u64(stat.block_size as u64)?;
187        response.u64(stat.block_count)?;
188        response.timespec(&stat.access_time)?;
189        response.timespec(&stat.write_time)?;
190        response.timespec(&stat.change_time)?;
191        response.u64(0)?; // btime sec (reserved)
192        response.u64(0)?; // btime nsec (reserved)
193        response.u64(0)?; // gen (reserved)
194        response.u64(0)?; // data version (reserved)
195        Ok(())
196    }
197
198    pub fn handle_walk(
199        &self,
200        message: Twalk<'_>,
201        response: &mut SliceWriter<'_>,
202    ) -> lx::Result<()> {
203        // Create a new fid for the walk.
204        let item = self.lookup_fid(message.fid)?.fid_clone();
205
206        // Walk each name and write the response.
207        response.u16(message.wnames.name_count())?;
208        for name in message.wnames {
209            let qid = item.walk(name?)?;
210            response.qid(&qid)?;
211        }
212
213        self.emplace_fid(message.newfid, item)?;
214        Ok(())
215    }
216
217    pub fn handle_lopen(
218        &self,
219        message: Tlopen<'_>,
220        response: &mut SliceWriter<'_>,
221    ) -> lx::Result<()> {
222        let item = self.lookup_fid(message.fid)?;
223        let qid = item.open(message.flags)?;
224        response.qid(&qid)?;
225        response.u32(IO_UNIT)?;
226        Ok(())
227    }
228
229    pub fn handle_lcreate(
230        &self,
231        message: Tlcreate<'_>,
232        response: &mut SliceWriter<'_>,
233    ) -> lx::Result<()> {
234        let item = self.lookup_fid(message.fid)?;
235        let qid = item.create(message.name, message.flags, message.mode, message.gid)?;
236        response.qid(&qid)?;
237        response.u32(IO_UNIT)?;
238        Ok(())
239    }
240
241    pub fn handle_read(
242        &self,
243        message: Tread<'_>,
244        response: &mut SliceWriter<'_>,
245    ) -> lx::Result<()> {
246        let file = self.lookup_fid(message.fid)?;
247        let start = size_of::<u32>();
248        let end = start + message.count as usize;
249        let size = file.read(message.offset, response.peek(start..end)?)?;
250        response.u32(size)?;
251        response.next(size as usize)?;
252        Ok(())
253    }
254
255    pub fn handle_write(
256        &self,
257        mut message: Twrite<'_>,
258        response: &mut SliceWriter<'_>,
259    ) -> lx::Result<()> {
260        let file = self.lookup_fid(message.fid)?;
261        let data = message.reader.read(message.count as usize)?;
262        let size = file.write(message.offset, data)?;
263        response.u32(size)?;
264        Ok(())
265    }
266
267    pub fn handle_read_dir(
268        &self,
269        message: Treaddir<'_>,
270        response: &mut SliceWriter<'_>,
271    ) -> lx::Result<()> {
272        let file = self.lookup_fid(message.fid)?;
273        let start = size_of::<u32>();
274        let end = start + message.count as usize;
275        let size = file.read_dir(message.offset, response.peek(start..end)?)?;
276        response.u32(size)?;
277        response.next(size as usize)?;
278        Ok(())
279    }
280
281    pub fn handle_mkdir(
282        &self,
283        message: Tmkdir<'_>,
284        response: &mut SliceWriter<'_>,
285    ) -> lx::Result<()> {
286        let dir = self.lookup_fid(message.dfid)?;
287        let qid = dir.mkdir(message.name, message.mode, message.gid)?;
288        response.qid(&qid)?;
289        Ok(())
290    }
291
292    pub fn handle_unlinkat(&self, message: Tunlinkat<'_>) -> lx::Result<()> {
293        let dir = self.lookup_fid(message.dfid)?;
294        dir.unlink_at(message.name, message.flags)?;
295        Ok(())
296    }
297
298    // Store a new fid. It's an error if the fid already exists.
299    fn emplace_fid(&self, fid: u32, item: Arc<dyn Fid>) -> lx::Result<()> {
300        let mut fids = self.fids.write();
301        match fids.entry(fid) {
302            Entry::Occupied(_) => return Err(Error::EINVAL),
303            Entry::Vacant(v) => v.insert(item),
304        };
305
306        Ok(())
307    }
308
309    // Find a fid with the specified number.
310    fn lookup_fid(&self, fid: u32) -> lx::Result<Arc<dyn Fid>> {
311        let fids = self.fids.read();
312        if let Some(item) = fids.get(&fid) {
313            return Ok(Arc::clone(item));
314        }
315
316        Err(Error::EINVAL)
317    }
318
319    // Remove a fid from the collection.
320    fn remove_fid(&self, fid: u32) -> Option<Arc<dyn Fid>> {
321        let mut fids = self.fids.write();
322        fids.remove(&fid)
323    }
324
325    fn log_response(response: &[u8]) {
326        let mut reader = SliceReader::new(response);
327        if let Ok(header) = reader.header() {
328            if let Ok(msg) = Plan9Message::read(header.message_type, reader) {
329                tracing::info!(
330                    message_type = header.message_type,
331                    tag = header.tag,
332                    ?msg,
333                    "[9P] Response",
334                );
335            }
336        }
337    }
338}