1#![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
27const 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 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 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 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 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 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 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)?; response.u64(0)?; response.u64(0)?; response.u64(0)?; Ok(())
196 }
197
198 pub fn handle_walk(
199 &self,
200 message: Twalk<'_>,
201 response: &mut SliceWriter<'_>,
202 ) -> lx::Result<()> {
203 let item = self.lookup_fid(message.fid)?.fid_clone();
205
206 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 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 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 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}