From f532ff267ec1397cdb5c425b609f01ea70bcc564 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Tue, 3 Feb 2026 14:52:39 -0800 Subject: [PATCH 01/16] add 9p fs --- litebox/src/fs/nine_p.rs | 143 -- litebox/src/fs/nine_p/client.rs | 511 ++++++ litebox/src/fs/nine_p/cursor.rs | 157 ++ litebox/src/fs/nine_p/fcall.rs | 2399 ++++++++++++++++++++++++++++ litebox/src/fs/nine_p/mod.rs | 806 ++++++++++ litebox/src/fs/nine_p/transport.rs | 87 + 6 files changed, 3960 insertions(+), 143 deletions(-) delete mode 100644 litebox/src/fs/nine_p.rs create mode 100644 litebox/src/fs/nine_p/client.rs create mode 100644 litebox/src/fs/nine_p/cursor.rs create mode 100644 litebox/src/fs/nine_p/fcall.rs create mode 100644 litebox/src/fs/nine_p/mod.rs create mode 100644 litebox/src/fs/nine_p/transport.rs diff --git a/litebox/src/fs/nine_p.rs b/litebox/src/fs/nine_p.rs deleted file mode 100644 index 968887c15..000000000 --- a/litebox/src/fs/nine_p.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -//! A network file system, using the 9p protocol - -use crate::{LiteBox, platform}; - -/// A backing implementation for [`FileSystem`](super::FileSystem) using a 9p-based network file -/// system. -// TODO(jayb): Reduce the requirements necessary on `Platform` to the most precise one possible. -pub struct FileSystem { - #[expect(dead_code, reason = "placeholder, currently nine_p is unimplemented")] - litebox: LiteBox, -} - -impl FileSystem { - /// Construct a new `FileSystem` instance - /// - /// This function is expected to only be invoked once per platform, as an initialiation step, - /// and the created `FileSystem` handle is expected to be shared across all usage over the - /// system. - #[must_use] - pub fn new(litebox: &LiteBox) -> Self { - Self { - litebox: litebox.clone(), - } - } -} - -impl super::private::Sealed for FileSystem {} - -#[expect(unused_variables, reason = "unimplemented")] -impl super::FileSystem for FileSystem { - fn open( - &self, - path: impl crate::path::Arg, - flags: super::OFlags, - mode: super::Mode, - ) -> Result, super::errors::OpenError> { - todo!() - } - - fn close(&self, fd: &FileFd) -> Result<(), super::errors::CloseError> { - todo!() - } - - fn read( - &self, - fd: &FileFd, - buf: &mut [u8], - offset: Option, - ) -> Result { - todo!() - } - - fn write( - &self, - fd: &FileFd, - buf: &[u8], - offset: Option, - ) -> Result { - todo!() - } - - fn seek( - &self, - fd: &FileFd, - offset: isize, - whence: super::SeekWhence, - ) -> Result { - todo!() - } - - fn truncate( - &self, - fd: &FileFd, - length: usize, - reset_offset: bool, - ) -> Result<(), super::errors::TruncateError> { - todo!() - } - - fn chmod( - &self, - path: impl crate::path::Arg, - mode: super::Mode, - ) -> Result<(), super::errors::ChmodError> { - todo!() - } - - fn chown( - &self, - path: impl crate::path::Arg, - user: Option, - group: Option, - ) -> Result<(), super::errors::ChownError> { - todo!() - } - - fn unlink(&self, path: impl crate::path::Arg) -> Result<(), super::errors::UnlinkError> { - todo!() - } - - fn mkdir( - &self, - path: impl crate::path::Arg, - mode: super::Mode, - ) -> Result<(), super::errors::MkdirError> { - todo!() - } - - fn rmdir(&self, path: impl crate::path::Arg) -> Result<(), super::errors::RmdirError> { - todo!() - } - - fn read_dir( - &self, - fd: &FileFd, - ) -> Result, super::errors::ReadDirError> { - todo!() - } - - fn file_status( - &self, - path: impl crate::path::Arg, - ) -> Result { - todo!() - } - - fn fd_file_status( - &self, - fd: &FileFd, - ) -> Result { - todo!() - } -} - -crate::fd::enable_fds_for_subsystem! { - @Platform: { platform::Provider + Sync + 'static }; - FileSystem; - (); - -> FileFd; -} diff --git a/litebox/src/fs/nine_p/client.rs b/litebox/src/fs/nine_p/client.rs new file mode 100644 index 000000000..b09d9a562 --- /dev/null +++ b/litebox/src/fs/nine_p/client.rs @@ -0,0 +1,511 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +//! 9P client implementation +//! +//! This module provides a high-level client for the 9P2000.L protocol. + +use alloc::vec; +use alloc::vec::Vec; +use core::sync::atomic::{AtomicU32, Ordering}; + +use crate::sync::{Mutex, RawSyncPrimitivesProvider}; + +use super::Error; +use super::fcall::{self, Fcall, FcallStr, GetattrMask, TaggedFcall}; +use super::transport::{self, Read, Write}; + +/// Client identifier for lock operations +pub(crate) const CLIENT_ID: &str = "litebox-9p"; + +/// ID generator for fids +struct IdGenerator { + next: u32, + free_ids: Vec, +} + +impl IdGenerator { + const fn new() -> Self { + IdGenerator { + next: 0, + free_ids: Vec::new(), + } + } + + fn next(&mut self) -> u32 { + if let Some(id) = self.free_ids.pop() { + id + } else { + let id = self.next; + self.next = self.next.checked_add(1).expect("out of fids"); + id + } + } + + fn free(&mut self, id: u32) { + self.free_ids.push(id); + } +} + +/// Fid generator with thread-safe access +pub(crate) struct FidGenerator { + inner: Mutex, +} + +impl Default for FidGenerator { + fn default() -> Self { + Self::new() + } +} + +impl FidGenerator { + /// Create a new fid generator + pub(crate) fn new() -> Self { + FidGenerator { + inner: Mutex::new(IdGenerator::new()), + } + } + + /// Allocate a new fid + pub(crate) fn next(&self) -> u32 { + self.inner.lock().next() + } + + /// Release a fid for reuse + pub(crate) fn free(&self, id: u32) { + self.inner.lock().free(id); + } +} + +/// 9P client state for writing to the connection +pub(crate) struct ClientWriteState { + /// The underlying transport + transport: T, + /// Write buffer + wbuf: Vec, +} + +/// 9P client +/// +/// This client provides synchronous 9P protocol operations. It uses a transport +/// that implements both Read and Write traits. +pub(crate) struct Client { + /// Maximum message size negotiated with server + msize: u32, + /// Write state protected by a mutex + write_state: Mutex>, + /// Read buffer for responses + rbuf: Mutex>, + /// Fid generator + fids: FidGenerator, + /// Next tag for synchronous operations + next_tag: AtomicU32, +} + +impl Client { + /// Create a new 9P client and perform version negotiation + /// + /// # Arguments + /// * `transport` - The underlying transport for read/write operations + /// * `max_msize` - Maximum message size to request + pub(crate) fn new(mut transport: T, max_msize: u32) -> Result { + const MIN_MSIZE: u32 = 4096 + fcall::READDIRHDRSZ; + let bufsize = max_msize.max(MIN_MSIZE) as usize; + + let mut wbuf = Vec::with_capacity(bufsize); + let mut rbuf = Vec::with_capacity(bufsize); + + // Perform version handshake + transport::write_message( + &mut transport, + &mut wbuf, + &TaggedFcall { + tag: fcall::NOTAG, + fcall: Fcall::Tversion(fcall::Tversion { + msize: bufsize as u32, + version: "9P2000.L".into(), + }), + }, + )?; + + let response = transport::read_message(&mut transport, &mut rbuf)?; + + let msize = match response { + TaggedFcall { + tag: fcall::NOTAG, + fcall: Fcall::Rversion(fcall::Rversion { msize, version }), + } => { + if version.as_bytes() != b"9P2000.L" { + return Err(Error::InvalidInput); + } + msize.min(bufsize as u32) + } + TaggedFcall { + fcall: Fcall::Rlerror(e), + .. + } => return Err(e.into_error()), + _ => return Err(Error::InvalidInput), + }; + + wbuf.truncate(msize as usize); + rbuf.truncate(msize as usize); + + Ok(Client { + msize, + write_state: Mutex::new(ClientWriteState { transport, wbuf }), + rbuf: Mutex::new(rbuf), + fids: FidGenerator::new(), + next_tag: AtomicU32::new(1), + }) + } + + /// Get the negotiated message size + pub(crate) fn msize(&self) -> u32 { + self.msize + } + + /// Send a request and wait for the response + fn fcall(&self, fcall: Fcall<'_>) -> Result, Error> { + let tag = self.next_tag.fetch_add(1, Ordering::Relaxed) as u16; + + let mut write_state = self.write_state.lock(); + let ClientWriteState { transport, wbuf } = &mut *write_state; + transport::write_message(transport, wbuf, &TaggedFcall { tag, fcall })?; + + let mut rbuf = self.rbuf.lock(); + + // Loop until we get a response with matching tag (in case of stale responses) + loop { + let response = transport::read_message(transport, &mut rbuf)?; + if response.tag == tag { + return Ok(response.fcall.clone_static()); + } + } + } + + /// Attach to a remote filesystem + pub(crate) fn attach( + &self, + uname: &str, + aname: &str, + ) -> Result<(fcall::Qid, fcall::Fid), Error> { + let fid = self.fids.next(); + match self.fcall(Fcall::Tattach(fcall::Tattach { + afid: fcall::NOFID, + fid, + n_uname: fcall::NONUNAME, + uname: uname.into(), + aname: aname.into(), + }))? { + Fcall::Rattach(fcall::Rattach { qid }) => Ok((qid, fid)), + Fcall::Rlerror(e) => { + self.fids.free(fid); + Err(e.into_error()) + } + _ => { + self.fids.free(fid); + Err(Error::InvalidInput) + } + } + } + + /// Walk to a path from a given fid + /// + /// Returns the qids for each path component and a new fid for the final location + pub(crate) fn walk<'a, S: Into> + Clone>( + &self, + fid: fcall::Fid, + wnames: &[S], + ) -> Result<(Vec, fcall::Fid), Error> { + if wnames.is_empty() { + // Clone the fid + let new_fid = self.fids.next(); + match self.fcall(Fcall::Twalk(fcall::Twalk { + fid, + new_fid, + wnames: vec![], + }))? { + Fcall::Rwalk(fcall::Rwalk { wqids }) => Ok((wqids, new_fid)), + Fcall::Rlerror(e) => { + self.fids.free(new_fid); + Err(e.into_error()) + } + _ => { + self.fids.free(new_fid); + Err(Error::InvalidInput) + } + } + } else { + // Walk in chunks of MAXWELEM + let mut current_fid = fid; + let mut all_qids = Vec::with_capacity(wnames.len()); + let mut new_fid = fid; + + for chunk in wnames.chunks(fcall::MAXWELEM) { + new_fid = self.fids.next(); + let wnames_vec: Vec> = + chunk.iter().map(|s| s.clone().into()).collect(); + + match self.fcall(Fcall::Twalk(fcall::Twalk { + fid: current_fid, + new_fid, + wnames: wnames_vec, + }))? { + Fcall::Rwalk(fcall::Rwalk { mut wqids }) => { + let expected_len = chunk.len(); + let actual_len = wqids.len(); + all_qids.append(&mut wqids); + + // Clunk intermediate fid if not the original + if current_fid != fid { + let _ = self.clunk(current_fid); + } + + if actual_len < expected_len { + // Walk failed partway + self.fids.free(new_fid); + return Err(Error::NotFound); + } + + current_fid = new_fid; + } + Fcall::Rlerror(e) => { + self.fids.free(new_fid); + if current_fid != fid { + let _ = self.clunk(current_fid); + } + return Err(e.into_error()); + } + _ => { + self.fids.free(new_fid); + if current_fid != fid { + let _ = self.clunk(current_fid); + } + return Err(Error::InvalidInput); + } + } + } + + Ok((all_qids, new_fid)) + } + } + + /// Open a file + pub(crate) fn open( + &self, + fid: fcall::Fid, + flags: fcall::LOpenFlags, + ) -> Result { + match self.fcall(Fcall::Tlopen(fcall::Tlopen { fid, flags }))? { + Fcall::Rlopen(r) => Ok(r), + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + } + } + + /// Create a file + pub(crate) fn create( + &self, + dfid: fcall::Fid, + name: &str, + flags: fcall::LOpenFlags, + mode: u32, + gid: u32, + ) -> Result { + match self.fcall(Fcall::Tlcreate(fcall::Tlcreate { + fid: dfid, + name: name.into(), + flags, + mode, + gid, + }))? { + Fcall::Rlcreate(r) => Ok(r), + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + } + } + + /// Read from a file + pub(crate) fn read(&self, fid: fcall::Fid, offset: u64, count: u32) -> Result, Error> { + let count = count.min(self.msize - fcall::IOHDRSZ); + match self.fcall(Fcall::Tread(fcall::Tread { fid, offset, count }))? { + Fcall::Rread(fcall::Rread { data }) => Ok(data.into_owned()), + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + } + } + + /// Write to a file + pub(crate) fn write(&self, fid: fcall::Fid, offset: u64, data: &[u8]) -> Result { + let count = data.len().min((self.msize - fcall::IOHDRSZ) as usize); + match self.fcall(Fcall::Twrite(fcall::Twrite { + fid, + offset, + data: alloc::borrow::Cow::Borrowed(&data[..count]), + }))? { + Fcall::Rwrite(fcall::Rwrite { count }) => Ok(count), + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + } + } + + /// Get file attributes + pub(crate) fn getattr( + &self, + fid: fcall::Fid, + req_mask: GetattrMask, + ) -> Result { + match self.fcall(Fcall::Tgetattr(fcall::Tgetattr { fid, req_mask }))? { + Fcall::Rgetattr(r) => Ok(r), + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + } + } + + /// Set file attributes + pub(crate) fn setattr( + &self, + fid: fcall::Fid, + valid: fcall::SetattrMask, + stat: fcall::SetAttr, + ) -> Result<(), Error> { + match self.fcall(Fcall::Tsetattr(fcall::Tsetattr { fid, valid, stat }))? { + Fcall::Rsetattr(_) => Ok(()), + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + } + } + + /// Read directory entries + pub(crate) fn readdir( + &self, + fid: fcall::Fid, + offset: u64, + count: u32, + ) -> Result>, Error> { + let count = count.min(self.msize - fcall::READDIRHDRSZ); + match self.fcall(Fcall::Treaddir(fcall::Treaddir { fid, offset, count }))? { + Fcall::Rreaddir(fcall::Rreaddir { data }) => { + // Clone the directory entries to owned versions + Ok(data + .data + .into_iter() + .map(|e| fcall::DirEntry { + qid: e.qid, + offset: e.offset, + typ: e.typ, + name: e.name.clone_static(), + }) + .collect()) + } + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + } + } + + /// Read all directory entries + pub(crate) fn readdir_all( + &self, + fid: fcall::Fid, + ) -> Result>, Error> { + let mut all_entries = Vec::new(); + let mut offset = 0u64; + loop { + let entries = self.readdir(fid, offset, self.msize - fcall::READDIRHDRSZ)?; + if entries.is_empty() { + break; + } + offset = entries.last().unwrap().offset; + all_entries.extend(entries); + } + Ok(all_entries) + } + + /// Create a directory + pub(crate) fn mkdir( + &self, + dfid: fcall::Fid, + name: &str, + mode: u32, + gid: u32, + ) -> Result { + match self.fcall(Fcall::Tmkdir(fcall::Tmkdir { + dfid, + name: name.into(), + mode, + gid, + }))? { + Fcall::Rmkdir(fcall::Rmkdir { qid }) => Ok(qid), + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + } + } + + /// Remove (unlink) a file or directory + pub(crate) fn unlinkat(&self, dfid: fcall::Fid, name: &str, flags: u32) -> Result<(), Error> { + match self.fcall(Fcall::Tunlinkat(fcall::Tunlinkat { + dfid, + name: name.into(), + flags, + }))? { + Fcall::Runlinkat(_) => Ok(()), + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + } + } + + /// Rename a file + pub(crate) fn rename( + &self, + fid: fcall::Fid, + dfid: fcall::Fid, + name: &str, + ) -> Result<(), Error> { + match self.fcall(Fcall::Trename(fcall::Trename { + fid, + dfid, + name: name.into(), + }))? { + Fcall::Rrename(_) => Ok(()), + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + } + } + + /// Fsync a file + pub(crate) fn fsync(&self, fid: fcall::Fid, datasync: bool) -> Result<(), Error> { + match self.fcall(Fcall::Tfsync(fcall::Tfsync { + fid, + datasync: u32::from(datasync), + }))? { + Fcall::Rfsync(_) => Ok(()), + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + } + } + + /// Clunk (close) a fid + pub(crate) fn clunk(&self, fid: fcall::Fid) -> Result<(), Error> { + let result = match self.fcall(Fcall::Tclunk(fcall::Tclunk { fid }))? { + Fcall::Rclunk(_) => Ok(()), + Fcall::Rlerror(e) => Err(e.into_error()), + _ => Err(Error::InvalidInput), + }; + self.fids.free(fid); + result + } + + /// Clone a fid (walk with empty path) + pub(crate) fn clone_fid(&self, fid: fcall::Fid) -> Result { + let empty: [&str; 0] = []; + let (_, new_fid) = self.walk(fid, &empty)?; + Ok(new_fid) + } + + /// Release a fid back to the pool without clunking + /// + /// Use this when the fid has already been invalidated (e.g., after remove) + pub(crate) fn free_fid(&self, fid: fcall::Fid) { + self.fids.free(fid); + } +} diff --git a/litebox/src/fs/nine_p/cursor.rs b/litebox/src/fs/nine_p/cursor.rs new file mode 100644 index 000000000..1bd858ccb --- /dev/null +++ b/litebox/src/fs/nine_p/cursor.rs @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +//! Write cursor utilities for 9P protocol encoding + +use alloc::vec::Vec; +use core::cmp::min; + +/// A cursor for writing to a buffer with position tracking. +#[derive(Debug, Default, Eq, PartialEq)] +pub(crate) struct Cursor { + inner: T, + pos: u64, +} + +impl Cursor { + /// Create a new cursor wrapping the given inner value. + pub const fn new(inner: T) -> Cursor { + Cursor { pos: 0, inner } + } + + /// Consume the cursor and return the inner value. + pub fn into_inner(self) -> T { + self.inner + } +} + +/// A trait for writing bytes to a buffer. +pub(crate) trait Write { + /// Write a buffer to the output. + /// + /// Returns the number of bytes written. + fn write(&mut self, buf: &[u8]) -> Result; + + /// Write all bytes from a buffer to the output. + fn write_all(&mut self, buf: &[u8]) -> Result<(), super::Error> { + let mut written = 0; + while written < buf.len() { + let n = self.write(&buf[written..])?; + if n == 0 { + return Err(super::Error::Io); + } + written += n; + } + Ok(()) + } +} + +impl Write for &mut [u8] { + fn write(&mut self, buf: &[u8]) -> Result { + let amt = min(self.len(), buf.len()); + let (a, b) = core::mem::take(self).split_at_mut(amt); + a.copy_from_slice(&buf[..amt]); + *self = b; + Ok(amt) + } +} + +// Non-resizing write implementation +#[inline] +fn slice_write(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> Result { + let pos = min(*pos_mut, slice.len() as u64); + let amt = (&mut slice[(pos as usize)..]).write(buf)?; + *pos_mut += amt as u64; + Ok(amt) +} + +impl Write for Cursor<&mut [u8]> { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + slice_write(&mut self.pos, self.inner, buf) + } +} + +fn reserve_and_pad( + pos_mut: &mut u64, + vec: &mut Vec, + buf_len: usize, +) -> Result { + let pos: usize = (*pos_mut) + .try_into() + .map_err(|_| super::Error::InvalidInput)?; + + // For safety reasons, we don't want these numbers to overflow + // otherwise our allocation won't be enough + let desired_cap = pos.saturating_add(buf_len); + if desired_cap > vec.capacity() { + // We want our vec's total capacity + // to have room for (pos+buf_len) bytes. Reserve allocates + // based on additional elements from the length, so we need to + // reserve the difference + vec.reserve(desired_cap - vec.len()); + } + // Pad if pos is above the current len. + if pos > vec.len() { + let diff = pos - vec.len(); + // Unfortunately, `resize()` would suffice but the optimiser does not + // realise the `reserve` it does can be eliminated. So we do it manually + // to eliminate that extra branch + let spare = vec.spare_capacity_mut(); + debug_assert!(spare.len() >= diff); + // Safety: we have allocated enough capacity for this. + // And we are only writing, not reading + unsafe { + spare + .get_unchecked_mut(..diff) + .fill(core::mem::MaybeUninit::new(0)); + vec.set_len(pos); + } + } + + Ok(pos) +} + +/// # Safety +/// The caller must ensure that `vec.capacity() >= pos + buf.len()` +unsafe fn vec_write_unchecked(pos: usize, vec: &mut Vec, buf: &[u8]) -> usize { + debug_assert!(vec.capacity() >= pos + buf.len()); + // SAFETY: The caller guarantees that vec.capacity() >= pos + buf.len(), + // so this pointer arithmetic and copy is safe. + unsafe { + vec.as_mut_ptr().add(pos).copy_from(buf.as_ptr(), buf.len()); + } + pos + buf.len() +} + +fn vec_write(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> Result { + let buf_len = buf.len(); + let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?; + + // Write the buf then progress the vec forward if necessary + // Safety: we have ensured that the capacity is available + // and that all bytes get written up to pos + unsafe { + pos = vec_write_unchecked(pos, vec, buf); + if pos > vec.len() { + vec.set_len(pos); + } + }; + + // Bump us forward + *pos_mut += buf_len as u64; + Ok(buf_len) +} + +impl Write for Cursor<&mut Vec> { + fn write(&mut self, buf: &[u8]) -> Result { + vec_write(&mut self.pos, self.inner, buf) + } +} + +impl Write for Vec { + fn write(&mut self, buf: &[u8]) -> Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } +} diff --git a/litebox/src/fs/nine_p/fcall.rs b/litebox/src/fs/nine_p/fcall.rs new file mode 100644 index 000000000..3f8b4627a --- /dev/null +++ b/litebox/src/fs/nine_p/fcall.rs @@ -0,0 +1,2399 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +//! 9P2000.L protocol message definitions and encoding/decoding +//! +//! This module implements the 9P2000.L protocol used for network filesystem access. +//! See and + +use super::cursor::Write; +use alloc::{borrow::Cow, vec::Vec}; +use bitflags::bitflags; + +/// File identifier type +pub(crate) type Fid = u32; + +/// Special tag which `Tversion`/`Rversion` must use as `tag` +pub(crate) const NOTAG: u16 = !0; + +/// Special value which `Tattach` with no auth must use as `afid` +/// +/// If the client does not wish to authenticate the connection, or knows that authentication is +/// not required, the afid field in the attach message should be set to `NOFID` +pub(crate) const NOFID: u32 = !0; + +/// Special uid which `Tauth`/`Tattach` use as `n_uname` to indicate no uid is specified +pub(crate) const NONUNAME: u32 = !0; + +/// Room for `Twrite`/`Rread` header +/// +/// size[4] Tread/Twrite[2] tag[2] fid[4] offset[8] count[4] +pub(crate) const IOHDRSZ: u32 = 24; + +/// Room for readdir header +pub(crate) const READDIRHDRSZ: u32 = 24; + +/// Maximum elements in a single walk. +pub(crate) const MAXWELEM: usize = 13; + +bitflags! { + /// Flags passed to Tlopen. + /// + /// Same as Linux's open flags. + /// https://elixir.bootlin.com/linux/v6.12/source/include/net/9p/9p.h#L263 + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct LOpenFlags: u32 { + const O_RDONLY = 0; + const O_WRONLY = 1; + const O_RDWR = 2; + + const O_CREAT = 0o100; + const O_EXCL = 0o200; + const O_NOCTTY = 0o400; + const O_TRUNC = 0o1000; + const O_APPEND = 0o2000; + const O_NONBLOCK = 0o4000; + const O_DSYNC = 0o10000; + const FASYNC = 0o20000; + const O_DIRECT = 0o40000; + const O_LARGEFILE = 0o100000; + const O_DIRECTORY = 0o200000; + const O_NOFOLLOW = 0o400000; + const O_NOATIME = 0o1000000; + const O_CLOEXEC = 0o2000000; + const O_SYNC = 0o4000000; + } +} + +bitflags! { + /// File lock type, Flock.typ + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct LockType: u8 { + const RDLOCK = 0; + const WRLOCK = 1; + const UNLOCK = 2; + } +} + +bitflags! { + /// File lock flags, Flock.flags + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct LockFlag: u32 { + /// Blocking request + const BLOCK = 1; + /// Reserved for future use + const RECLAIM = 2; + } +} + +bitflags! { + /// File lock status + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct LockStatus: u8 { + const SUCCESS = 0; + const BLOCKED = 1; + const ERROR = 2; + const GRACE = 3; + } +} + +bitflags! { + /// Bits in Qid.typ + /// + /// QidType can be constructed from std::fs::FileType via From trait + /// + /// # Protocol + /// 9P2000/9P2000.L + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] + pub struct QidType: u8 { + /// Type bit for directories + const DIR = 0x80; + /// Type bit for append only files + const APPEND = 0x40; + /// Type bit for exclusive use files + const EXCL = 0x20; + /// Type bit for mounted channel + const MOUNT = 0x10; + /// Type bit for authentication file + const AUTH = 0x08; + /// Type bit for not-backed-up file + const TMP = 0x04; + /// Type bits for symbolic links (9P2000.u) + const SYMLINK = 0x02; + /// Type bits for hard-link (9P2000.u) + const LINK = 0x01; + /// Plain file + const FILE = 0x00; + } +} + +bitflags! { + /// Bits in `mask` and `valid` of `Tgetattr` and `Rgetattr`. + /// + /// # Protocol + /// 9P2000.L + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct GetattrMask: u64 { + const MODE = 0x00000001; + const NLINK = 0x00000002; + const UID = 0x00000004; + const GID = 0x00000008; + const RDEV = 0x00000010; + const ATIME = 0x00000020; + const MTIME = 0x00000040; + const CTIME = 0x00000080; + const INO = 0x00000100; + const SIZE = 0x00000200; + const BLOCKS = 0x00000400; + + const BTIME = 0x00000800; + const GEN = 0x00001000; + const DATA_VERSION = 0x00002000; + + /// Mask for fields up to BLOCKS + const BASIC = 0x000007ff; + /// Mask for All fields above + const ALL = 0x00003fff; + } +} + +bitflags! { + /// Bits in `mask` of `Tsetattr`. + /// + /// If a time bit is set without the corresponding SET bit, the current + /// system time on the server is used instead of the value sent in the request. + /// + /// # Protocol + /// 9P2000.L + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct SetattrMask: u32 { + const MODE = 0x00000001; + const UID = 0x00000002; + const GID = 0x00000004; + const SIZE = 0x00000008; + const ATIME = 0x00000010; + const MTIME = 0x00000020; + const CTIME = 0x00000040; + const ATIME_SET = 0x00000080; + const MTIME_SET = 0x00000100; + } +} + +/// String type used in 9P protocol messages +#[derive(Clone, Debug)] +pub(crate) enum FcallStr<'a> { + Owned(Vec), + Borrowed(&'a [u8]), +} + +impl<'a> FcallStr<'a> { + /// Get the bytes of the string + pub fn as_bytes(&'a self) -> &'a [u8] { + match self { + FcallStr::Owned(b) => b, + FcallStr::Borrowed(b) => b, + } + } + + /// Create a static (owned) copy of this string + pub fn clone_static(&self) -> FcallStr<'static> { + FcallStr::Owned(self.as_bytes().to_vec()) + } + + /// Get the length of the string + pub fn len(&self) -> usize { + self.as_bytes().len() + } + + /// Check if the string is empty + pub fn is_empty(&self) -> bool { + self.as_bytes().is_empty() + } +} + +impl<'a, T: ?Sized + AsRef<[u8]>> From<&'a T> for FcallStr<'a> { + fn from(b: &'a T) -> FcallStr<'a> { + FcallStr::Borrowed(b.as_ref()) + } +} + +/// Directory entry data container +#[derive(Clone, Debug)] +pub(crate) struct DirEntryData<'a> { + pub data: Vec>, +} + +impl<'a> DirEntryData<'a> { + /// Create a new empty directory entry data + pub fn new() -> DirEntryData<'a> { + Self::with(Vec::new()) + } + + /// Create directory entry data from a vector + pub fn with(v: Vec>) -> DirEntryData<'a> { + DirEntryData { data: v } + } + + /// Get the directory entries + pub fn data(&self) -> &[DirEntry<'_>] { + &self.data + } + + /// Calculate the total size of all entries + pub fn size(&self) -> u64 { + self.data.iter().fold(0, |a, e| a + e.size()) + } + + /// Push a new entry + pub fn push(&mut self, entry: DirEntry<'a>) { + self.data.push(entry); + } +} + +impl Default for DirEntryData<'_> { + fn default() -> Self { + Self::new() + } +} + +/// 9P message types +#[derive(Copy, Clone, Debug)] +pub(crate) enum FcallType { + // 9P2000.L + Rlerror = 7, + Tstatfs = 8, + Rstatfs = 9, + Tlopen = 12, + Rlopen = 13, + Tlcreate = 14, + Rlcreate = 15, + Tsymlink = 16, + Rsymlink = 17, + Tmknod = 18, + Rmknod = 19, + Trename = 20, + Rrename = 21, + Treadlink = 22, + Rreadlink = 23, + Tgetattr = 24, + Rgetattr = 25, + Tsetattr = 26, + Rsetattr = 27, + Txattrwalk = 30, + Rxattrwalk = 31, + Txattrcreate = 32, + Rxattrcreate = 33, + Treaddir = 40, + Rreaddir = 41, + Tfsync = 50, + Rfsync = 51, + Tlock = 52, + Rlock = 53, + Tgetlock = 54, + Rgetlock = 55, + Tlink = 70, + Rlink = 71, + Tmkdir = 72, + Rmkdir = 73, + Trenameat = 74, + Rrenameat = 75, + Tunlinkat = 76, + Runlinkat = 77, + + // 9P2000 + Tversion = 100, + Rversion = 101, + Tauth = 102, + Rauth = 103, + Tattach = 104, + Rattach = 105, + Tflush = 108, + Rflush = 109, + Twalk = 110, + Rwalk = 111, + Tread = 116, + Rread = 117, + Twrite = 118, + Rwrite = 119, + Tclunk = 120, + Rclunk = 121, + Tremove = 122, + Rremove = 123, +} + +impl FcallType { + /// Convert a u8 to FcallType + pub fn from_u8(v: u8) -> Option { + match v { + // 9P2000.L + 7 => Some(FcallType::Rlerror), + 8 => Some(FcallType::Tstatfs), + 9 => Some(FcallType::Rstatfs), + 12 => Some(FcallType::Tlopen), + 13 => Some(FcallType::Rlopen), + 14 => Some(FcallType::Tlcreate), + 15 => Some(FcallType::Rlcreate), + 16 => Some(FcallType::Tsymlink), + 17 => Some(FcallType::Rsymlink), + 18 => Some(FcallType::Tmknod), + 19 => Some(FcallType::Rmknod), + 20 => Some(FcallType::Trename), + 21 => Some(FcallType::Rrename), + 22 => Some(FcallType::Treadlink), + 23 => Some(FcallType::Rreadlink), + 24 => Some(FcallType::Tgetattr), + 25 => Some(FcallType::Rgetattr), + 26 => Some(FcallType::Tsetattr), + 27 => Some(FcallType::Rsetattr), + 30 => Some(FcallType::Txattrwalk), + 31 => Some(FcallType::Rxattrwalk), + 32 => Some(FcallType::Txattrcreate), + 33 => Some(FcallType::Rxattrcreate), + 40 => Some(FcallType::Treaddir), + 41 => Some(FcallType::Rreaddir), + 50 => Some(FcallType::Tfsync), + 51 => Some(FcallType::Rfsync), + 52 => Some(FcallType::Tlock), + 53 => Some(FcallType::Rlock), + 54 => Some(FcallType::Tgetlock), + 55 => Some(FcallType::Rgetlock), + 70 => Some(FcallType::Tlink), + 71 => Some(FcallType::Rlink), + 72 => Some(FcallType::Tmkdir), + 73 => Some(FcallType::Rmkdir), + 74 => Some(FcallType::Trenameat), + 75 => Some(FcallType::Rrenameat), + 76 => Some(FcallType::Tunlinkat), + 77 => Some(FcallType::Runlinkat), + + // 9P2000 + 100 => Some(FcallType::Tversion), + 101 => Some(FcallType::Rversion), + 102 => Some(FcallType::Tauth), + 103 => Some(FcallType::Rauth), + 104 => Some(FcallType::Tattach), + 105 => Some(FcallType::Rattach), + 108 => Some(FcallType::Tflush), + 109 => Some(FcallType::Rflush), + 110 => Some(FcallType::Twalk), + 111 => Some(FcallType::Rwalk), + 116 => Some(FcallType::Tread), + 117 => Some(FcallType::Rread), + 118 => Some(FcallType::Twrite), + 119 => Some(FcallType::Rwrite), + 120 => Some(FcallType::Tclunk), + 121 => Some(FcallType::Rclunk), + 122 => Some(FcallType::Tremove), + 123 => Some(FcallType::Rremove), + _ => None, + } + } +} + +/// Unique identifier for a file +#[derive(Clone, Debug, Copy)] +pub(crate) struct Qid { + pub typ: QidType, + pub version: u32, + pub path: u64, +} + +/// File system statistics +#[derive(Clone, Debug, Copy)] +pub(crate) struct Statfs { + pub typ: u32, + pub bsize: u32, + pub blocks: u64, + pub bfree: u64, + pub bavail: u64, + pub files: u64, + pub ffree: u64, + pub fsid: u64, + pub namelen: u32, +} + +/// Time structure +#[derive(Clone, Debug, Copy, Default)] +pub(crate) struct Time { + pub sec: u64, + pub nsec: u64, +} + +/// File attributes +#[derive(Clone, Debug, Copy)] +pub(crate) struct Stat { + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub nlink: u64, + pub rdev: u64, + pub size: u64, + pub blksize: u64, + pub blocks: u64, + pub atime: Time, + pub mtime: Time, + pub ctime: Time, + pub btime: Time, + pub generation: u64, + pub data_version: u64, +} + +/// Set file attributes +#[derive(Clone, Debug, Copy, Default)] +pub(crate) struct SetAttr { + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub size: u64, + pub atime: Time, + pub mtime: Time, +} + +/// Directory entry +#[derive(Clone, Debug)] +pub(crate) struct DirEntry<'a> { + pub qid: Qid, + pub offset: u64, + pub typ: u8, + pub name: FcallStr<'a>, +} + +impl DirEntry<'_> { + /// Calculate the size of this entry when encoded + pub fn size(&self) -> u64 { + (13 + 8 + 1 + 2 + self.name.len()) as u64 + } +} + +/// File lock request +#[derive(Clone, Debug)] +pub(crate) struct Flock<'a> { + pub typ: LockType, + pub flags: LockFlag, + pub start: u64, + pub length: u64, + pub proc_id: u32, + pub client_id: FcallStr<'a>, +} + +/// Get lock request +#[derive(Clone, Debug)] +pub(crate) struct Getlock<'a> { + pub typ: LockType, + pub start: u64, + pub length: u64, + pub proc_id: u32, + pub client_id: FcallStr<'a>, +} + +// ============================================================================ +// Response/Request structures +// ============================================================================ + +/// Error response +#[derive(Clone, Debug)] +pub(crate) struct Rlerror { + pub ecode: u32, +} + +impl Rlerror { + /// Convert to an error code + pub fn into_error(self) -> super::Error { + super::Error::Remote(self.ecode) + } +} + +/// Attach request +#[derive(Clone, Debug)] +pub(crate) struct Tattach<'a> { + pub fid: u32, + pub afid: u32, + pub uname: FcallStr<'a>, + pub aname: FcallStr<'a>, + pub n_uname: u32, +} + +impl Tattach<'_> { + pub fn clone_static(&self) -> Tattach<'static> { + Tattach { + afid: self.afid, + fid: self.fid, + n_uname: self.n_uname, + aname: self.aname.clone_static(), + uname: self.uname.clone_static(), + } + } +} + +/// Attach response +#[derive(Clone, Debug)] +pub(crate) struct Rattach { + pub qid: Qid, +} + +/// Statfs request +#[derive(Clone, Debug)] +pub(crate) struct Tstatfs { + pub fid: u32, +} + +/// Statfs response +#[derive(Clone, Debug)] +pub(crate) struct Rstatfs { + pub statfs: Statfs, +} + +/// Open request +#[derive(Clone, Debug)] +pub(crate) struct Tlopen { + pub fid: u32, + pub flags: LOpenFlags, +} + +/// Open response +#[derive(Clone, Debug)] +pub(crate) struct Rlopen { + pub qid: Qid, + pub iounit: u32, +} + +/// Create request +#[derive(Clone, Debug)] +pub(crate) struct Tlcreate<'a> { + pub fid: u32, + pub name: FcallStr<'a>, + pub flags: LOpenFlags, + pub mode: u32, + pub gid: u32, +} + +impl<'a> Tlcreate<'a> { + pub fn clone_static(&'a self) -> Tlcreate<'static> { + Tlcreate { + fid: self.fid, + flags: self.flags, + gid: self.gid, + mode: self.mode, + name: self.name.clone_static(), + } + } +} + +/// Create response +#[derive(Clone, Debug)] +pub(crate) struct Rlcreate { + pub qid: Qid, + pub iounit: u32, +} + +/// Symlink request +#[derive(Clone, Debug)] +pub(crate) struct Tsymlink<'a> { + pub fid: u32, + pub name: FcallStr<'a>, + pub symtgt: FcallStr<'a>, + pub gid: u32, +} + +impl<'a> Tsymlink<'a> { + pub fn clone_static(&'a self) -> Tsymlink<'static> { + Tsymlink { + fid: self.fid, + name: self.name.clone_static(), + symtgt: self.symtgt.clone_static(), + gid: self.gid, + } + } +} + +/// Symlink response +#[derive(Clone, Debug)] +pub(crate) struct Rsymlink { + pub qid: Qid, +} + +/// Mknod request +#[derive(Clone, Debug)] +pub(crate) struct Tmknod<'a> { + pub dfid: u32, + pub name: FcallStr<'a>, + pub mode: u32, + pub major: u32, + pub minor: u32, + pub gid: u32, +} + +impl<'a> Tmknod<'a> { + pub fn clone_static(&'a self) -> Tmknod<'static> { + Tmknod { + dfid: self.dfid, + gid: self.gid, + major: self.major, + minor: self.minor, + mode: self.mode, + name: self.name.clone_static(), + } + } +} + +/// Mknod response +#[derive(Clone, Debug)] +pub(crate) struct Rmknod { + pub qid: Qid, +} + +/// Rename request +#[derive(Clone, Debug)] +pub(crate) struct Trename<'a> { + pub fid: u32, + pub dfid: u32, + pub name: FcallStr<'a>, +} + +impl<'a> Trename<'a> { + pub fn clone_static(&'a self) -> Trename<'static> { + Trename { + fid: self.fid, + dfid: self.dfid, + name: self.name.clone_static(), + } + } +} + +/// Rename response +#[derive(Clone, Debug)] +pub(crate) struct Rrename {} + +/// Readlink request +#[derive(Clone, Debug)] +pub(crate) struct Treadlink { + pub fid: u32, +} + +/// Readlink response +#[derive(Clone, Debug)] +pub(crate) struct Rreadlink<'a> { + pub target: FcallStr<'a>, +} + +impl<'a> Rreadlink<'a> { + pub fn clone_static(&'a self) -> Rreadlink<'static> { + Rreadlink { + target: self.target.clone_static(), + } + } +} + +/// Getattr request +#[derive(Clone, Debug)] +pub(crate) struct Tgetattr { + pub fid: u32, + pub req_mask: GetattrMask, +} + +/// Getattr response +#[derive(Clone, Debug)] +pub(crate) struct Rgetattr { + pub valid: GetattrMask, + pub qid: Qid, + pub stat: Stat, +} + +/// Setattr request +#[derive(Clone, Debug)] +pub(crate) struct Tsetattr { + pub fid: u32, + pub valid: SetattrMask, + pub stat: SetAttr, +} + +/// Setattr response +#[derive(Clone, Debug)] +pub(crate) struct Rsetattr {} + +/// Xattr walk request +#[derive(Clone, Debug)] +pub(crate) struct Txattrwalk<'a> { + pub fid: u32, + pub new_fid: u32, + pub name: FcallStr<'a>, +} + +impl<'a> Txattrwalk<'a> { + pub fn clone_static(&'a self) -> Txattrwalk<'static> { + Txattrwalk { + fid: self.fid, + new_fid: self.new_fid, + name: self.name.clone_static(), + } + } +} + +/// Xattr walk response +#[derive(Clone, Debug)] +pub(crate) struct Rxattrwalk { + pub size: u64, +} + +/// Xattr create request +#[derive(Clone, Debug)] +pub(crate) struct Txattrcreate<'a> { + pub fid: u32, + pub name: FcallStr<'a>, + pub attr_size: u64, + pub flags: u32, +} + +impl<'a> Txattrcreate<'a> { + pub fn clone_static(&'a self) -> Txattrcreate<'static> { + Txattrcreate { + fid: self.fid, + name: self.name.clone_static(), + attr_size: self.attr_size, + flags: self.flags, + } + } +} + +/// Xattr create response +#[derive(Clone, Debug)] +pub(crate) struct Rxattrcreate {} + +/// Readdir request +#[derive(Clone, Debug)] +pub(crate) struct Treaddir { + pub fid: u32, + pub offset: u64, + pub count: u32, +} + +/// Readdir response +#[derive(Clone, Debug)] +pub(crate) struct Rreaddir<'a> { + pub data: DirEntryData<'a>, +} + +impl<'a> Rreaddir<'a> { + pub fn clone_static(&'a self) -> Rreaddir<'static> { + Rreaddir { + data: DirEntryData { + data: self + .data + .data + .iter() + .map(|de| DirEntry { + qid: de.qid, + offset: de.offset, + typ: de.typ, + name: de.name.clone_static(), + }) + .collect(), + }, + } + } +} + +/// Fsync request +#[derive(Clone, Debug)] +pub(crate) struct Tfsync { + pub fid: u32, + pub datasync: u32, +} + +/// Fsync response +#[derive(Clone, Debug)] +pub(crate) struct Rfsync {} + +/// Lock request +#[derive(Clone, Debug)] +pub(crate) struct Tlock<'a> { + pub fid: u32, + pub flock: Flock<'a>, +} + +impl<'a> Tlock<'a> { + pub fn clone_static(&'a self) -> Tlock<'static> { + Tlock { + fid: self.fid, + flock: Flock { + typ: self.flock.typ, + flags: self.flock.flags, + start: self.flock.start, + length: self.flock.length, + proc_id: self.flock.proc_id, + client_id: self.flock.client_id.clone_static(), + }, + } + } +} + +/// Lock response +#[derive(Clone, Debug)] +pub(crate) struct Rlock { + pub status: LockStatus, +} + +/// Getlock request +#[derive(Clone, Debug)] +pub(crate) struct Tgetlock<'a> { + pub fid: u32, + pub flock: Getlock<'a>, +} + +impl<'a> Tgetlock<'a> { + pub fn clone_static(&'a self) -> Tgetlock<'static> { + Tgetlock { + fid: self.fid, + flock: Getlock { + typ: self.flock.typ, + start: self.flock.start, + length: self.flock.length, + proc_id: self.flock.proc_id, + client_id: self.flock.client_id.clone_static(), + }, + } + } +} + +/// Getlock response +#[derive(Clone, Debug)] +pub(crate) struct Rgetlock<'a> { + pub flock: Getlock<'a>, +} + +impl<'a> Rgetlock<'a> { + pub fn clone_static(&'a self) -> Rgetlock<'static> { + Rgetlock { + flock: Getlock { + typ: self.flock.typ, + start: self.flock.start, + length: self.flock.length, + proc_id: self.flock.proc_id, + client_id: self.flock.client_id.clone_static(), + }, + } + } +} + +/// Link request +#[derive(Clone, Debug)] +pub(crate) struct Tlink<'a> { + pub dfid: u32, + pub fid: u32, + pub name: FcallStr<'a>, +} + +impl<'a> Tlink<'a> { + pub fn clone_static(&'a self) -> Tlink<'static> { + Tlink { + fid: self.fid, + dfid: self.dfid, + name: self.name.clone_static(), + } + } +} + +/// Link response +#[derive(Clone, Debug)] +pub(crate) struct Rlink {} + +/// Mkdir request +#[derive(Clone, Debug)] +pub(crate) struct Tmkdir<'a> { + pub dfid: u32, + pub name: FcallStr<'a>, + pub mode: u32, + pub gid: u32, +} + +impl<'a> Tmkdir<'a> { + pub fn clone_static(&'a self) -> Tmkdir<'static> { + Tmkdir { + dfid: self.dfid, + gid: self.gid, + mode: self.mode, + name: self.name.clone_static(), + } + } +} + +/// Mkdir response +#[derive(Clone, Debug)] +pub(crate) struct Rmkdir { + pub qid: Qid, +} + +/// Renameat request +#[derive(Clone, Debug)] +pub(crate) struct Trenameat<'a> { + pub olddfid: u32, + pub oldname: FcallStr<'a>, + pub newdfid: u32, + pub newname: FcallStr<'a>, +} + +impl<'a> Trenameat<'a> { + pub fn clone_static(&'a self) -> Trenameat<'static> { + Trenameat { + newdfid: self.newdfid, + olddfid: self.olddfid, + newname: self.newname.clone_static(), + oldname: self.oldname.clone_static(), + } + } +} + +/// Renameat response +#[derive(Clone, Debug)] +pub(crate) struct Rrenameat {} + +/// Unlinkat request +#[derive(Clone, Debug)] +pub(crate) struct Tunlinkat<'a> { + pub dfid: u32, + pub name: FcallStr<'a>, + pub flags: u32, +} + +impl<'a> Tunlinkat<'a> { + pub fn clone_static(&'a self) -> Tunlinkat<'static> { + Tunlinkat { + dfid: self.dfid, + flags: self.flags, + name: self.name.clone_static(), + } + } +} + +/// Unlinkat response +#[derive(Clone, Debug)] +pub(crate) struct Runlinkat {} + +/// Auth request +#[derive(Clone, Debug)] +pub(crate) struct Tauth<'a> { + pub afid: u32, + pub uname: FcallStr<'a>, + pub aname: FcallStr<'a>, + pub n_uname: u32, +} + +impl<'a> Tauth<'a> { + pub fn clone_static(&'a self) -> Tauth<'static> { + Tauth { + afid: self.afid, + n_uname: self.n_uname, + aname: self.aname.clone_static(), + uname: self.uname.clone_static(), + } + } +} + +/// Auth response +#[derive(Clone, Debug)] +pub(crate) struct Rauth { + pub aqid: Qid, +} + +/// Version request +#[derive(Clone, Debug)] +pub(crate) struct Tversion<'a> { + pub msize: u32, + pub version: FcallStr<'a>, +} + +impl<'a> Tversion<'a> { + pub fn clone_static(&'a self) -> Tversion<'static> { + Tversion { + msize: self.msize, + version: self.version.clone_static(), + } + } +} + +/// Version response +#[derive(Clone, Debug)] +pub(crate) struct Rversion<'a> { + pub msize: u32, + pub version: FcallStr<'a>, +} + +impl<'a> Rversion<'a> { + pub fn clone_static(&'a self) -> Rversion<'static> { + Rversion { + msize: self.msize, + version: self.version.clone_static(), + } + } +} + +/// Flush request +#[derive(Clone, Debug)] +pub(crate) struct Tflush { + pub oldtag: u16, +} + +/// Flush response +#[derive(Clone, Debug)] +pub(crate) struct Rflush {} + +/// Walk request +#[derive(Clone, Debug)] +pub(crate) struct Twalk<'a> { + pub fid: u32, + pub new_fid: u32, + pub wnames: Vec>, +} + +impl<'a> Twalk<'a> { + pub fn clone_static(&'a self) -> Twalk<'static> { + Twalk { + fid: self.fid, + new_fid: self.new_fid, + wnames: self.wnames.iter().map(|n| n.clone_static()).collect(), + } + } +} + +/// Walk response +#[derive(Clone, Debug)] +pub(crate) struct Rwalk { + pub wqids: Vec, +} + +/// Read request +#[derive(Clone, Debug)] +pub(crate) struct Tread { + pub fid: u32, + pub offset: u64, + pub count: u32, +} + +/// Read response +#[derive(Clone, Debug)] +pub(crate) struct Rread<'a> { + pub data: Cow<'a, [u8]>, +} + +impl<'a> Rread<'a> { + pub fn clone_static(&'a self) -> Rread<'static> { + Rread { + data: Cow::from(self.data.clone().into_owned()), + } + } +} + +/// Write request +#[derive(Clone, Debug)] +pub(crate) struct Twrite<'a> { + pub fid: u32, + pub offset: u64, + pub data: Cow<'a, [u8]>, +} + +impl<'a> Twrite<'a> { + pub fn clone_static(&'a self) -> Twrite<'static> { + Twrite { + fid: self.fid, + offset: self.offset, + data: Cow::from(self.data.clone().into_owned()), + } + } +} + +/// Write response +#[derive(Clone, Debug)] +pub(crate) struct Rwrite { + pub count: u32, +} + +/// Clunk request +#[derive(Clone, Debug)] +pub(crate) struct Tclunk { + pub fid: u32, +} + +/// Clunk response +#[derive(Clone, Debug)] +pub(crate) struct Rclunk {} + +/// Remove request +#[derive(Clone, Debug)] +pub(crate) struct Tremove { + pub fid: u32, +} + +/// Remove response +#[derive(Clone, Debug)] +pub(crate) struct Rremove {} + +// ============================================================================ +// Fcall enum and conversions +// ============================================================================ + +/// 9P protocol message +#[derive(Clone, Debug)] +pub(crate) enum Fcall<'a> { + Rlerror(Rlerror), + Tattach(Tattach<'a>), + Rattach(Rattach), + Tstatfs(Tstatfs), + Rstatfs(Rstatfs), + Tlopen(Tlopen), + Rlopen(Rlopen), + Tlcreate(Tlcreate<'a>), + Rlcreate(Rlcreate), + Tsymlink(Tsymlink<'a>), + Rsymlink(Rsymlink), + Tmknod(Tmknod<'a>), + Rmknod(Rmknod), + Trename(Trename<'a>), + Rrename(Rrename), + Treadlink(Treadlink), + Rreadlink(Rreadlink<'a>), + Tgetattr(Tgetattr), + Rgetattr(Rgetattr), + Tsetattr(Tsetattr), + Rsetattr(Rsetattr), + Txattrwalk(Txattrwalk<'a>), + Rxattrwalk(Rxattrwalk), + Txattrcreate(Txattrcreate<'a>), + Rxattrcreate(Rxattrcreate), + Treaddir(Treaddir), + Rreaddir(Rreaddir<'a>), + Tfsync(Tfsync), + Rfsync(Rfsync), + Tlock(Tlock<'a>), + Rlock(Rlock), + Tgetlock(Tgetlock<'a>), + Rgetlock(Rgetlock<'a>), + Tlink(Tlink<'a>), + Rlink(Rlink), + Tmkdir(Tmkdir<'a>), + Rmkdir(Rmkdir), + Trenameat(Trenameat<'a>), + Rrenameat(Rrenameat), + Tunlinkat(Tunlinkat<'a>), + Runlinkat(Runlinkat), + Tauth(Tauth<'a>), + Rauth(Rauth), + Tversion(Tversion<'a>), + Rversion(Rversion<'a>), + Tflush(Tflush), + Rflush(Rflush), + Twalk(Twalk<'a>), + Rwalk(Rwalk), + Tread(Tread), + Rread(Rread<'a>), + Twrite(Twrite<'a>), + Rwrite(Rwrite), + Tclunk(Tclunk), + Rclunk(Rclunk), + Tremove(Tremove), + Rremove(Rremove), +} + +impl Fcall<'_> { + /// Create a static (owned) copy of this Fcall + pub fn clone_static(&self) -> Fcall<'static> { + match self { + Fcall::Rlerror(v) => Fcall::Rlerror(v.clone()), + Fcall::Tattach(v) => Fcall::Tattach(v.clone_static()), + Fcall::Rattach(v) => Fcall::Rattach(v.clone()), + Fcall::Tstatfs(v) => Fcall::Tstatfs(v.clone()), + Fcall::Rstatfs(v) => Fcall::Rstatfs(v.clone()), + Fcall::Tlopen(v) => Fcall::Tlopen(v.clone()), + Fcall::Rlopen(v) => Fcall::Rlopen(v.clone()), + Fcall::Tlcreate(v) => Fcall::Tlcreate(v.clone_static()), + Fcall::Rlcreate(v) => Fcall::Rlcreate(v.clone()), + Fcall::Tsymlink(v) => Fcall::Tsymlink(v.clone_static()), + Fcall::Rsymlink(v) => Fcall::Rsymlink(v.clone()), + Fcall::Tmknod(v) => Fcall::Tmknod(v.clone_static()), + Fcall::Rmknod(v) => Fcall::Rmknod(v.clone()), + Fcall::Trename(v) => Fcall::Trename(v.clone_static()), + Fcall::Rrename(v) => Fcall::Rrename(v.clone()), + Fcall::Treadlink(v) => Fcall::Treadlink(v.clone()), + Fcall::Rreadlink(v) => Fcall::Rreadlink(v.clone_static()), + Fcall::Tgetattr(v) => Fcall::Tgetattr(v.clone()), + Fcall::Rgetattr(v) => Fcall::Rgetattr(v.clone()), + Fcall::Tsetattr(v) => Fcall::Tsetattr(v.clone()), + Fcall::Rsetattr(v) => Fcall::Rsetattr(v.clone()), + Fcall::Txattrwalk(v) => Fcall::Txattrwalk(v.clone_static()), + Fcall::Rxattrwalk(v) => Fcall::Rxattrwalk(v.clone()), + Fcall::Txattrcreate(v) => Fcall::Txattrcreate(v.clone_static()), + Fcall::Rxattrcreate(v) => Fcall::Rxattrcreate(v.clone()), + Fcall::Treaddir(v) => Fcall::Treaddir(v.clone()), + Fcall::Rreaddir(v) => Fcall::Rreaddir(v.clone_static()), + Fcall::Tfsync(v) => Fcall::Tfsync(v.clone()), + Fcall::Rfsync(v) => Fcall::Rfsync(v.clone()), + Fcall::Tlock(v) => Fcall::Tlock(v.clone_static()), + Fcall::Rlock(v) => Fcall::Rlock(v.clone()), + Fcall::Tgetlock(v) => Fcall::Tgetlock(v.clone_static()), + Fcall::Rgetlock(v) => Fcall::Rgetlock(v.clone_static()), + Fcall::Tlink(v) => Fcall::Tlink(v.clone_static()), + Fcall::Rlink(v) => Fcall::Rlink(v.clone()), + Fcall::Tmkdir(v) => Fcall::Tmkdir(v.clone_static()), + Fcall::Rmkdir(v) => Fcall::Rmkdir(v.clone()), + Fcall::Trenameat(v) => Fcall::Trenameat(v.clone_static()), + Fcall::Rrenameat(v) => Fcall::Rrenameat(v.clone()), + Fcall::Tunlinkat(v) => Fcall::Tunlinkat(v.clone_static()), + Fcall::Runlinkat(v) => Fcall::Runlinkat(v.clone()), + Fcall::Tauth(v) => Fcall::Tauth(v.clone_static()), + Fcall::Rauth(v) => Fcall::Rauth(v.clone()), + Fcall::Tversion(v) => Fcall::Tversion(v.clone_static()), + Fcall::Rversion(v) => Fcall::Rversion(v.clone_static()), + Fcall::Tflush(v) => Fcall::Tflush(v.clone()), + Fcall::Rflush(v) => Fcall::Rflush(v.clone()), + Fcall::Twalk(v) => Fcall::Twalk(v.clone_static()), + Fcall::Rwalk(v) => Fcall::Rwalk(v.clone()), + Fcall::Tread(v) => Fcall::Tread(v.clone()), + Fcall::Rread(v) => Fcall::Rread(v.clone_static()), + Fcall::Twrite(v) => Fcall::Twrite(v.clone_static()), + Fcall::Rwrite(v) => Fcall::Rwrite(v.clone()), + Fcall::Tclunk(v) => Fcall::Tclunk(v.clone()), + Fcall::Rclunk(v) => Fcall::Rclunk(v.clone()), + Fcall::Tremove(v) => Fcall::Tremove(v.clone()), + Fcall::Rremove(v) => Fcall::Rremove(v.clone()), + } + } +} + +// Implement From for all message types +macro_rules! impl_from_for_fcall { + ($($variant:ident($ty:ty)),* $(,)?) => { + $( + impl<'a> From<$ty> for Fcall<'a> { + fn from(v: $ty) -> Fcall<'a> { + Fcall::$variant(v) + } + } + )* + }; +} + +impl_from_for_fcall! { + Rlerror(Rlerror), + Rattach(Rattach), + Tstatfs(Tstatfs), + Rstatfs(Rstatfs), + Tlopen(Tlopen), + Rlopen(Rlopen), + Rlcreate(Rlcreate), + Rsymlink(Rsymlink), + Rmknod(Rmknod), + Rrename(Rrename), + Treadlink(Treadlink), + Tgetattr(Tgetattr), + Rgetattr(Rgetattr), + Tsetattr(Tsetattr), + Rsetattr(Rsetattr), + Rxattrwalk(Rxattrwalk), + Rxattrcreate(Rxattrcreate), + Treaddir(Treaddir), + Tfsync(Tfsync), + Rfsync(Rfsync), + Rlock(Rlock), + Rlink(Rlink), + Rmkdir(Rmkdir), + Rrenameat(Rrenameat), + Runlinkat(Runlinkat), + Rauth(Rauth), + Tflush(Tflush), + Rflush(Rflush), + Rwalk(Rwalk), + Tread(Tread), + Rwrite(Rwrite), + Tclunk(Tclunk), + Rclunk(Rclunk), + Tremove(Tremove), + Rremove(Rremove), +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Tattach<'a>) -> Fcall<'a> { + Fcall::Tattach(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Tlcreate<'a>) -> Fcall<'a> { + Fcall::Tlcreate(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Tsymlink<'a>) -> Fcall<'a> { + Fcall::Tsymlink(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Tmknod<'a>) -> Fcall<'a> { + Fcall::Tmknod(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Trename<'a>) -> Fcall<'a> { + Fcall::Trename(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Rreadlink<'a>) -> Fcall<'a> { + Fcall::Rreadlink(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Txattrwalk<'a>) -> Fcall<'a> { + Fcall::Txattrwalk(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Txattrcreate<'a>) -> Fcall<'a> { + Fcall::Txattrcreate(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Rreaddir<'a>) -> Fcall<'a> { + Fcall::Rreaddir(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Tlock<'a>) -> Fcall<'a> { + Fcall::Tlock(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Tgetlock<'a>) -> Fcall<'a> { + Fcall::Tgetlock(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Rgetlock<'a>) -> Fcall<'a> { + Fcall::Rgetlock(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Tlink<'a>) -> Fcall<'a> { + Fcall::Tlink(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Tmkdir<'a>) -> Fcall<'a> { + Fcall::Tmkdir(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Trenameat<'a>) -> Fcall<'a> { + Fcall::Trenameat(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Tunlinkat<'a>) -> Fcall<'a> { + Fcall::Tunlinkat(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Tauth<'a>) -> Fcall<'a> { + Fcall::Tauth(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Tversion<'a>) -> Fcall<'a> { + Fcall::Tversion(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Rversion<'a>) -> Fcall<'a> { + Fcall::Rversion(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Twalk<'a>) -> Fcall<'a> { + Fcall::Twalk(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Rread<'a>) -> Fcall<'a> { + Fcall::Rread(v) + } +} + +impl<'a> From> for Fcall<'a> { + fn from(v: Twrite<'a>) -> Fcall<'a> { + Fcall::Twrite(v) + } +} + +/// Tagged 9P message +#[derive(Clone, Debug)] +pub(crate) struct TaggedFcall<'a> { + pub tag: u16, + pub fcall: Fcall<'a>, +} + +impl<'a> TaggedFcall<'a> { + /// Encode the message to a buffer + pub fn encode_to_buf(&self, buf: &mut Vec) -> Result<(), super::Error> { + buf.clear(); + buf.resize(4, 0); // Reserve space for size + + // Encode the message directly to the buffer (appending after the size field) + encode_fcall(buf, &self.tag, &self.fcall)?; + + // Write the size at the beginning + let size = buf.len() as u32; + buf[0..4].copy_from_slice(&size.to_le_bytes()); + + Ok(()) + } + + /// Decode a message from a buffer + pub fn decode(buf: &'a [u8]) -> Result, super::Error> { + if buf.len() < 7 { + return Err(super::Error::InvalidInput); + } + + let mut decoder = FcallDecoder { buf: &buf[4..] }; + decoder.decode() + } +} + +// ============================================================================ +// Encoding functions +// ============================================================================ + +fn encode_u8(w: &mut W, v: u8) -> Result<(), super::Error> { + w.write_all(&[v]) +} + +fn encode_u16(w: &mut W, v: u16) -> Result<(), super::Error> { + w.write_all(&v.to_le_bytes()) +} + +fn encode_u32(w: &mut W, v: u32) -> Result<(), super::Error> { + w.write_all(&v.to_le_bytes()) +} + +fn encode_u64(w: &mut W, v: u64) -> Result<(), super::Error> { + w.write_all(&v.to_le_bytes()) +} + +fn encode_str(w: &mut W, v: &FcallStr<'_>) -> Result<(), super::Error> { + encode_u16(w, v.len() as u16)?; + w.write_all(v.as_bytes()) +} + +fn encode_data_buf(w: &mut W, v: &[u8]) -> Result<(), super::Error> { + encode_u32(w, v.len() as u32)?; + w.write_all(v) +} + +fn encode_vec_str(w: &mut W, v: &[FcallStr<'_>]) -> Result<(), super::Error> { + encode_u16(w, v.len() as u16)?; + for s in v { + encode_str(w, s)?; + } + Ok(()) +} + +fn encode_vec_qid(w: &mut W, v: &[Qid]) -> Result<(), super::Error> { + encode_u16(w, v.len() as u16)?; + for q in v { + encode_qid(w, q)?; + } + Ok(()) +} + +fn encode_qidtype(w: &mut W, v: &QidType) -> Result<(), super::Error> { + encode_u8(w, v.bits()) +} + +fn encode_locktype(w: &mut W, v: &LockType) -> Result<(), super::Error> { + encode_u8(w, v.bits()) +} + +fn encode_lockstatus(w: &mut W, v: &LockStatus) -> Result<(), super::Error> { + encode_u8(w, v.bits()) +} + +fn encode_lockflag(w: &mut W, v: &LockFlag) -> Result<(), super::Error> { + encode_u32(w, v.bits()) +} + +fn encode_getattrmask(w: &mut W, v: &GetattrMask) -> Result<(), super::Error> { + encode_u64(w, v.bits()) +} + +fn encode_setattrmask(w: &mut W, v: &SetattrMask) -> Result<(), super::Error> { + encode_u32(w, v.bits()) +} + +fn encode_qid(w: &mut W, v: &Qid) -> Result<(), super::Error> { + encode_qidtype(w, &v.typ)?; + encode_u32(w, v.version)?; + encode_u64(w, v.path)?; + Ok(()) +} + +fn encode_statfs(w: &mut W, v: &Statfs) -> Result<(), super::Error> { + encode_u32(w, v.typ)?; + encode_u32(w, v.bsize)?; + encode_u64(w, v.blocks)?; + encode_u64(w, v.bfree)?; + encode_u64(w, v.bavail)?; + encode_u64(w, v.files)?; + encode_u64(w, v.ffree)?; + encode_u64(w, v.fsid)?; + encode_u32(w, v.namelen)?; + Ok(()) +} + +fn encode_time(w: &mut W, v: &Time) -> Result<(), super::Error> { + encode_u64(w, v.sec)?; + encode_u64(w, v.nsec)?; + Ok(()) +} + +fn encode_stat(w: &mut W, v: &Stat) -> Result<(), super::Error> { + encode_u32(w, v.mode)?; + encode_u32(w, v.uid)?; + encode_u32(w, v.gid)?; + encode_u64(w, v.nlink)?; + encode_u64(w, v.rdev)?; + encode_u64(w, v.size)?; + encode_u64(w, v.blksize)?; + encode_u64(w, v.blocks)?; + encode_time(w, &v.atime)?; + encode_time(w, &v.mtime)?; + encode_time(w, &v.ctime)?; + encode_time(w, &v.btime)?; + encode_u64(w, v.generation)?; + encode_u64(w, v.data_version)?; + Ok(()) +} + +fn encode_setattr(w: &mut W, v: &SetAttr) -> Result<(), super::Error> { + encode_u32(w, v.mode)?; + encode_u32(w, v.uid)?; + encode_u32(w, v.gid)?; + encode_u64(w, v.size)?; + encode_time(w, &v.atime)?; + encode_time(w, &v.mtime)?; + Ok(()) +} + +fn encode_direntrydata(w: &mut W, v: &DirEntryData<'_>) -> Result<(), super::Error> { + encode_u32(w, v.size() as u32)?; + for e in &v.data { + encode_direntry(w, e)?; + } + Ok(()) +} + +fn encode_direntry(w: &mut W, v: &DirEntry<'_>) -> Result<(), super::Error> { + encode_qid(w, &v.qid)?; + encode_u64(w, v.offset)?; + encode_u8(w, v.typ)?; + encode_str(w, &v.name)?; + Ok(()) +} + +fn encode_flock(w: &mut W, v: &Flock<'_>) -> Result<(), super::Error> { + encode_locktype(w, &v.typ)?; + encode_lockflag(w, &v.flags)?; + encode_u64(w, v.start)?; + encode_u64(w, v.length)?; + encode_u32(w, v.proc_id)?; + encode_str(w, &v.client_id)?; + Ok(()) +} + +fn encode_getlock(w: &mut W, v: &Getlock<'_>) -> Result<(), super::Error> { + encode_locktype(w, &v.typ)?; + encode_u64(w, v.start)?; + encode_u64(w, v.length)?; + encode_u32(w, v.proc_id)?; + encode_str(w, &v.client_id)?; + Ok(()) +} + +fn encode_fcall(w: &mut W, tag: &u16, fcall: &Fcall<'_>) -> Result<(), super::Error> { + match fcall { + Fcall::Rlerror(v) => { + encode_u8(w, FcallType::Rlerror as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.ecode)?; + } + Fcall::Tattach(v) => { + encode_u8(w, FcallType::Tattach as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_u32(w, v.afid)?; + encode_str(w, &v.uname)?; + encode_str(w, &v.aname)?; + encode_u32(w, v.n_uname)?; + } + Fcall::Rattach(v) => { + encode_u8(w, FcallType::Rattach as u8)?; + encode_u16(w, *tag)?; + encode_qid(w, &v.qid)?; + } + Fcall::Tstatfs(v) => { + encode_u8(w, FcallType::Tstatfs as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + } + Fcall::Rstatfs(v) => { + encode_u8(w, FcallType::Rstatfs as u8)?; + encode_u16(w, *tag)?; + encode_statfs(w, &v.statfs)?; + } + Fcall::Tlopen(v) => { + encode_u8(w, FcallType::Tlopen as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_u32(w, v.flags.bits())?; + } + Fcall::Rlopen(v) => { + encode_u8(w, FcallType::Rlopen as u8)?; + encode_u16(w, *tag)?; + encode_qid(w, &v.qid)?; + encode_u32(w, v.iounit)?; + } + Fcall::Tlcreate(v) => { + encode_u8(w, FcallType::Tlcreate as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_str(w, &v.name)?; + encode_u32(w, v.flags.bits())?; + encode_u32(w, v.mode)?; + encode_u32(w, v.gid)?; + } + Fcall::Rlcreate(v) => { + encode_u8(w, FcallType::Rlcreate as u8)?; + encode_u16(w, *tag)?; + encode_qid(w, &v.qid)?; + encode_u32(w, v.iounit)?; + } + Fcall::Tsymlink(v) => { + encode_u8(w, FcallType::Tsymlink as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_str(w, &v.name)?; + encode_str(w, &v.symtgt)?; + encode_u32(w, v.gid)?; + } + Fcall::Rsymlink(v) => { + encode_u8(w, FcallType::Rsymlink as u8)?; + encode_u16(w, *tag)?; + encode_qid(w, &v.qid)?; + } + Fcall::Tmknod(v) => { + encode_u8(w, FcallType::Tmknod as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.dfid)?; + encode_str(w, &v.name)?; + encode_u32(w, v.mode)?; + encode_u32(w, v.major)?; + encode_u32(w, v.minor)?; + encode_u32(w, v.gid)?; + } + Fcall::Rmknod(v) => { + encode_u8(w, FcallType::Rmknod as u8)?; + encode_u16(w, *tag)?; + encode_qid(w, &v.qid)?; + } + Fcall::Trename(v) => { + encode_u8(w, FcallType::Trename as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_u32(w, v.dfid)?; + encode_str(w, &v.name)?; + } + Fcall::Rrename(_) => { + encode_u8(w, FcallType::Rrename as u8)?; + encode_u16(w, *tag)?; + } + Fcall::Treadlink(v) => { + encode_u8(w, FcallType::Treadlink as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + } + Fcall::Rreadlink(v) => { + encode_u8(w, FcallType::Rreadlink as u8)?; + encode_u16(w, *tag)?; + encode_str(w, &v.target)?; + } + Fcall::Tgetattr(v) => { + encode_u8(w, FcallType::Tgetattr as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_getattrmask(w, &v.req_mask)?; + } + Fcall::Rgetattr(v) => { + encode_u8(w, FcallType::Rgetattr as u8)?; + encode_u16(w, *tag)?; + encode_getattrmask(w, &v.valid)?; + encode_qid(w, &v.qid)?; + encode_stat(w, &v.stat)?; + } + Fcall::Tsetattr(v) => { + encode_u8(w, FcallType::Tsetattr as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_setattrmask(w, &v.valid)?; + encode_setattr(w, &v.stat)?; + } + Fcall::Rsetattr(_) => { + encode_u8(w, FcallType::Rsetattr as u8)?; + encode_u16(w, *tag)?; + } + Fcall::Txattrwalk(v) => { + encode_u8(w, FcallType::Txattrwalk as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_u32(w, v.new_fid)?; + encode_str(w, &v.name)?; + } + Fcall::Rxattrwalk(v) => { + encode_u8(w, FcallType::Rxattrwalk as u8)?; + encode_u16(w, *tag)?; + encode_u64(w, v.size)?; + } + Fcall::Txattrcreate(v) => { + encode_u8(w, FcallType::Txattrcreate as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_str(w, &v.name)?; + encode_u64(w, v.attr_size)?; + encode_u32(w, v.flags)?; + } + Fcall::Rxattrcreate(_) => { + encode_u8(w, FcallType::Rxattrcreate as u8)?; + encode_u16(w, *tag)?; + } + Fcall::Treaddir(v) => { + encode_u8(w, FcallType::Treaddir as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_u64(w, v.offset)?; + encode_u32(w, v.count)?; + } + Fcall::Rreaddir(v) => { + encode_u8(w, FcallType::Rreaddir as u8)?; + encode_u16(w, *tag)?; + encode_direntrydata(w, &v.data)?; + } + Fcall::Tfsync(v) => { + encode_u8(w, FcallType::Tfsync as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_u32(w, v.datasync)?; + } + Fcall::Rfsync(_) => { + encode_u8(w, FcallType::Rfsync as u8)?; + encode_u16(w, *tag)?; + } + Fcall::Tlock(v) => { + encode_u8(w, FcallType::Tlock as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_flock(w, &v.flock)?; + } + Fcall::Rlock(v) => { + encode_u8(w, FcallType::Rlock as u8)?; + encode_u16(w, *tag)?; + encode_lockstatus(w, &v.status)?; + } + Fcall::Tgetlock(v) => { + encode_u8(w, FcallType::Tgetlock as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_getlock(w, &v.flock)?; + } + Fcall::Rgetlock(v) => { + encode_u8(w, FcallType::Rgetlock as u8)?; + encode_u16(w, *tag)?; + encode_getlock(w, &v.flock)?; + } + Fcall::Tlink(v) => { + encode_u8(w, FcallType::Tlink as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.dfid)?; + encode_u32(w, v.fid)?; + encode_str(w, &v.name)?; + } + Fcall::Rlink(_) => { + encode_u8(w, FcallType::Rlink as u8)?; + encode_u16(w, *tag)?; + } + Fcall::Tmkdir(v) => { + encode_u8(w, FcallType::Tmkdir as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.dfid)?; + encode_str(w, &v.name)?; + encode_u32(w, v.mode)?; + encode_u32(w, v.gid)?; + } + Fcall::Rmkdir(v) => { + encode_u8(w, FcallType::Rmkdir as u8)?; + encode_u16(w, *tag)?; + encode_qid(w, &v.qid)?; + } + Fcall::Trenameat(v) => { + encode_u8(w, FcallType::Trenameat as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.olddfid)?; + encode_str(w, &v.oldname)?; + encode_u32(w, v.newdfid)?; + encode_str(w, &v.newname)?; + } + Fcall::Rrenameat(_) => { + encode_u8(w, FcallType::Rrenameat as u8)?; + encode_u16(w, *tag)?; + } + Fcall::Tunlinkat(v) => { + encode_u8(w, FcallType::Tunlinkat as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.dfid)?; + encode_str(w, &v.name)?; + encode_u32(w, v.flags)?; + } + Fcall::Runlinkat(_) => { + encode_u8(w, FcallType::Runlinkat as u8)?; + encode_u16(w, *tag)?; + } + Fcall::Tauth(v) => { + encode_u8(w, FcallType::Tauth as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.afid)?; + encode_str(w, &v.uname)?; + encode_str(w, &v.aname)?; + encode_u32(w, v.n_uname)?; + } + Fcall::Rauth(v) => { + encode_u8(w, FcallType::Rauth as u8)?; + encode_u16(w, *tag)?; + encode_qid(w, &v.aqid)?; + } + Fcall::Tversion(v) => { + encode_u8(w, FcallType::Tversion as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.msize)?; + encode_str(w, &v.version)?; + } + Fcall::Rversion(v) => { + encode_u8(w, FcallType::Rversion as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.msize)?; + encode_str(w, &v.version)?; + } + Fcall::Tflush(v) => { + encode_u8(w, FcallType::Tflush as u8)?; + encode_u16(w, *tag)?; + encode_u16(w, v.oldtag)?; + } + Fcall::Rflush(_) => { + encode_u8(w, FcallType::Rflush as u8)?; + encode_u16(w, *tag)?; + } + Fcall::Twalk(v) => { + encode_u8(w, FcallType::Twalk as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_u32(w, v.new_fid)?; + encode_vec_str(w, &v.wnames)?; + } + Fcall::Rwalk(v) => { + encode_u8(w, FcallType::Rwalk as u8)?; + encode_u16(w, *tag)?; + encode_vec_qid(w, &v.wqids)?; + } + Fcall::Tread(v) => { + encode_u8(w, FcallType::Tread as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_u64(w, v.offset)?; + encode_u32(w, v.count)?; + } + Fcall::Rread(v) => { + encode_u8(w, FcallType::Rread as u8)?; + encode_u16(w, *tag)?; + encode_data_buf(w, &v.data)?; + } + Fcall::Twrite(v) => { + encode_u8(w, FcallType::Twrite as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + encode_u64(w, v.offset)?; + encode_data_buf(w, &v.data)?; + } + Fcall::Rwrite(v) => { + encode_u8(w, FcallType::Rwrite as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.count)?; + } + Fcall::Tclunk(v) => { + encode_u8(w, FcallType::Tclunk as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + } + Fcall::Rclunk(_) => { + encode_u8(w, FcallType::Rclunk as u8)?; + encode_u16(w, *tag)?; + } + Fcall::Tremove(v) => { + encode_u8(w, FcallType::Tremove as u8)?; + encode_u16(w, *tag)?; + encode_u32(w, v.fid)?; + } + Fcall::Rremove(_) => { + encode_u8(w, FcallType::Rremove as u8)?; + encode_u16(w, *tag)?; + } + } + Ok(()) +} + +// ============================================================================ +// Decoding +// ============================================================================ + +struct FcallDecoder<'b> { + buf: &'b [u8], +} + +impl<'b> FcallDecoder<'b> { + fn decode_u8(&mut self) -> Result { + if let Some(v) = self.buf.first() { + self.buf = &self.buf[1..]; + Ok(*v) + } else { + Err(super::Error::InvalidInput) + } + } + + fn decode_u16(&mut self) -> Result { + if self.buf.len() >= 2 { + let v = u16::from_le_bytes(self.buf[0..2].try_into().unwrap()); + self.buf = &self.buf[2..]; + Ok(v) + } else { + Err(super::Error::InvalidInput) + } + } + + fn decode_u32(&mut self) -> Result { + if self.buf.len() >= 4 { + let v = u32::from_le_bytes(self.buf[0..4].try_into().unwrap()); + self.buf = &self.buf[4..]; + Ok(v) + } else { + Err(super::Error::InvalidInput) + } + } + + fn decode_u64(&mut self) -> Result { + if self.buf.len() >= 8 { + let v = u64::from_le_bytes(self.buf[0..8].try_into().unwrap()); + self.buf = &self.buf[8..]; + Ok(v) + } else { + Err(super::Error::InvalidInput) + } + } + + fn decode_str(&mut self) -> Result, super::Error> { + let n = self.decode_u16()? as usize; + if self.buf.len() >= n { + let v = FcallStr::Borrowed(&self.buf[..n]); + self.buf = &self.buf[n..]; + Ok(v) + } else { + Err(super::Error::InvalidInput) + } + } + + fn decode_data_buf(&mut self) -> Result, super::Error> { + let n = self.decode_u32()? as usize; + if self.buf.len() >= n { + let v = &self.buf[..n]; + self.buf = &self.buf[n..]; + Ok(Cow::from(v)) + } else { + Err(super::Error::InvalidInput) + } + } + + fn decode_vec_qid(&mut self) -> Result, super::Error> { + let len = self.decode_u16()?; + let mut v = Vec::new(); + for _ in 0..len { + v.push(self.decode_qid()?); + } + Ok(v) + } + + fn decode_direntrydata(&mut self) -> Result, super::Error> { + let end_len = self.buf.len() - self.decode_u32()? as usize; + let mut v = Vec::new(); + while self.buf.len() > end_len { + v.push(self.decode_direntry()?); + } + Ok(DirEntryData::with(v)) + } + + fn decode_qidtype(&mut self) -> Result { + Ok(QidType::from_bits_truncate(self.decode_u8()?)) + } + + fn decode_locktype(&mut self) -> Result { + Ok(LockType::from_bits_truncate(self.decode_u8()?)) + } + + fn decode_lockstatus(&mut self) -> Result { + Ok(LockStatus::from_bits_truncate(self.decode_u8()?)) + } + + fn decode_lockflag(&mut self) -> Result { + Ok(LockFlag::from_bits_truncate(self.decode_u32()?)) + } + + fn decode_getattrmask(&mut self) -> Result { + Ok(GetattrMask::from_bits_truncate(self.decode_u64()?)) + } + + fn decode_setattrmask(&mut self) -> Result { + Ok(SetattrMask::from_bits_truncate(self.decode_u32()?)) + } + + fn decode_qid(&mut self) -> Result { + Ok(Qid { + typ: self.decode_qidtype()?, + version: self.decode_u32()?, + path: self.decode_u64()?, + }) + } + + fn decode_statfs(&mut self) -> Result { + Ok(Statfs { + typ: self.decode_u32()?, + bsize: self.decode_u32()?, + blocks: self.decode_u64()?, + bfree: self.decode_u64()?, + bavail: self.decode_u64()?, + files: self.decode_u64()?, + ffree: self.decode_u64()?, + fsid: self.decode_u64()?, + namelen: self.decode_u32()?, + }) + } + + fn decode_time(&mut self) -> Result { + Ok(Time { + sec: self.decode_u64()?, + nsec: self.decode_u64()?, + }) + } + + fn decode_stat(&mut self) -> Result { + Ok(Stat { + mode: self.decode_u32()?, + uid: self.decode_u32()?, + gid: self.decode_u32()?, + nlink: self.decode_u64()?, + rdev: self.decode_u64()?, + size: self.decode_u64()?, + blksize: self.decode_u64()?, + blocks: self.decode_u64()?, + atime: self.decode_time()?, + mtime: self.decode_time()?, + ctime: self.decode_time()?, + btime: self.decode_time()?, + generation: self.decode_u64()?, + data_version: self.decode_u64()?, + }) + } + + fn decode_setattr(&mut self) -> Result { + Ok(SetAttr { + mode: self.decode_u32()?, + uid: self.decode_u32()?, + gid: self.decode_u32()?, + size: self.decode_u64()?, + atime: self.decode_time()?, + mtime: self.decode_time()?, + }) + } + + fn decode_direntry(&mut self) -> Result, super::Error> { + Ok(DirEntry { + qid: self.decode_qid()?, + offset: self.decode_u64()?, + typ: self.decode_u8()?, + name: self.decode_str()?, + }) + } + + fn decode_flock(&mut self) -> Result, super::Error> { + Ok(Flock { + typ: self.decode_locktype()?, + flags: self.decode_lockflag()?, + start: self.decode_u64()?, + length: self.decode_u64()?, + proc_id: self.decode_u32()?, + client_id: self.decode_str()?, + }) + } + + fn decode_getlock(&mut self) -> Result, super::Error> { + Ok(Getlock { + typ: self.decode_locktype()?, + start: self.decode_u64()?, + length: self.decode_u64()?, + proc_id: self.decode_u32()?, + client_id: self.decode_str()?, + }) + } + + fn decode(&mut self) -> Result, super::Error> { + let msg_type = FcallType::from_u8(self.decode_u8()?); + let tag = self.decode_u16()?; + let fcall = match msg_type { + Some(FcallType::Rlerror) => Fcall::Rlerror(Rlerror { + ecode: self.decode_u32()?, + }), + Some(FcallType::Tattach) => Fcall::Tattach(Tattach { + fid: self.decode_u32()?, + afid: self.decode_u32()?, + uname: self.decode_str()?, + aname: self.decode_str()?, + n_uname: self.decode_u32()?, + }), + Some(FcallType::Rattach) => Fcall::Rattach(Rattach { + qid: self.decode_qid()?, + }), + Some(FcallType::Tstatfs) => Fcall::Tstatfs(Tstatfs { + fid: self.decode_u32()?, + }), + Some(FcallType::Rstatfs) => Fcall::Rstatfs(Rstatfs { + statfs: self.decode_statfs()?, + }), + Some(FcallType::Tlopen) => Fcall::Tlopen(Tlopen { + fid: self.decode_u32()?, + flags: LOpenFlags::from_bits_truncate(self.decode_u32()?), + }), + Some(FcallType::Rlopen) => Fcall::Rlopen(Rlopen { + qid: self.decode_qid()?, + iounit: self.decode_u32()?, + }), + Some(FcallType::Tlcreate) => Fcall::Tlcreate(Tlcreate { + fid: self.decode_u32()?, + name: self.decode_str()?, + flags: LOpenFlags::from_bits_truncate(self.decode_u32()?), + mode: self.decode_u32()?, + gid: self.decode_u32()?, + }), + Some(FcallType::Rlcreate) => Fcall::Rlcreate(Rlcreate { + qid: self.decode_qid()?, + iounit: self.decode_u32()?, + }), + Some(FcallType::Tsymlink) => Fcall::Tsymlink(Tsymlink { + fid: self.decode_u32()?, + name: self.decode_str()?, + symtgt: self.decode_str()?, + gid: self.decode_u32()?, + }), + Some(FcallType::Rsymlink) => Fcall::Rsymlink(Rsymlink { + qid: self.decode_qid()?, + }), + Some(FcallType::Tmknod) => Fcall::Tmknod(Tmknod { + dfid: self.decode_u32()?, + name: self.decode_str()?, + mode: self.decode_u32()?, + major: self.decode_u32()?, + minor: self.decode_u32()?, + gid: self.decode_u32()?, + }), + Some(FcallType::Rmknod) => Fcall::Rmknod(Rmknod { + qid: self.decode_qid()?, + }), + Some(FcallType::Trename) => Fcall::Trename(Trename { + fid: self.decode_u32()?, + dfid: self.decode_u32()?, + name: self.decode_str()?, + }), + Some(FcallType::Rrename) => Fcall::Rrename(Rrename {}), + Some(FcallType::Treadlink) => Fcall::Treadlink(Treadlink { + fid: self.decode_u32()?, + }), + Some(FcallType::Rreadlink) => Fcall::Rreadlink(Rreadlink { + target: self.decode_str()?, + }), + Some(FcallType::Tgetattr) => Fcall::Tgetattr(Tgetattr { + fid: self.decode_u32()?, + req_mask: self.decode_getattrmask()?, + }), + Some(FcallType::Rgetattr) => Fcall::Rgetattr(Rgetattr { + valid: self.decode_getattrmask()?, + qid: self.decode_qid()?, + stat: self.decode_stat()?, + }), + Some(FcallType::Tsetattr) => Fcall::Tsetattr(Tsetattr { + fid: self.decode_u32()?, + valid: self.decode_setattrmask()?, + stat: self.decode_setattr()?, + }), + Some(FcallType::Rsetattr) => Fcall::Rsetattr(Rsetattr {}), + Some(FcallType::Txattrwalk) => Fcall::Txattrwalk(Txattrwalk { + fid: self.decode_u32()?, + new_fid: self.decode_u32()?, + name: self.decode_str()?, + }), + Some(FcallType::Rxattrwalk) => Fcall::Rxattrwalk(Rxattrwalk { + size: self.decode_u64()?, + }), + Some(FcallType::Txattrcreate) => Fcall::Txattrcreate(Txattrcreate { + fid: self.decode_u32()?, + name: self.decode_str()?, + attr_size: self.decode_u64()?, + flags: self.decode_u32()?, + }), + Some(FcallType::Rxattrcreate) => Fcall::Rxattrcreate(Rxattrcreate {}), + Some(FcallType::Treaddir) => Fcall::Treaddir(Treaddir { + fid: self.decode_u32()?, + offset: self.decode_u64()?, + count: self.decode_u32()?, + }), + Some(FcallType::Rreaddir) => Fcall::Rreaddir(Rreaddir { + data: self.decode_direntrydata()?, + }), + Some(FcallType::Tfsync) => Fcall::Tfsync(Tfsync { + fid: self.decode_u32()?, + datasync: self.decode_u32()?, + }), + Some(FcallType::Rfsync) => Fcall::Rfsync(Rfsync {}), + Some(FcallType::Tlock) => Fcall::Tlock(Tlock { + fid: self.decode_u32()?, + flock: self.decode_flock()?, + }), + Some(FcallType::Rlock) => Fcall::Rlock(Rlock { + status: self.decode_lockstatus()?, + }), + Some(FcallType::Tgetlock) => Fcall::Tgetlock(Tgetlock { + fid: self.decode_u32()?, + flock: self.decode_getlock()?, + }), + Some(FcallType::Rgetlock) => Fcall::Rgetlock(Rgetlock { + flock: self.decode_getlock()?, + }), + Some(FcallType::Tlink) => Fcall::Tlink(Tlink { + dfid: self.decode_u32()?, + fid: self.decode_u32()?, + name: self.decode_str()?, + }), + Some(FcallType::Rlink) => Fcall::Rlink(Rlink {}), + Some(FcallType::Tmkdir) => Fcall::Tmkdir(Tmkdir { + dfid: self.decode_u32()?, + name: self.decode_str()?, + mode: self.decode_u32()?, + gid: self.decode_u32()?, + }), + Some(FcallType::Rmkdir) => Fcall::Rmkdir(Rmkdir { + qid: self.decode_qid()?, + }), + Some(FcallType::Trenameat) => Fcall::Trenameat(Trenameat { + olddfid: self.decode_u32()?, + oldname: self.decode_str()?, + newdfid: self.decode_u32()?, + newname: self.decode_str()?, + }), + Some(FcallType::Rrenameat) => Fcall::Rrenameat(Rrenameat {}), + Some(FcallType::Tunlinkat) => Fcall::Tunlinkat(Tunlinkat { + dfid: self.decode_u32()?, + name: self.decode_str()?, + flags: self.decode_u32()?, + }), + Some(FcallType::Runlinkat) => Fcall::Runlinkat(Runlinkat {}), + Some(FcallType::Tauth) => Fcall::Tauth(Tauth { + afid: self.decode_u32()?, + uname: self.decode_str()?, + aname: self.decode_str()?, + n_uname: self.decode_u32()?, + }), + Some(FcallType::Rauth) => Fcall::Rauth(Rauth { + aqid: self.decode_qid()?, + }), + Some(FcallType::Tversion) => Fcall::Tversion(Tversion { + msize: self.decode_u32()?, + version: self.decode_str()?, + }), + Some(FcallType::Rversion) => Fcall::Rversion(Rversion { + msize: self.decode_u32()?, + version: self.decode_str()?, + }), + Some(FcallType::Tflush) => Fcall::Tflush(Tflush { + oldtag: self.decode_u16()?, + }), + Some(FcallType::Rflush) => Fcall::Rflush(Rflush {}), + Some(FcallType::Twalk) => Fcall::Twalk(Twalk { + fid: self.decode_u32()?, + new_fid: self.decode_u32()?, + wnames: { + let len = self.decode_u16()?; + let mut wnames = Vec::new(); + for _ in 0..len { + wnames.push(self.decode_str()?); + } + wnames + }, + }), + Some(FcallType::Rwalk) => Fcall::Rwalk(Rwalk { + wqids: self.decode_vec_qid()?, + }), + Some(FcallType::Tread) => Fcall::Tread(Tread { + fid: self.decode_u32()?, + offset: self.decode_u64()?, + count: self.decode_u32()?, + }), + Some(FcallType::Rread) => Fcall::Rread(Rread { + data: self.decode_data_buf()?, + }), + Some(FcallType::Twrite) => Fcall::Twrite(Twrite { + fid: self.decode_u32()?, + offset: self.decode_u64()?, + data: self.decode_data_buf()?, + }), + Some(FcallType::Rwrite) => Fcall::Rwrite(Rwrite { + count: self.decode_u32()?, + }), + Some(FcallType::Tclunk) => Fcall::Tclunk(Tclunk { + fid: self.decode_u32()?, + }), + Some(FcallType::Rclunk) => Fcall::Rclunk(Rclunk {}), + Some(FcallType::Tremove) => Fcall::Tremove(Tremove { + fid: self.decode_u32()?, + }), + Some(FcallType::Rremove) => Fcall::Rremove(Rremove {}), + None => return Err(super::Error::InvalidInput), + }; + Ok(TaggedFcall { tag, fcall }) + } +} diff --git a/litebox/src/fs/nine_p/mod.rs b/litebox/src/fs/nine_p/mod.rs new file mode 100644 index 000000000..9d2a1fced --- /dev/null +++ b/litebox/src/fs/nine_p/mod.rs @@ -0,0 +1,806 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +//! A network file system, using the 9P2000.L protocol +//! +//! This module provides a [`FileSystem`] implementation that accesses files over a 9P2000.L +//! network connection. The 9P protocol is a simple, message-based protocol originally designed +//! for Plan 9 from Bell Labs. 9P2000.L is a Linux-specific variant that provides better +//! compatibility with POSIX semantics. +//! +//! # Submodules +//! +//! The 9P implementation is split into several submodules: +//! - `fcall` - Protocol message definitions and encoding/decoding +//! - `cursor` - Write cursor utilities for message encoding +//! - `transport` - Transport layer traits and message I/O +//! - `client` - High-level 9P client for protocol operations + +// Protocol implementation requires various casts that are known to be safe +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::cast_sign_loss)] +#![allow(clippy::cast_lossless)] + +use alloc::string::String; +use alloc::vec::Vec; +use core::num::NonZeroUsize; +use core::sync::atomic::{AtomicU64, Ordering}; + +use thiserror::Error; + +use crate::{LiteBox, sync}; + +mod client; +mod cursor; +mod fcall; + +pub mod transport; + +/// Error type for 9P operations +#[derive(Debug, Error)] +pub enum Error { + /// I/O error during transport + #[error("I/O error")] + Io, + + /// Invalid input (e.g., malformed protocol message) + #[error("Invalid input")] + InvalidInput, + + /// Remote error from the 9P server + #[error("Remote error: {0}")] + Remote(u32), + + /// Path not found + #[error("Path not found")] + NotFound, + + /// File already exists + #[error("File already exists")] + AlreadyExists, + + /// Permission denied + #[error("Permission denied")] + PermissionDenied, + + /// Not a directory + #[error("Not a directory")] + NotADirectory, + + /// Is a directory + #[error("Is a directory")] + IsADirectory, + + /// Name too long + #[error("Name too long")] + NameTooLong, + + /// Connection error + #[error("Connection error")] + Connection, + + /// Operation not supported + #[error("Operation not supported")] + NotSupported, +} + +/// Convert remote error code to our Error type +impl From for Error { + fn from(ecode: u32) -> Self { + // Common POSIX error codes + const ENOENT: u32 = 2; + const EACCES: u32 = 13; + const EEXIST: u32 = 17; + const ENOTDIR: u32 = 20; + const EISDIR: u32 = 21; + const ENAMETOOLONG: u32 = 36; + const ENOSYS: u32 = 38; + const EOPNOTSUPP: u32 = 95; + + match ecode { + ENOENT => Error::NotFound, + EACCES => Error::PermissionDenied, + EEXIST => Error::AlreadyExists, + ENOTDIR => Error::NotADirectory, + EISDIR => Error::IsADirectory, + ENAMETOOLONG => Error::NameTooLong, + ENOSYS | EOPNOTSUPP => Error::NotSupported, + _ => Error::Remote(ecode), + } + } +} + +/// A backing implementation for [`FileSystem`](super::FileSystem) using a 9P2000.L-based network +/// file system. +/// +/// This filesystem implementation communicates with a 9P server to provide access to remote files. +/// All file operations are translated into 9P protocol messages that are sent to the server. +/// +/// # Type Parameters +/// +/// - `Platform`: The platform provider that supplies synchronization primitives and other +/// platform-specific functionality. +/// - `T`: The transport type that implements both `Read` and `Write` traits. +pub struct FileSystem< + Platform: sync::RawSyncPrimitivesProvider, + T: transport::Read + transport::Write, +> { + /// Reference to the LiteBox instance + litebox: LiteBox, + /// 9P client for protocol operations + client: client::Client, + /// Root fid (attached to the root of the remote filesystem) + root_fid: fcall::Fid, +} + +impl + FileSystem +{ + /// Construct a new `FileSystem` instance + /// + /// This function is expected to only be invoked once per platform, as an initialization step, + /// and the created `FileSystem` handle is expected to be shared across all usage over the + /// system. + /// + /// # Arguments + /// + /// * `litebox` - Reference to the LiteBox instance for platform access + /// * `transport` - The transport for 9P communication + /// * `msize` - Maximum message size to negotiate + /// * `aname` - Attach name (typically the root directory path) + /// + /// # Errors + /// + /// Returns an error if version negotiation or attach fails. + pub fn new( + litebox: &LiteBox, + transport: T, + msize: u32, + aname: &str, + ) -> Result { + let client = client::Client::new(transport, msize)?; + let (_, root_fid) = client.attach("", aname)?; + + Ok(Self { + litebox: litebox.clone(), + client, + root_fid, + }) + } + + /// Parse a path string and split it into path components + fn parse_path(path: &str) -> Vec<&str> { + path.split('/') + .filter(|s| !s.is_empty() && *s != ".") + .collect() + } + + /// Walk to a path and return the fid + fn walk_to(&self, path: &str) -> Result { + let components = Self::parse_path(path); + if components.is_empty() { + // Clone the root fid + self.client.clone_fid(self.root_fid) + } else { + let (_, fid) = self.client.walk(self.root_fid, &components)?; + Ok(fid) + } + } + + /// Walk to the parent of a path and return the parent fid and the name + fn walk_to_parent<'a>(&self, path: &'a str) -> Result<(fcall::Fid, &'a str), Error> { + let components = Self::parse_path(path); + if components.is_empty() { + return Err(Error::InvalidInput); + } + + let name = components.last().unwrap(); + let parent_components = &components[..components.len() - 1]; + + if parent_components.is_empty() { + let parent_fid = self.client.clone_fid(self.root_fid)?; + Ok((parent_fid, name)) + } else { + let (_, parent_fid) = self.client.walk(self.root_fid, parent_components)?; + Ok((parent_fid, name)) + } + } + + /// Convert FileSystem OFlags to 9P LOpenFlags + fn oflags_to_lopen(flags: super::OFlags) -> fcall::LOpenFlags { + let mut lflags = fcall::LOpenFlags::empty(); + + // Access mode (RDONLY is 0, so we only check for WRONLY and RDWR) + if flags.contains(super::OFlags::RDWR) { + lflags |= fcall::LOpenFlags::O_RDWR; + } else if flags.contains(super::OFlags::WRONLY) { + lflags |= fcall::LOpenFlags::O_WRONLY; + } + // RDONLY is implicit if neither WRONLY nor RDWR + + if flags.contains(super::OFlags::CREAT) { + lflags |= fcall::LOpenFlags::O_CREAT; + } + if flags.contains(super::OFlags::EXCL) { + lflags |= fcall::LOpenFlags::O_EXCL; + } + if flags.contains(super::OFlags::TRUNC) { + lflags |= fcall::LOpenFlags::O_TRUNC; + } + if flags.contains(super::OFlags::APPEND) { + lflags |= fcall::LOpenFlags::O_APPEND; + } + if flags.contains(super::OFlags::DIRECTORY) { + lflags |= fcall::LOpenFlags::O_DIRECTORY; + } + if flags.contains(super::OFlags::NOFOLLOW) { + lflags |= fcall::LOpenFlags::O_NOFOLLOW; + } + if flags.contains(super::OFlags::NONBLOCK) { + lflags |= fcall::LOpenFlags::O_NONBLOCK; + } + if flags.contains(super::OFlags::SYNC) { + lflags |= fcall::LOpenFlags::O_SYNC; + } + if flags.contains(super::OFlags::DSYNC) { + lflags |= fcall::LOpenFlags::O_DSYNC; + } + if flags.contains(super::OFlags::DIRECT) { + lflags |= fcall::LOpenFlags::O_DIRECT; + } + if flags.contains(super::OFlags::NOATIME) { + lflags |= fcall::LOpenFlags::O_NOATIME; + } + + lflags + } + + /// Convert a Qid type to our FileType + fn qid_type_to_file_type(qid_type: fcall::QidType) -> super::FileType { + if qid_type.contains(fcall::QidType::DIR) { + super::FileType::Directory + } else { + super::FileType::RegularFile + } + } + + /// Convert getattr response to FileStatus + fn rgetattr_to_file_status(attr: &fcall::Rgetattr) -> super::FileStatus { + let file_type = Self::qid_type_to_file_type(attr.qid.typ); + + super::FileStatus { + file_type, + mode: super::Mode::from_bits_truncate(attr.stat.mode), + size: attr.stat.size as usize, + owner: super::UserInfo { + user: attr.stat.uid as u16, + group: attr.stat.gid as u16, + }, + node_info: super::NodeInfo { + dev: attr.stat.rdev as usize, + ino: attr.qid.path as usize, + rdev: NonZeroUsize::new(attr.stat.rdev as usize), + }, + blksize: attr.stat.blksize as usize, + } + } +} + +impl + super::private::Sealed for FileSystem +{ +} + +impl + super::FileSystem for FileSystem +{ + fn open( + &self, + path: impl crate::path::Arg, + flags: super::OFlags, + mode: super::Mode, + ) -> Result, super::errors::OpenError> { + let path_str = path + .as_rust_str() + .map_err(|_| super::errors::PathError::InvalidPathname)?; + + let lflags = Self::oflags_to_lopen(flags); + let needs_create = flags.contains(super::OFlags::CREAT); + + // Determine read/write permissions from flags + let read_allowed = !flags.contains(super::OFlags::WRONLY); + let write_allowed = + flags.contains(super::OFlags::WRONLY) || flags.contains(super::OFlags::RDWR); + + let (fid, qid) = if needs_create { + // Try to walk to the parent and create the file + let (parent_fid, name) = self + .walk_to_parent(path_str) + .map_err(|_| super::errors::PathError::NoSuchFileOrDirectory)?; + + match self.client.create(parent_fid, name, lflags, mode.bits(), 0) { + Ok(r) => { + // create() transforms the parent_fid into the file fid + (parent_fid, r.qid) + } + Err(Error::AlreadyExists) if !flags.contains(super::OFlags::EXCL) => { + // File exists and EXCL is not set, try to open it + let _ = self.client.clunk(parent_fid); + let fid = self + .walk_to(path_str) + .map_err(|_| super::errors::PathError::NoSuchFileOrDirectory)?; + let r = self.client.open(fid, lflags).map_err(|_| { + let _ = self.client.clunk(fid); + super::errors::OpenError::AccessNotAllowed + })?; + (fid, r.qid) + } + Err(Error::AlreadyExists) => { + let _ = self.client.clunk(parent_fid); + return Err(super::errors::OpenError::AlreadyExists); + } + Err(_) => { + let _ = self.client.clunk(parent_fid); + return Err(super::errors::OpenError::AccessNotAllowed); + } + } + } else { + // Just walk and open + let fid = self + .walk_to(path_str) + .map_err(|_| super::errors::PathError::NoSuchFileOrDirectory)?; + let r = self.client.open(fid, lflags).map_err(|_| { + let _ = self.client.clunk(fid); + super::errors::OpenError::AccessNotAllowed + })?; + (fid, r.qid) + }; + + // Wrap in a file descriptor + let descriptor = Descriptor { + fid, + offset: AtomicU64::new(0), + read_allowed, + write_allowed, + qid, + }; + + let fd = self.litebox.descriptor_table_mut().insert(descriptor); + Ok(fd) + } + + fn close(&self, fd: &FileFd) -> Result<(), super::errors::CloseError> { + let descriptor_table = self.litebox.descriptor_table(); + if let Some(entry) = descriptor_table.get_entry(fd) { + let _ = self.client.clunk(entry.entry.fid); + } + self.litebox.descriptor_table_mut().remove(fd); + Ok(()) + } + + fn read( + &self, + fd: &FileFd, + buf: &mut [u8], + offset: Option, + ) -> Result { + let descriptor_table = self.litebox.descriptor_table(); + let entry = descriptor_table + .get_entry(fd) + .ok_or(super::errors::ReadError::ClosedFd)?; + let desc = &entry.entry; + + if !desc.read_allowed { + return Err(super::errors::ReadError::NotForReading); + } + + // Determine offset to use + let read_offset = match offset { + Some(o) => o as u64, + None => desc.offset.load(Ordering::SeqCst), + }; + + // Read data + let count = buf + .len() + .min((self.client.msize() - fcall::IOHDRSZ) as usize); + let data = self + .client + .read(desc.fid, read_offset, count as u32) + .map_err(|_| super::errors::ReadError::ClosedFd)?; + + let bytes_read = data.len(); + buf[..bytes_read].copy_from_slice(&data); + + // Update offset if not using explicit offset + if offset.is_none() { + desc.offset.fetch_add(bytes_read as u64, Ordering::SeqCst); + } + + Ok(bytes_read) + } + + fn write( + &self, + fd: &FileFd, + buf: &[u8], + offset: Option, + ) -> Result { + let descriptor_table = self.litebox.descriptor_table(); + let entry = descriptor_table + .get_entry(fd) + .ok_or(super::errors::WriteError::ClosedFd)?; + let desc = &entry.entry; + + if !desc.write_allowed { + return Err(super::errors::WriteError::NotForWriting); + } + + // Determine offset to use + let write_offset = match offset { + Some(o) => o as u64, + None => desc.offset.load(Ordering::SeqCst), + }; + + // Write data + let bytes_written = self + .client + .write(desc.fid, write_offset, buf) + .map_err(|_| super::errors::WriteError::ClosedFd)?; + + // Update offset if not using explicit offset + if offset.is_none() { + desc.offset + .fetch_add(bytes_written as u64, Ordering::SeqCst); + } + + Ok(bytes_written as usize) + } + + fn seek( + &self, + fd: &FileFd, + offset: isize, + whence: super::SeekWhence, + ) -> Result { + let descriptor_table = self.litebox.descriptor_table(); + let entry = descriptor_table + .get_entry(fd) + .ok_or(super::errors::SeekError::ClosedFd)?; + let desc = &entry.entry; + + let current_offset = desc.offset.load(Ordering::SeqCst); + + let new_offset = match whence { + super::SeekWhence::RelativeToBeginning => { + if offset < 0 { + return Err(super::errors::SeekError::InvalidOffset); + } + offset as u64 + } + super::SeekWhence::RelativeToCurrentOffset => { + if offset < 0 { + let neg_offset = (-offset) as u64; + if neg_offset > current_offset { + return Err(super::errors::SeekError::InvalidOffset); + } + current_offset - neg_offset + } else { + current_offset + offset as u64 + } + } + super::SeekWhence::RelativeToEnd => { + // Need to get file size + let attr = self + .client + .getattr(desc.fid, fcall::GetattrMask::SIZE) + .map_err(|_| super::errors::SeekError::ClosedFd)?; + let size = attr.stat.size; + + if offset < 0 { + let neg_offset = (-offset) as u64; + if neg_offset > size { + return Err(super::errors::SeekError::InvalidOffset); + } + size - neg_offset + } else { + size + offset as u64 + } + } + }; + + desc.offset.store(new_offset, Ordering::SeqCst); + Ok(new_offset as usize) + } + + fn truncate( + &self, + fd: &FileFd, + length: usize, + reset_offset: bool, + ) -> Result<(), super::errors::TruncateError> { + let descriptor_table = self.litebox.descriptor_table(); + let entry = descriptor_table + .get_entry(fd) + .ok_or(super::errors::TruncateError::ClosedFd)?; + let desc = &entry.entry; + + if !desc.write_allowed { + return Err(super::errors::TruncateError::NotForWriting); + } + + if desc.qid.typ.contains(fcall::QidType::DIR) { + return Err(super::errors::TruncateError::IsDirectory); + } + + let stat = fcall::SetAttr { + mode: 0, + uid: 0, + gid: 0, + size: length as u64, + atime: fcall::Time::default(), + mtime: fcall::Time::default(), + }; + + self.client + .setattr(desc.fid, fcall::SetattrMask::SIZE, stat) + .map_err(|_| super::errors::TruncateError::ClosedFd)?; + + if reset_offset { + desc.offset.store(0, Ordering::SeqCst); + } + + Ok(()) + } + + fn chmod( + &self, + path: impl crate::path::Arg, + mode: super::Mode, + ) -> Result<(), super::errors::ChmodError> { + let path_str = path + .as_rust_str() + .map_err(|_| super::errors::PathError::InvalidPathname)?; + + let fid = self.walk_to(path_str).map_err(|e| match e { + Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), + _ => super::errors::ChmodError::ReadOnlyFileSystem, + })?; + + let stat = fcall::SetAttr { + mode: mode.bits(), + uid: 0, + gid: 0, + size: 0, + atime: fcall::Time::default(), + mtime: fcall::Time::default(), + }; + + let result = self.client.setattr(fid, fcall::SetattrMask::MODE, stat); + let _ = self.client.clunk(fid); + + result.map_err(|_| super::errors::ChmodError::NotTheOwner) + } + + fn chown( + &self, + path: impl crate::path::Arg, + user: Option, + group: Option, + ) -> Result<(), super::errors::ChownError> { + let path_str = path + .as_rust_str() + .map_err(|_| super::errors::PathError::InvalidPathname)?; + + let fid = self.walk_to(path_str).map_err(|e| match e { + Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), + _ => super::errors::ChownError::ReadOnlyFileSystem, + })?; + + let mut valid = fcall::SetattrMask::empty(); + let uid = match user { + Some(u) => { + valid |= fcall::SetattrMask::UID; + u as u32 + } + None => 0, + }; + let gid = match group { + Some(g) => { + valid |= fcall::SetattrMask::GID; + g as u32 + } + None => 0, + }; + let stat = fcall::SetAttr { + mode: 0, + uid, + gid, + size: 0, + atime: fcall::Time::default(), + mtime: fcall::Time::default(), + }; + + let result = self.client.setattr(fid, valid, stat); + let _ = self.client.clunk(fid); + + result.map_err(|_| super::errors::ChownError::NotTheOwner) + } + + fn unlink(&self, path: impl crate::path::Arg) -> Result<(), super::errors::UnlinkError> { + let path_str = path + .as_rust_str() + .map_err(|_| super::errors::PathError::InvalidPathname)?; + + let (parent_fid, name) = self.walk_to_parent(path_str).map_err(|e| match e { + Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), + _ => super::errors::UnlinkError::NoWritePerms, + })?; + + let result = self.client.unlinkat(parent_fid, name, 0); + let _ = self.client.clunk(parent_fid); + + result.map_err(|e| match e { + Error::IsADirectory => super::errors::UnlinkError::IsADirectory, + Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), + Error::PermissionDenied => super::errors::UnlinkError::NoWritePerms, + _ => super::errors::UnlinkError::ReadOnlyFileSystem, + }) + } + + fn mkdir( + &self, + path: impl crate::path::Arg, + mode: super::Mode, + ) -> Result<(), super::errors::MkdirError> { + let path_str = path + .as_rust_str() + .map_err(|_| super::errors::PathError::InvalidPathname)?; + + let (parent_fid, name) = self.walk_to_parent(path_str).map_err(|e| match e { + Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), + _ => super::errors::MkdirError::NoWritePerms, + })?; + + let result = self.client.mkdir(parent_fid, name, mode.bits(), 0); + let _ = self.client.clunk(parent_fid); + + result.map_err(|e| match e { + Error::AlreadyExists => super::errors::MkdirError::AlreadyExists, + Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), + Error::PermissionDenied => super::errors::MkdirError::NoWritePerms, + _ => super::errors::MkdirError::ReadOnlyFileSystem, + })?; + + Ok(()) + } + + fn rmdir(&self, path: impl crate::path::Arg) -> Result<(), super::errors::RmdirError> { + const AT_REMOVEDIR: u32 = 0x200; + + let path_str = path + .as_rust_str() + .map_err(|_| super::errors::PathError::InvalidPathname)?; + + let (parent_fid, name) = self.walk_to_parent(path_str).map_err(|e| match e { + Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), + _ => super::errors::RmdirError::NoWritePerms, + })?; + + let result = self.client.unlinkat(parent_fid, name, AT_REMOVEDIR); + let _ = self.client.clunk(parent_fid); + + result.map_err(|e| match e { + Error::NotADirectory => super::errors::RmdirError::NotADirectory, + Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), + Error::PermissionDenied => super::errors::RmdirError::NoWritePerms, + _ => super::errors::RmdirError::ReadOnlyFileSystem, + }) + } + + fn read_dir( + &self, + fd: &FileFd, + ) -> Result, super::errors::ReadDirError> { + let descriptor_table = self.litebox.descriptor_table(); + let entry = descriptor_table + .get_entry(fd) + .ok_or(super::errors::ReadDirError::ClosedFd)?; + let desc = &entry.entry; + + if !desc.qid.typ.contains(fcall::QidType::DIR) { + return Err(super::errors::ReadDirError::NotADirectory); + } + + let entries = self + .client + .readdir_all(desc.fid) + .map_err(|_| super::errors::ReadDirError::ClosedFd)?; + + let dir_entries: Vec = entries + .into_iter() + .filter(|e| { + let name = e.name.as_bytes(); + name != b"." && name != b".." + }) + .map(|e| { + let file_type = if e.typ == fcall::QidType::DIR.bits() { + super::FileType::Directory + } else { + super::FileType::RegularFile + }; + + super::DirEntry { + name: String::from_utf8_lossy(e.name.as_bytes()).into_owned(), + file_type, + ino_info: Some(super::NodeInfo { + dev: 0, + ino: e.qid.path as usize, + rdev: None, + }), + } + }) + .collect(); + + Ok(dir_entries) + } + + fn file_status( + &self, + path: impl crate::path::Arg, + ) -> Result { + let path_str = path + .as_rust_str() + .map_err(|_| super::errors::PathError::InvalidPathname)?; + + let fid = self + .walk_to(path_str) + .map_err(|_| super::errors::PathError::NoSuchFileOrDirectory)?; + + let result = self.client.getattr(fid, fcall::GetattrMask::ALL); + let _ = self.client.clunk(fid); + + let attr = result.map_err(|_| super::errors::PathError::NoSuchFileOrDirectory)?; + Ok(Self::rgetattr_to_file_status(&attr)) + } + + fn fd_file_status( + &self, + fd: &FileFd, + ) -> Result { + let descriptor_table = self.litebox.descriptor_table(); + let entry = descriptor_table + .get_entry(fd) + .ok_or(super::errors::FileStatusError::ClosedFd)?; + let desc = &entry.entry; + + let attr = self + .client + .getattr(desc.fid, fcall::GetattrMask::ALL) + .map_err(|_| super::errors::FileStatusError::ClosedFd)?; + + Ok(Self::rgetattr_to_file_status(&attr)) + } +} + +/// Internal descriptor state for a 9P file descriptor +#[derive(Debug)] +struct Descriptor { + /// The 9P fid for this file + fid: fcall::Fid, + /// Current file offset (9P doesn't track this server-side) + offset: AtomicU64, + /// Whether this file is opened for reading + read_allowed: bool, + /// Whether this file is opened for writing + write_allowed: bool, + /// The qid of the file (contains type and unique ID) + qid: fcall::Qid, +} + +crate::fd::enable_fds_for_subsystem! { + @Platform: { sync::RawSyncPrimitivesProvider }, T: { transport::Read + transport::Write }; + FileSystem; + Descriptor; + -> FileFd; +} diff --git a/litebox/src/fs/nine_p/transport.rs b/litebox/src/fs/nine_p/transport.rs new file mode 100644 index 000000000..7906ae19f --- /dev/null +++ b/litebox/src/fs/nine_p/transport.rs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +//! 9P transport layer abstraction +//! +//! This module defines traits for reading and writing 9P protocol messages over +//! an underlying transport (e.g., TCP socket, virtio-9p, etc.). + +use alloc::vec::Vec; + +/// Trait for reading bytes from a transport +pub trait Read { + /// Read bytes into the buffer + /// + /// Returns the number of bytes read + fn read(&mut self, buf: &mut [u8]) -> Result; + + /// Read exactly `buf.len()` bytes into the buffer + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), super::Error> { + let mut total_read = 0; + while total_read < buf.len() { + let n = self.read(&mut buf[total_read..])?; + if n == 0 { + return Err(super::Error::Io); + } + total_read += n; + } + Ok(()) + } +} + +/// Trait for writing bytes to a transport +pub trait Write { + /// Write bytes from the buffer + /// + /// Returns the number of bytes written + fn write(&mut self, buf: &[u8]) -> Result; + + /// Write all bytes from the buffer + fn write_all(&mut self, buf: &[u8]) -> Result<(), super::Error> { + let mut total_written = 0; + while total_written < buf.len() { + let n = self.write(&buf[total_written..])?; + if n == 0 { + return Err(super::Error::Io); + } + total_written += n; + } + Ok(()) + } +} + +/// Write a 9P message to a transport +pub(crate) fn write_message( + w: &mut W, + buf: &mut Vec, + fcall: &super::fcall::TaggedFcall<'_>, +) -> Result<(), super::Error> { + fcall.encode_to_buf(buf)?; + w.write_all(&buf[..]) +} + +/// Read a 9P message size header (4 bytes) and then the full message +pub(crate) fn read_to_buf(r: &mut R, buf: &mut Vec) -> Result<(), super::Error> { + buf.resize(4, 0); + r.read_exact(&mut buf[..])?; + let sz = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize; + if sz < 7 { + // Minimum message size: size(4) + type(1) + tag(2) + return Err(super::Error::InvalidInput); + } + if sz > buf.capacity() { + buf.reserve(sz - buf.len()); + } + buf.resize(sz, 0); + r.read_exact(&mut buf[4..])?; + Ok(()) +} + +/// Read a 9P message from a transport +pub(crate) fn read_message<'a, R: Read>( + r: &mut R, + buf: &'a mut Vec, +) -> Result, super::Error> { + read_to_buf(r, buf)?; + super::fcall::TaggedFcall::decode(&buf[..]) +} From e4ba56ab72120d7ddc452b7b1057796b5c9eab7f Mon Sep 17 00:00:00 2001 From: weitengchen Date: Wed, 11 Feb 2026 10:41:38 -0800 Subject: [PATCH 02/16] fix 9p fs --- litebox/src/fs/nine_p/client.rs | 328 ++++++------- litebox/src/fs/nine_p/cursor.rs | 157 ------- litebox/src/fs/nine_p/fcall.rs | 246 +++++----- litebox/src/fs/nine_p/mod.rs | 718 ++++++++++++++++++----------- litebox/src/fs/nine_p/transport.rs | 45 +- 5 files changed, 770 insertions(+), 724 deletions(-) delete mode 100644 litebox/src/fs/nine_p/cursor.rs diff --git a/litebox/src/fs/nine_p/client.rs b/litebox/src/fs/nine_p/client.rs index b09d9a562..8947e3a0f 100644 --- a/litebox/src/fs/nine_p/client.rs +++ b/litebox/src/fs/nine_p/client.rs @@ -5,7 +5,6 @@ //! //! This module provides a high-level client for the 9P2000.L protocol. -use alloc::vec; use alloc::vec::Vec; use core::sync::atomic::{AtomicU32, Ordering}; @@ -15,9 +14,6 @@ use super::Error; use super::fcall::{self, Fcall, FcallStr, GetattrMask, TaggedFcall}; use super::transport::{self, Read, Write}; -/// Client identifier for lock operations -pub(crate) const CLIENT_ID: &str = "litebox-9p"; - /// ID generator for fids struct IdGenerator { next: u32, @@ -48,7 +44,7 @@ impl IdGenerator { } /// Fid generator with thread-safe access -pub(crate) struct FidGenerator { +struct FidGenerator { inner: Mutex, } @@ -60,25 +56,25 @@ impl Default for FidGenerator { impl FidGenerator { /// Create a new fid generator - pub(crate) fn new() -> Self { + fn new() -> Self { FidGenerator { inner: Mutex::new(IdGenerator::new()), } } /// Allocate a new fid - pub(crate) fn next(&self) -> u32 { + fn next(&self) -> u32 { self.inner.lock().next() } /// Release a fid for reuse - pub(crate) fn free(&self, id: u32) { + fn free(&self, id: u32) { self.inner.lock().free(id); } } /// 9P client state for writing to the connection -pub(crate) struct ClientWriteState { +struct ClientWriteState { /// The underlying transport transport: T, /// Write buffer @@ -89,7 +85,7 @@ pub(crate) struct ClientWriteState { /// /// This client provides synchronous 9P protocol operations. It uses a transport /// that implements both Read and Write traits. -pub(crate) struct Client { +pub(super) struct Client { /// Maximum message size negotiated with server msize: u32, /// Write state protected by a mutex @@ -108,12 +104,12 @@ impl Client { /// # Arguments /// * `transport` - The underlying transport for read/write operations /// * `max_msize` - Maximum message size to request - pub(crate) fn new(mut transport: T, max_msize: u32) -> Result { + pub(super) fn new(mut transport: T, max_msize: u32) -> Result { const MIN_MSIZE: u32 = 4096 + fcall::READDIRHDRSZ; - let bufsize = max_msize.max(MIN_MSIZE) as usize; + let bufsize = max_msize.max(MIN_MSIZE); - let mut wbuf = Vec::with_capacity(bufsize); - let mut rbuf = Vec::with_capacity(bufsize); + let mut wbuf = Vec::with_capacity(bufsize as usize); + let mut rbuf = Vec::with_capacity(bufsize as usize); // Perform version handshake transport::write_message( @@ -122,11 +118,12 @@ impl Client { &TaggedFcall { tag: fcall::NOTAG, fcall: Fcall::Tversion(fcall::Tversion { - msize: bufsize as u32, + msize: bufsize, version: "9P2000.L".into(), }), }, - )?; + ) + .map_err(|_| Error::Io)?; let response = transport::read_message(&mut transport, &mut rbuf)?; @@ -136,15 +133,15 @@ impl Client { fcall: Fcall::Rversion(fcall::Rversion { msize, version }), } => { if version.as_bytes() != b"9P2000.L" { - return Err(Error::InvalidInput); + return Err(Error::InvalidResponse); } - msize.min(bufsize as u32) + msize.min(bufsize) } TaggedFcall { fcall: Fcall::Rlerror(e), .. - } => return Err(e.into_error()), - _ => return Err(Error::InvalidInput), + } => return Err(Error::from(e)), + _ => return Err(Error::InvalidResponse), }; wbuf.truncate(msize as usize); @@ -159,18 +156,16 @@ impl Client { }) } - /// Get the negotiated message size - pub(crate) fn msize(&self) -> u32 { - self.msize - } - /// Send a request and wait for the response + /// + /// TODO: support async operations fn fcall(&self, fcall: Fcall<'_>) -> Result, Error> { let tag = self.next_tag.fetch_add(1, Ordering::Relaxed) as u16; let mut write_state = self.write_state.lock(); let ClientWriteState { transport, wbuf } = &mut *write_state; - transport::write_message(transport, wbuf, &TaggedFcall { tag, fcall })?; + transport::write_message(transport, wbuf, &TaggedFcall { tag, fcall }) + .map_err(|_| Error::Io)?; let mut rbuf = self.rbuf.lock(); @@ -184,7 +179,7 @@ impl Client { } /// Attach to a remote filesystem - pub(crate) fn attach( + pub(super) fn attach( &self, uname: &str, aname: &str, @@ -200,118 +195,118 @@ impl Client { Fcall::Rattach(fcall::Rattach { qid }) => Ok((qid, fid)), Fcall::Rlerror(e) => { self.fids.free(fid); - Err(e.into_error()) + Err(Error::from(e)) } _ => { self.fids.free(fid); - Err(Error::InvalidInput) + Err(Error::InvalidResponse) } } } - /// Walk to a path from a given fid + /// Walks the path from the given fid. /// - /// Returns the qids for each path component and a new fid for the final location - pub(crate) fn walk<'a, S: Into> + Clone>( + /// The given wnames should not exceed the maximum number of elements (fcall::MAXWELEM), + /// which is checked at the beginning of the function. This is an internal function that + /// is used by [`walk_chunked`](Client::walk_chunked), which handles the case where the number of elements exceeds the limit. + fn walk_once( &self, fid: fcall::Fid, - wnames: &[S], + wnames: &[FcallStr], + ) -> Result<(Vec, fcall::Fid), Error> { + if wnames.len() > fcall::MAXWELEM { + return Err(Error::NameTooLong); + } + let new_fid = self.fids.next(); + let ret = match self.fcall(Fcall::Twalk(fcall::Twalk { + fid, + new_fid, + wnames: wnames.to_vec(), + }))? { + Fcall::Rwalk(fcall::Rwalk { wqids }) => Ok((wqids, new_fid)), + Fcall::Rlerror(err) => Err(Error::from(err)), + _ => Err(Error::InvalidResponse), + }; + if ret.is_err() { + self.fids.free(new_fid); + } + ret + } + + /// Walks the path from the given fid, handling paths longer than fcall::MAXWELEM by walking in chunks. + /// + /// Returns the qids for each path component and a new fid for the final location on success. + fn walk_chunked( + &self, + fid: fcall::Fid, + wnames: &[FcallStr], ) -> Result<(Vec, fcall::Fid), Error> { if wnames.is_empty() { - // Clone the fid - let new_fid = self.fids.next(); - match self.fcall(Fcall::Twalk(fcall::Twalk { - fid, - new_fid, - wnames: vec![], - }))? { - Fcall::Rwalk(fcall::Rwalk { wqids }) => Ok((wqids, new_fid)), - Fcall::Rlerror(e) => { - self.fids.free(new_fid); - Err(e.into_error()) - } - _ => { - self.fids.free(new_fid); - Err(Error::InvalidInput) - } + return self.walk_once(fid, wnames); + } + let mut wqids = Vec::with_capacity(fcall::MAXWELEM); + let mut f = fid; + for wnames in wnames.chunks(fcall::MAXWELEM) { + let (mut new_wqids, new_f) = self.walk_once(f, wnames)?; + let new_len = new_wqids.len(); + wqids.append(&mut new_wqids); + // Clunk the old fid if it's not the original fid + if f != fid { + let _ = self.clunk(f); } - } else { - // Walk in chunks of MAXWELEM - let mut current_fid = fid; - let mut all_qids = Vec::with_capacity(wnames.len()); - let mut new_fid = fid; - - for chunk in wnames.chunks(fcall::MAXWELEM) { - new_fid = self.fids.next(); - let wnames_vec: Vec> = - chunk.iter().map(|s| s.clone().into()).collect(); - - match self.fcall(Fcall::Twalk(fcall::Twalk { - fid: current_fid, - new_fid, - wnames: wnames_vec, - }))? { - Fcall::Rwalk(fcall::Rwalk { mut wqids }) => { - let expected_len = chunk.len(); - let actual_len = wqids.len(); - all_qids.append(&mut wqids); - - // Clunk intermediate fid if not the original - if current_fid != fid { - let _ = self.clunk(current_fid); - } - - if actual_len < expected_len { - // Walk failed partway - self.fids.free(new_fid); - return Err(Error::NotFound); - } - - current_fid = new_fid; - } - Fcall::Rlerror(e) => { - self.fids.free(new_fid); - if current_fid != fid { - let _ = self.clunk(current_fid); - } - return Err(e.into_error()); - } - _ => { - self.fids.free(new_fid); - if current_fid != fid { - let _ = self.clunk(current_fid); - } - return Err(Error::InvalidInput); - } + f = new_f; + // It means that the walk failed at the nwqid-th element + if new_len < wnames.len() { + if wqids + .last() + .is_some_and(|e| e.typ == fcall::QidType::SYMLINK) + { + todo!("symlink"); } + let _ = self.clunk(f); + return Err(Error::NotFound); } - - Ok((all_qids, new_fid)) } + Ok((wqids, f)) + } + + /// Walk to a path from a given fid + /// + /// Returns the qids for each path component and a new fid for the final location + pub(super) fn walk<'a, S: Into> + AsRef<[u8]>>( + &self, + fid: fcall::Fid, + wnames: &[S], + ) -> Result<(Vec, fcall::Fid), Error> { + let wnames: Vec = wnames.iter().map(Into::into).collect(); + self.walk_chunked(fid, &wnames) } /// Open a file - pub(crate) fn open( + pub(super) fn open( &self, fid: fcall::Fid, flags: fcall::LOpenFlags, - ) -> Result { + ) -> Result { match self.fcall(Fcall::Tlopen(fcall::Tlopen { fid, flags }))? { - Fcall::Rlopen(r) => Ok(r), - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + Fcall::Rlopen(fcall::Rlopen { qid, .. }) => Ok(qid), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), } } - /// Create a file - pub(crate) fn create( + /// Create a file with the given name and flags. + /// + /// The input dfid initially represents the parent directory of the new file. + /// After the call it represents the new file. + pub(super) fn create( &self, dfid: fcall::Fid, name: &str, flags: fcall::LOpenFlags, mode: u32, gid: u32, - ) -> Result { + ) -> Result<(fcall::Qid, fcall::Fid), Error> { match self.fcall(Fcall::Tlcreate(fcall::Tlcreate { fid: dfid, name: name.into(), @@ -319,51 +314,63 @@ impl Client { mode, gid, }))? { - Fcall::Rlcreate(r) => Ok(r), - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + Fcall::Rlcreate(fcall::Rlcreate { qid, iounit: _ }) => Ok((qid, dfid)), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), } } /// Read from a file - pub(crate) fn read(&self, fid: fcall::Fid, offset: u64, count: u32) -> Result, Error> { - let count = count.min(self.msize - fcall::IOHDRSZ); - match self.fcall(Fcall::Tread(fcall::Tread { fid, offset, count }))? { - Fcall::Rread(fcall::Rread { data }) => Ok(data.into_owned()), - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + pub(super) fn read( + &self, + fid: fcall::Fid, + offset: u64, + buf: &mut [u8], + ) -> Result { + let count = buf.len().min((self.msize - fcall::IOHDRSZ) as usize); + match self.fcall(Fcall::Tread(fcall::Tread { + fid, + offset, + count: count as u32, + }))? { + Fcall::Rread(fcall::Rread { data }) => { + buf[..data.len()].copy_from_slice(&data); + Ok(data.len()) + } + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), } } /// Write to a file - pub(crate) fn write(&self, fid: fcall::Fid, offset: u64, data: &[u8]) -> Result { + pub(super) fn write(&self, fid: fcall::Fid, offset: u64, data: &[u8]) -> Result { let count = data.len().min((self.msize - fcall::IOHDRSZ) as usize); match self.fcall(Fcall::Twrite(fcall::Twrite { fid, offset, data: alloc::borrow::Cow::Borrowed(&data[..count]), }))? { - Fcall::Rwrite(fcall::Rwrite { count }) => Ok(count), - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + Fcall::Rwrite(fcall::Rwrite { count }) => Ok(count as usize), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), } } /// Get file attributes - pub(crate) fn getattr( + pub(super) fn getattr( &self, fid: fcall::Fid, req_mask: GetattrMask, ) -> Result { match self.fcall(Fcall::Tgetattr(fcall::Tgetattr { fid, req_mask }))? { Fcall::Rgetattr(r) => Ok(r), - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), } } /// Set file attributes - pub(crate) fn setattr( + pub(super) fn setattr( &self, fid: fcall::Fid, valid: fcall::SetattrMask, @@ -371,47 +378,37 @@ impl Client { ) -> Result<(), Error> { match self.fcall(Fcall::Tsetattr(fcall::Tsetattr { fid, valid, stat }))? { Fcall::Rsetattr(_) => Ok(()), - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), } } /// Read directory entries - pub(crate) fn readdir( + pub(super) fn readdir( &self, fid: fcall::Fid, offset: u64, - count: u32, ) -> Result>, Error> { - let count = count.min(self.msize - fcall::READDIRHDRSZ); + let count = self.msize - fcall::READDIRHDRSZ; match self.fcall(Fcall::Treaddir(fcall::Treaddir { fid, offset, count }))? { Fcall::Rreaddir(fcall::Rreaddir { data }) => { // Clone the directory entries to owned versions - Ok(data - .data - .into_iter() - .map(|e| fcall::DirEntry { - qid: e.qid, - offset: e.offset, - typ: e.typ, - name: e.name.clone_static(), - }) - .collect()) + Ok(data.data) } - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), } } /// Read all directory entries - pub(crate) fn readdir_all( + pub(super) fn readdir_all( &self, fid: fcall::Fid, ) -> Result>, Error> { let mut all_entries = Vec::new(); let mut offset = 0u64; loop { - let entries = self.readdir(fid, offset, self.msize - fcall::READDIRHDRSZ)?; + let entries = self.readdir(fid, offset)?; if entries.is_empty() { break; } @@ -422,7 +419,7 @@ impl Client { } /// Create a directory - pub(crate) fn mkdir( + pub(super) fn mkdir( &self, dfid: fcall::Fid, name: &str, @@ -436,26 +433,36 @@ impl Client { gid, }))? { Fcall::Rmkdir(fcall::Rmkdir { qid }) => Ok(qid), - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + } + } + + /// Remove the file represented by fid and clunk the fid, even if the remove fails + pub(super) fn remove(&self, fid: fcall::Fid) -> Result<(), Error> { + match self.fcall(Fcall::Tremove(fcall::Tremove { fid }))? { + Fcall::Rremove(_) => Ok(()), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), } } /// Remove (unlink) a file or directory - pub(crate) fn unlinkat(&self, dfid: fcall::Fid, name: &str, flags: u32) -> Result<(), Error> { + pub(super) fn unlinkat(&self, dfid: fcall::Fid, name: &str, flags: u32) -> Result<(), Error> { match self.fcall(Fcall::Tunlinkat(fcall::Tunlinkat { dfid, name: name.into(), flags, }))? { Fcall::Runlinkat(_) => Ok(()), - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), } } /// Rename a file - pub(crate) fn rename( + #[expect(dead_code)] + pub(super) fn rename( &self, fid: fcall::Fid, dfid: fcall::Fid, @@ -467,36 +474,37 @@ impl Client { name: name.into(), }))? { Fcall::Rrename(_) => Ok(()), - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), } } /// Fsync a file - pub(crate) fn fsync(&self, fid: fcall::Fid, datasync: bool) -> Result<(), Error> { + #[expect(dead_code)] + pub(super) fn fsync(&self, fid: fcall::Fid, datasync: bool) -> Result<(), Error> { match self.fcall(Fcall::Tfsync(fcall::Tfsync { fid, datasync: u32::from(datasync), }))? { Fcall::Rfsync(_) => Ok(()), - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), } } /// Clunk (close) a fid - pub(crate) fn clunk(&self, fid: fcall::Fid) -> Result<(), Error> { + pub(super) fn clunk(&self, fid: fcall::Fid) -> Result<(), Error> { let result = match self.fcall(Fcall::Tclunk(fcall::Tclunk { fid }))? { Fcall::Rclunk(_) => Ok(()), - Fcall::Rlerror(e) => Err(e.into_error()), - _ => Err(Error::InvalidInput), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), }; self.fids.free(fid); result } /// Clone a fid (walk with empty path) - pub(crate) fn clone_fid(&self, fid: fcall::Fid) -> Result { + pub(super) fn clone_fid(&self, fid: fcall::Fid) -> Result { let empty: [&str; 0] = []; let (_, new_fid) = self.walk(fid, &empty)?; Ok(new_fid) @@ -505,7 +513,7 @@ impl Client { /// Release a fid back to the pool without clunking /// /// Use this when the fid has already been invalidated (e.g., after remove) - pub(crate) fn free_fid(&self, fid: fcall::Fid) { + pub(super) fn free_fid(&self, fid: fcall::Fid) { self.fids.free(fid); } } diff --git a/litebox/src/fs/nine_p/cursor.rs b/litebox/src/fs/nine_p/cursor.rs deleted file mode 100644 index 1bd858ccb..000000000 --- a/litebox/src/fs/nine_p/cursor.rs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -//! Write cursor utilities for 9P protocol encoding - -use alloc::vec::Vec; -use core::cmp::min; - -/// A cursor for writing to a buffer with position tracking. -#[derive(Debug, Default, Eq, PartialEq)] -pub(crate) struct Cursor { - inner: T, - pos: u64, -} - -impl Cursor { - /// Create a new cursor wrapping the given inner value. - pub const fn new(inner: T) -> Cursor { - Cursor { pos: 0, inner } - } - - /// Consume the cursor and return the inner value. - pub fn into_inner(self) -> T { - self.inner - } -} - -/// A trait for writing bytes to a buffer. -pub(crate) trait Write { - /// Write a buffer to the output. - /// - /// Returns the number of bytes written. - fn write(&mut self, buf: &[u8]) -> Result; - - /// Write all bytes from a buffer to the output. - fn write_all(&mut self, buf: &[u8]) -> Result<(), super::Error> { - let mut written = 0; - while written < buf.len() { - let n = self.write(&buf[written..])?; - if n == 0 { - return Err(super::Error::Io); - } - written += n; - } - Ok(()) - } -} - -impl Write for &mut [u8] { - fn write(&mut self, buf: &[u8]) -> Result { - let amt = min(self.len(), buf.len()); - let (a, b) = core::mem::take(self).split_at_mut(amt); - a.copy_from_slice(&buf[..amt]); - *self = b; - Ok(amt) - } -} - -// Non-resizing write implementation -#[inline] -fn slice_write(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> Result { - let pos = min(*pos_mut, slice.len() as u64); - let amt = (&mut slice[(pos as usize)..]).write(buf)?; - *pos_mut += amt as u64; - Ok(amt) -} - -impl Write for Cursor<&mut [u8]> { - #[inline] - fn write(&mut self, buf: &[u8]) -> Result { - slice_write(&mut self.pos, self.inner, buf) - } -} - -fn reserve_and_pad( - pos_mut: &mut u64, - vec: &mut Vec, - buf_len: usize, -) -> Result { - let pos: usize = (*pos_mut) - .try_into() - .map_err(|_| super::Error::InvalidInput)?; - - // For safety reasons, we don't want these numbers to overflow - // otherwise our allocation won't be enough - let desired_cap = pos.saturating_add(buf_len); - if desired_cap > vec.capacity() { - // We want our vec's total capacity - // to have room for (pos+buf_len) bytes. Reserve allocates - // based on additional elements from the length, so we need to - // reserve the difference - vec.reserve(desired_cap - vec.len()); - } - // Pad if pos is above the current len. - if pos > vec.len() { - let diff = pos - vec.len(); - // Unfortunately, `resize()` would suffice but the optimiser does not - // realise the `reserve` it does can be eliminated. So we do it manually - // to eliminate that extra branch - let spare = vec.spare_capacity_mut(); - debug_assert!(spare.len() >= diff); - // Safety: we have allocated enough capacity for this. - // And we are only writing, not reading - unsafe { - spare - .get_unchecked_mut(..diff) - .fill(core::mem::MaybeUninit::new(0)); - vec.set_len(pos); - } - } - - Ok(pos) -} - -/// # Safety -/// The caller must ensure that `vec.capacity() >= pos + buf.len()` -unsafe fn vec_write_unchecked(pos: usize, vec: &mut Vec, buf: &[u8]) -> usize { - debug_assert!(vec.capacity() >= pos + buf.len()); - // SAFETY: The caller guarantees that vec.capacity() >= pos + buf.len(), - // so this pointer arithmetic and copy is safe. - unsafe { - vec.as_mut_ptr().add(pos).copy_from(buf.as_ptr(), buf.len()); - } - pos + buf.len() -} - -fn vec_write(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> Result { - let buf_len = buf.len(); - let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?; - - // Write the buf then progress the vec forward if necessary - // Safety: we have ensured that the capacity is available - // and that all bytes get written up to pos - unsafe { - pos = vec_write_unchecked(pos, vec, buf); - if pos > vec.len() { - vec.set_len(pos); - } - }; - - // Bump us forward - *pos_mut += buf_len as u64; - Ok(buf_len) -} - -impl Write for Cursor<&mut Vec> { - fn write(&mut self, buf: &[u8]) -> Result { - vec_write(&mut self.pos, self.inner, buf) - } -} - -impl Write for Vec { - fn write(&mut self, buf: &[u8]) -> Result { - self.extend_from_slice(buf); - Ok(buf.len()) - } -} diff --git a/litebox/src/fs/nine_p/fcall.rs b/litebox/src/fs/nine_p/fcall.rs index 3f8b4627a..67dfed6a0 100644 --- a/litebox/src/fs/nine_p/fcall.rs +++ b/litebox/src/fs/nine_p/fcall.rs @@ -6,35 +6,37 @@ //! This module implements the 9P2000.L protocol used for network filesystem access. //! See and -use super::cursor::Write; +use core::fmt::Display; + +use super::transport::{self, Write}; use alloc::{borrow::Cow, vec::Vec}; use bitflags::bitflags; /// File identifier type -pub(crate) type Fid = u32; +pub(super) type Fid = u32; /// Special tag which `Tversion`/`Rversion` must use as `tag` -pub(crate) const NOTAG: u16 = !0; +pub(super) const NOTAG: u16 = !0; /// Special value which `Tattach` with no auth must use as `afid` /// /// If the client does not wish to authenticate the connection, or knows that authentication is /// not required, the afid field in the attach message should be set to `NOFID` -pub(crate) const NOFID: u32 = !0; +pub(super) const NOFID: u32 = !0; /// Special uid which `Tauth`/`Tattach` use as `n_uname` to indicate no uid is specified -pub(crate) const NONUNAME: u32 = !0; +pub(super) const NONUNAME: u32 = !0; /// Room for `Twrite`/`Rread` header /// /// size[4] Tread/Twrite[2] tag[2] fid[4] offset[8] count[4] -pub(crate) const IOHDRSZ: u32 = 24; +pub(super) const IOHDRSZ: u32 = 24; /// Room for readdir header -pub(crate) const READDIRHDRSZ: u32 = 24; +pub(super) const READDIRHDRSZ: u32 = 24; /// Maximum elements in a single walk. -pub(crate) const MAXWELEM: usize = 13; +pub(super) const MAXWELEM: usize = 13; bitflags! { /// Flags passed to Tlopen. @@ -181,7 +183,7 @@ bitflags! { /// String type used in 9P protocol messages #[derive(Clone, Debug)] -pub(crate) enum FcallStr<'a> { +pub(super) enum FcallStr<'a> { Owned(Vec), Borrowed(&'a [u8]), } @@ -205,10 +207,6 @@ impl<'a> FcallStr<'a> { self.as_bytes().len() } - /// Check if the string is empty - pub fn is_empty(&self) -> bool { - self.as_bytes().is_empty() - } } impl<'a, T: ?Sized + AsRef<[u8]>> From<&'a T> for FcallStr<'a> { @@ -219,7 +217,7 @@ impl<'a, T: ?Sized + AsRef<[u8]>> From<&'a T> for FcallStr<'a> { /// Directory entry data container #[derive(Clone, Debug)] -pub(crate) struct DirEntryData<'a> { +pub(super) struct DirEntryData<'a> { pub data: Vec>, } @@ -258,7 +256,7 @@ impl Default for DirEntryData<'_> { /// 9P message types #[derive(Copy, Clone, Debug)] -pub(crate) enum FcallType { +pub(super) enum FcallType { // 9P2000.L Rlerror = 7, Tstatfs = 8, @@ -392,7 +390,7 @@ impl FcallType { /// Unique identifier for a file #[derive(Clone, Debug, Copy)] -pub(crate) struct Qid { +pub(super) struct Qid { pub typ: QidType, pub version: u32, pub path: u64, @@ -400,7 +398,7 @@ pub(crate) struct Qid { /// File system statistics #[derive(Clone, Debug, Copy)] -pub(crate) struct Statfs { +pub(super) struct Statfs { pub typ: u32, pub bsize: u32, pub blocks: u64, @@ -414,14 +412,14 @@ pub(crate) struct Statfs { /// Time structure #[derive(Clone, Debug, Copy, Default)] -pub(crate) struct Time { +pub(super) struct Time { pub sec: u64, pub nsec: u64, } /// File attributes #[derive(Clone, Debug, Copy)] -pub(crate) struct Stat { +pub(super) struct Stat { pub mode: u32, pub uid: u32, pub gid: u32, @@ -440,7 +438,7 @@ pub(crate) struct Stat { /// Set file attributes #[derive(Clone, Debug, Copy, Default)] -pub(crate) struct SetAttr { +pub(super) struct SetAttr { pub mode: u32, pub uid: u32, pub gid: u32, @@ -451,7 +449,7 @@ pub(crate) struct SetAttr { /// Directory entry #[derive(Clone, Debug)] -pub(crate) struct DirEntry<'a> { +pub(super) struct DirEntry<'a> { pub qid: Qid, pub offset: u64, pub typ: u8, @@ -467,18 +465,18 @@ impl DirEntry<'_> { /// File lock request #[derive(Clone, Debug)] -pub(crate) struct Flock<'a> { - pub typ: LockType, - pub flags: LockFlag, - pub start: u64, - pub length: u64, - pub proc_id: u32, - pub client_id: FcallStr<'a>, +pub(super) struct Flock<'a> { + typ: LockType, + flags: LockFlag, + start: u64, + length: u64, + proc_id: u32, + client_id: FcallStr<'a>, } /// Get lock request #[derive(Clone, Debug)] -pub(crate) struct Getlock<'a> { +pub(super) struct Getlock<'a> { pub typ: LockType, pub start: u64, pub length: u64, @@ -492,20 +490,19 @@ pub(crate) struct Getlock<'a> { /// Error response #[derive(Clone, Debug)] -pub(crate) struct Rlerror { - pub ecode: u32, +pub(super) struct Rlerror { + pub(super) ecode: u32, } -impl Rlerror { - /// Convert to an error code - pub fn into_error(self) -> super::Error { - super::Error::Remote(self.ecode) +impl Display for Rlerror { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Remote error: {}", self.ecode) } } /// Attach request #[derive(Clone, Debug)] -pub(crate) struct Tattach<'a> { +pub(super) struct Tattach<'a> { pub fid: u32, pub afid: u32, pub uname: FcallStr<'a>, @@ -527,39 +524,39 @@ impl Tattach<'_> { /// Attach response #[derive(Clone, Debug)] -pub(crate) struct Rattach { +pub(super) struct Rattach { pub qid: Qid, } /// Statfs request #[derive(Clone, Debug)] -pub(crate) struct Tstatfs { +pub(super) struct Tstatfs { pub fid: u32, } /// Statfs response #[derive(Clone, Debug)] -pub(crate) struct Rstatfs { +pub(super) struct Rstatfs { pub statfs: Statfs, } /// Open request #[derive(Clone, Debug)] -pub(crate) struct Tlopen { +pub(super) struct Tlopen { pub fid: u32, pub flags: LOpenFlags, } /// Open response #[derive(Clone, Debug)] -pub(crate) struct Rlopen { +pub(super) struct Rlopen { pub qid: Qid, pub iounit: u32, } /// Create request #[derive(Clone, Debug)] -pub(crate) struct Tlcreate<'a> { +pub(super) struct Tlcreate<'a> { pub fid: u32, pub name: FcallStr<'a>, pub flags: LOpenFlags, @@ -581,14 +578,14 @@ impl<'a> Tlcreate<'a> { /// Create response #[derive(Clone, Debug)] -pub(crate) struct Rlcreate { +pub(super) struct Rlcreate { pub qid: Qid, pub iounit: u32, } /// Symlink request #[derive(Clone, Debug)] -pub(crate) struct Tsymlink<'a> { +pub(super) struct Tsymlink<'a> { pub fid: u32, pub name: FcallStr<'a>, pub symtgt: FcallStr<'a>, @@ -608,13 +605,13 @@ impl<'a> Tsymlink<'a> { /// Symlink response #[derive(Clone, Debug)] -pub(crate) struct Rsymlink { +pub(super) struct Rsymlink { pub qid: Qid, } /// Mknod request #[derive(Clone, Debug)] -pub(crate) struct Tmknod<'a> { +pub(super) struct Tmknod<'a> { pub dfid: u32, pub name: FcallStr<'a>, pub mode: u32, @@ -638,13 +635,13 @@ impl<'a> Tmknod<'a> { /// Mknod response #[derive(Clone, Debug)] -pub(crate) struct Rmknod { +pub(super) struct Rmknod { pub qid: Qid, } /// Rename request #[derive(Clone, Debug)] -pub(crate) struct Trename<'a> { +pub(super) struct Trename<'a> { pub fid: u32, pub dfid: u32, pub name: FcallStr<'a>, @@ -662,17 +659,17 @@ impl<'a> Trename<'a> { /// Rename response #[derive(Clone, Debug)] -pub(crate) struct Rrename {} +pub(super) struct Rrename {} /// Readlink request #[derive(Clone, Debug)] -pub(crate) struct Treadlink { +pub(super) struct Treadlink { pub fid: u32, } /// Readlink response #[derive(Clone, Debug)] -pub(crate) struct Rreadlink<'a> { +pub(super) struct Rreadlink<'a> { pub target: FcallStr<'a>, } @@ -686,14 +683,14 @@ impl<'a> Rreadlink<'a> { /// Getattr request #[derive(Clone, Debug)] -pub(crate) struct Tgetattr { +pub(super) struct Tgetattr { pub fid: u32, pub req_mask: GetattrMask, } /// Getattr response #[derive(Clone, Debug)] -pub(crate) struct Rgetattr { +pub(super) struct Rgetattr { pub valid: GetattrMask, pub qid: Qid, pub stat: Stat, @@ -701,7 +698,7 @@ pub(crate) struct Rgetattr { /// Setattr request #[derive(Clone, Debug)] -pub(crate) struct Tsetattr { +pub(super) struct Tsetattr { pub fid: u32, pub valid: SetattrMask, pub stat: SetAttr, @@ -709,11 +706,11 @@ pub(crate) struct Tsetattr { /// Setattr response #[derive(Clone, Debug)] -pub(crate) struct Rsetattr {} +pub(super) struct Rsetattr {} /// Xattr walk request #[derive(Clone, Debug)] -pub(crate) struct Txattrwalk<'a> { +pub(super) struct Txattrwalk<'a> { pub fid: u32, pub new_fid: u32, pub name: FcallStr<'a>, @@ -731,13 +728,13 @@ impl<'a> Txattrwalk<'a> { /// Xattr walk response #[derive(Clone, Debug)] -pub(crate) struct Rxattrwalk { +pub(super) struct Rxattrwalk { pub size: u64, } /// Xattr create request #[derive(Clone, Debug)] -pub(crate) struct Txattrcreate<'a> { +pub(super) struct Txattrcreate<'a> { pub fid: u32, pub name: FcallStr<'a>, pub attr_size: u64, @@ -757,11 +754,11 @@ impl<'a> Txattrcreate<'a> { /// Xattr create response #[derive(Clone, Debug)] -pub(crate) struct Rxattrcreate {} +pub(super) struct Rxattrcreate {} /// Readdir request #[derive(Clone, Debug)] -pub(crate) struct Treaddir { +pub(super) struct Treaddir { pub fid: u32, pub offset: u64, pub count: u32, @@ -769,7 +766,7 @@ pub(crate) struct Treaddir { /// Readdir response #[derive(Clone, Debug)] -pub(crate) struct Rreaddir<'a> { +pub(super) struct Rreaddir<'a> { pub data: DirEntryData<'a>, } @@ -795,18 +792,18 @@ impl<'a> Rreaddir<'a> { /// Fsync request #[derive(Clone, Debug)] -pub(crate) struct Tfsync { +pub(super) struct Tfsync { pub fid: u32, pub datasync: u32, } /// Fsync response #[derive(Clone, Debug)] -pub(crate) struct Rfsync {} +pub(super) struct Rfsync {} /// Lock request #[derive(Clone, Debug)] -pub(crate) struct Tlock<'a> { +pub(super) struct Tlock<'a> { pub fid: u32, pub flock: Flock<'a>, } @@ -829,13 +826,13 @@ impl<'a> Tlock<'a> { /// Lock response #[derive(Clone, Debug)] -pub(crate) struct Rlock { +pub(super) struct Rlock { pub status: LockStatus, } /// Getlock request #[derive(Clone, Debug)] -pub(crate) struct Tgetlock<'a> { +pub(super) struct Tgetlock<'a> { pub fid: u32, pub flock: Getlock<'a>, } @@ -857,7 +854,7 @@ impl<'a> Tgetlock<'a> { /// Getlock response #[derive(Clone, Debug)] -pub(crate) struct Rgetlock<'a> { +pub(super) struct Rgetlock<'a> { pub flock: Getlock<'a>, } @@ -877,7 +874,7 @@ impl<'a> Rgetlock<'a> { /// Link request #[derive(Clone, Debug)] -pub(crate) struct Tlink<'a> { +pub(super) struct Tlink<'a> { pub dfid: u32, pub fid: u32, pub name: FcallStr<'a>, @@ -895,11 +892,11 @@ impl<'a> Tlink<'a> { /// Link response #[derive(Clone, Debug)] -pub(crate) struct Rlink {} +pub(super) struct Rlink {} /// Mkdir request #[derive(Clone, Debug)] -pub(crate) struct Tmkdir<'a> { +pub(super) struct Tmkdir<'a> { pub dfid: u32, pub name: FcallStr<'a>, pub mode: u32, @@ -919,13 +916,13 @@ impl<'a> Tmkdir<'a> { /// Mkdir response #[derive(Clone, Debug)] -pub(crate) struct Rmkdir { +pub(super) struct Rmkdir { pub qid: Qid, } /// Renameat request #[derive(Clone, Debug)] -pub(crate) struct Trenameat<'a> { +pub(super) struct Trenameat<'a> { pub olddfid: u32, pub oldname: FcallStr<'a>, pub newdfid: u32, @@ -945,11 +942,11 @@ impl<'a> Trenameat<'a> { /// Renameat response #[derive(Clone, Debug)] -pub(crate) struct Rrenameat {} +pub(super) struct Rrenameat {} /// Unlinkat request #[derive(Clone, Debug)] -pub(crate) struct Tunlinkat<'a> { +pub(super) struct Tunlinkat<'a> { pub dfid: u32, pub name: FcallStr<'a>, pub flags: u32, @@ -967,11 +964,11 @@ impl<'a> Tunlinkat<'a> { /// Unlinkat response #[derive(Clone, Debug)] -pub(crate) struct Runlinkat {} +pub(super) struct Runlinkat {} /// Auth request #[derive(Clone, Debug)] -pub(crate) struct Tauth<'a> { +pub(super) struct Tauth<'a> { pub afid: u32, pub uname: FcallStr<'a>, pub aname: FcallStr<'a>, @@ -991,13 +988,13 @@ impl<'a> Tauth<'a> { /// Auth response #[derive(Clone, Debug)] -pub(crate) struct Rauth { +pub(super) struct Rauth { pub aqid: Qid, } /// Version request #[derive(Clone, Debug)] -pub(crate) struct Tversion<'a> { +pub(super) struct Tversion<'a> { pub msize: u32, pub version: FcallStr<'a>, } @@ -1013,7 +1010,7 @@ impl<'a> Tversion<'a> { /// Version response #[derive(Clone, Debug)] -pub(crate) struct Rversion<'a> { +pub(super) struct Rversion<'a> { pub msize: u32, pub version: FcallStr<'a>, } @@ -1029,17 +1026,17 @@ impl<'a> Rversion<'a> { /// Flush request #[derive(Clone, Debug)] -pub(crate) struct Tflush { +pub(super) struct Tflush { pub oldtag: u16, } /// Flush response #[derive(Clone, Debug)] -pub(crate) struct Rflush {} +pub(super) struct Rflush {} /// Walk request #[derive(Clone, Debug)] -pub(crate) struct Twalk<'a> { +pub(super) struct Twalk<'a> { pub fid: u32, pub new_fid: u32, pub wnames: Vec>, @@ -1050,20 +1047,20 @@ impl<'a> Twalk<'a> { Twalk { fid: self.fid, new_fid: self.new_fid, - wnames: self.wnames.iter().map(|n| n.clone_static()).collect(), + wnames: self.wnames.iter().map(FcallStr::clone_static).collect(), } } } /// Walk response #[derive(Clone, Debug)] -pub(crate) struct Rwalk { +pub(super) struct Rwalk { pub wqids: Vec, } /// Read request #[derive(Clone, Debug)] -pub(crate) struct Tread { +pub(super) struct Tread { pub fid: u32, pub offset: u64, pub count: u32, @@ -1071,7 +1068,7 @@ pub(crate) struct Tread { /// Read response #[derive(Clone, Debug)] -pub(crate) struct Rread<'a> { +pub(super) struct Rread<'a> { pub data: Cow<'a, [u8]>, } @@ -1085,7 +1082,7 @@ impl<'a> Rread<'a> { /// Write request #[derive(Clone, Debug)] -pub(crate) struct Twrite<'a> { +pub(super) struct Twrite<'a> { pub fid: u32, pub offset: u64, pub data: Cow<'a, [u8]>, @@ -1103,29 +1100,29 @@ impl<'a> Twrite<'a> { /// Write response #[derive(Clone, Debug)] -pub(crate) struct Rwrite { +pub(super) struct Rwrite { pub count: u32, } /// Clunk request #[derive(Clone, Debug)] -pub(crate) struct Tclunk { +pub(super) struct Tclunk { pub fid: u32, } /// Clunk response #[derive(Clone, Debug)] -pub(crate) struct Rclunk {} +pub(super) struct Rclunk {} /// Remove request #[derive(Clone, Debug)] -pub(crate) struct Tremove { +pub(super) struct Tremove { pub fid: u32, } /// Remove response #[derive(Clone, Debug)] -pub(crate) struct Rremove {} +pub(super) struct Rremove {} // ============================================================================ // Fcall enum and conversions @@ -1133,7 +1130,7 @@ pub(crate) struct Rremove {} /// 9P protocol message #[derive(Clone, Debug)] -pub(crate) enum Fcall<'a> { +pub(super) enum Fcall<'a> { Rlerror(Rlerror), Tattach(Tattach<'a>), Rattach(Rattach), @@ -1443,14 +1440,14 @@ impl<'a> From> for Fcall<'a> { /// Tagged 9P message #[derive(Clone, Debug)] -pub(crate) struct TaggedFcall<'a> { - pub tag: u16, - pub fcall: Fcall<'a>, +pub(super) struct TaggedFcall<'a> { + pub(super) tag: u16, + pub(super) fcall: Fcall<'a>, } impl<'a> TaggedFcall<'a> { /// Encode the message to a buffer - pub fn encode_to_buf(&self, buf: &mut Vec) -> Result<(), super::Error> { + pub fn encode_to_buf(&self, buf: &mut Vec) -> Result<(), transport::WriteError> { buf.clear(); buf.resize(4, 0); // Reserve space for size @@ -1479,33 +1476,33 @@ impl<'a> TaggedFcall<'a> { // Encoding functions // ============================================================================ -fn encode_u8(w: &mut W, v: u8) -> Result<(), super::Error> { +fn encode_u8(w: &mut W, v: u8) -> Result<(), transport::WriteError> { w.write_all(&[v]) } -fn encode_u16(w: &mut W, v: u16) -> Result<(), super::Error> { +fn encode_u16(w: &mut W, v: u16) -> Result<(), transport::WriteError> { w.write_all(&v.to_le_bytes()) } -fn encode_u32(w: &mut W, v: u32) -> Result<(), super::Error> { +fn encode_u32(w: &mut W, v: u32) -> Result<(), transport::WriteError> { w.write_all(&v.to_le_bytes()) } -fn encode_u64(w: &mut W, v: u64) -> Result<(), super::Error> { +fn encode_u64(w: &mut W, v: u64) -> Result<(), transport::WriteError> { w.write_all(&v.to_le_bytes()) } -fn encode_str(w: &mut W, v: &FcallStr<'_>) -> Result<(), super::Error> { +fn encode_str(w: &mut W, v: &FcallStr<'_>) -> Result<(), transport::WriteError> { encode_u16(w, v.len() as u16)?; w.write_all(v.as_bytes()) } -fn encode_data_buf(w: &mut W, v: &[u8]) -> Result<(), super::Error> { +fn encode_data_buf(w: &mut W, v: &[u8]) -> Result<(), transport::WriteError> { encode_u32(w, v.len() as u32)?; w.write_all(v) } -fn encode_vec_str(w: &mut W, v: &[FcallStr<'_>]) -> Result<(), super::Error> { +fn encode_vec_str(w: &mut W, v: &[FcallStr<'_>]) -> Result<(), transport::WriteError> { encode_u16(w, v.len() as u16)?; for s in v { encode_str(w, s)?; @@ -1513,7 +1510,7 @@ fn encode_vec_str(w: &mut W, v: &[FcallStr<'_>]) -> Result<(), super:: Ok(()) } -fn encode_vec_qid(w: &mut W, v: &[Qid]) -> Result<(), super::Error> { +fn encode_vec_qid(w: &mut W, v: &[Qid]) -> Result<(), transport::WriteError> { encode_u16(w, v.len() as u16)?; for q in v { encode_qid(w, q)?; @@ -1521,38 +1518,38 @@ fn encode_vec_qid(w: &mut W, v: &[Qid]) -> Result<(), super::Error> { Ok(()) } -fn encode_qidtype(w: &mut W, v: &QidType) -> Result<(), super::Error> { +fn encode_qidtype(w: &mut W, v: &QidType) -> Result<(), transport::WriteError> { encode_u8(w, v.bits()) } -fn encode_locktype(w: &mut W, v: &LockType) -> Result<(), super::Error> { +fn encode_locktype(w: &mut W, v: &LockType) -> Result<(), transport::WriteError> { encode_u8(w, v.bits()) } -fn encode_lockstatus(w: &mut W, v: &LockStatus) -> Result<(), super::Error> { +fn encode_lockstatus(w: &mut W, v: &LockStatus) -> Result<(), transport::WriteError> { encode_u8(w, v.bits()) } -fn encode_lockflag(w: &mut W, v: &LockFlag) -> Result<(), super::Error> { +fn encode_lockflag(w: &mut W, v: &LockFlag) -> Result<(), transport::WriteError> { encode_u32(w, v.bits()) } -fn encode_getattrmask(w: &mut W, v: &GetattrMask) -> Result<(), super::Error> { +fn encode_getattrmask(w: &mut W, v: &GetattrMask) -> Result<(), transport::WriteError> { encode_u64(w, v.bits()) } -fn encode_setattrmask(w: &mut W, v: &SetattrMask) -> Result<(), super::Error> { +fn encode_setattrmask(w: &mut W, v: &SetattrMask) -> Result<(), transport::WriteError> { encode_u32(w, v.bits()) } -fn encode_qid(w: &mut W, v: &Qid) -> Result<(), super::Error> { +fn encode_qid(w: &mut W, v: &Qid) -> Result<(), transport::WriteError> { encode_qidtype(w, &v.typ)?; encode_u32(w, v.version)?; encode_u64(w, v.path)?; Ok(()) } -fn encode_statfs(w: &mut W, v: &Statfs) -> Result<(), super::Error> { +fn encode_statfs(w: &mut W, v: &Statfs) -> Result<(), transport::WriteError> { encode_u32(w, v.typ)?; encode_u32(w, v.bsize)?; encode_u64(w, v.blocks)?; @@ -1565,13 +1562,13 @@ fn encode_statfs(w: &mut W, v: &Statfs) -> Result<(), super::Error> { Ok(()) } -fn encode_time(w: &mut W, v: &Time) -> Result<(), super::Error> { +fn encode_time(w: &mut W, v: &Time) -> Result<(), transport::WriteError> { encode_u64(w, v.sec)?; encode_u64(w, v.nsec)?; Ok(()) } -fn encode_stat(w: &mut W, v: &Stat) -> Result<(), super::Error> { +fn encode_stat(w: &mut W, v: &Stat) -> Result<(), transport::WriteError> { encode_u32(w, v.mode)?; encode_u32(w, v.uid)?; encode_u32(w, v.gid)?; @@ -1589,7 +1586,7 @@ fn encode_stat(w: &mut W, v: &Stat) -> Result<(), super::Error> { Ok(()) } -fn encode_setattr(w: &mut W, v: &SetAttr) -> Result<(), super::Error> { +fn encode_setattr(w: &mut W, v: &SetAttr) -> Result<(), transport::WriteError> { encode_u32(w, v.mode)?; encode_u32(w, v.uid)?; encode_u32(w, v.gid)?; @@ -1599,7 +1596,10 @@ fn encode_setattr(w: &mut W, v: &SetAttr) -> Result<(), super::Error> Ok(()) } -fn encode_direntrydata(w: &mut W, v: &DirEntryData<'_>) -> Result<(), super::Error> { +fn encode_direntrydata( + w: &mut W, + v: &DirEntryData<'_>, +) -> Result<(), transport::WriteError> { encode_u32(w, v.size() as u32)?; for e in &v.data { encode_direntry(w, e)?; @@ -1607,7 +1607,7 @@ fn encode_direntrydata(w: &mut W, v: &DirEntryData<'_>) -> Result<(), Ok(()) } -fn encode_direntry(w: &mut W, v: &DirEntry<'_>) -> Result<(), super::Error> { +fn encode_direntry(w: &mut W, v: &DirEntry<'_>) -> Result<(), transport::WriteError> { encode_qid(w, &v.qid)?; encode_u64(w, v.offset)?; encode_u8(w, v.typ)?; @@ -1615,7 +1615,7 @@ fn encode_direntry(w: &mut W, v: &DirEntry<'_>) -> Result<(), super::E Ok(()) } -fn encode_flock(w: &mut W, v: &Flock<'_>) -> Result<(), super::Error> { +fn encode_flock(w: &mut W, v: &Flock<'_>) -> Result<(), transport::WriteError> { encode_locktype(w, &v.typ)?; encode_lockflag(w, &v.flags)?; encode_u64(w, v.start)?; @@ -1625,7 +1625,7 @@ fn encode_flock(w: &mut W, v: &Flock<'_>) -> Result<(), super::Error> Ok(()) } -fn encode_getlock(w: &mut W, v: &Getlock<'_>) -> Result<(), super::Error> { +fn encode_getlock(w: &mut W, v: &Getlock<'_>) -> Result<(), transport::WriteError> { encode_locktype(w, &v.typ)?; encode_u64(w, v.start)?; encode_u64(w, v.length)?; @@ -1634,7 +1634,11 @@ fn encode_getlock(w: &mut W, v: &Getlock<'_>) -> Result<(), super::Err Ok(()) } -fn encode_fcall(w: &mut W, tag: &u16, fcall: &Fcall<'_>) -> Result<(), super::Error> { +fn encode_fcall( + w: &mut W, + tag: &u16, + fcall: &Fcall<'_>, +) -> Result<(), transport::WriteError> { match fcall { Fcall::Rlerror(v) => { encode_u8(w, FcallType::Rlerror as u8)?; diff --git a/litebox/src/fs/nine_p/mod.rs b/litebox/src/fs/nine_p/mod.rs index 9d2a1fced..8c71191ab 100644 --- a/litebox/src/fs/nine_p/mod.rs +++ b/litebox/src/fs/nine_p/mod.rs @@ -12,30 +12,32 @@ //! //! The 9P implementation is split into several submodules: //! - `fcall` - Protocol message definitions and encoding/decoding -//! - `cursor` - Write cursor utilities for message encoding //! - `transport` - Transport layer traits and message I/O //! - `client` - High-level 9P client for protocol operations -// Protocol implementation requires various casts that are known to be safe -#![allow(clippy::cast_possible_truncation)] -#![allow(clippy::cast_sign_loss)] -#![allow(clippy::cast_lossless)] - use alloc::string::String; use alloc::vec::Vec; use core::num::NonZeroUsize; -use core::sync::atomic::{AtomicU64, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use thiserror::Error; +use crate::fs::OFlags; +use crate::fs::errors::{ + ChmodError, ChownError, FileStatusError, MkdirError, OpenError, PathError, ReadDirError, + ReadError, RmdirError, SeekError, TruncateError, UnlinkError, WriteError, +}; +use crate::fs::nine_p::fcall::Rlerror; +use crate::path::Arg; use crate::{LiteBox, sync}; mod client; -mod cursor; mod fcall; pub mod transport; +const DEVICE_ID: usize = u32::from_le_bytes(*b"NINE") as usize; + /// Error type for 9P operations #[derive(Debug, Error)] pub enum Error { @@ -47,9 +49,11 @@ pub enum Error { #[error("Invalid input")] InvalidInput, - /// Remote error from the 9P server - #[error("Remote error: {0}")] - Remote(u32), + #[error("Invalid response from server")] + InvalidResponse, + + #[error("Invalid pathname")] + InvalidPathname, /// Path not found #[error("Path not found")] @@ -84,9 +88,237 @@ pub enum Error { NotSupported, } +impl From for OpenError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => OpenError::PathError(PathError::NoSuchFileOrDirectory), + Error::AlreadyExists => OpenError::AlreadyExists, + Error::PermissionDenied => OpenError::AccessNotAllowed, + Error::NotADirectory => OpenError::PathError(PathError::ComponentNotADirectory), + Error::InvalidPathname => OpenError::PathError(PathError::InvalidPathname), + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::IsADirectory + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to OpenError"), + } + } +} + +impl From for ReadError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => ReadError::NotAFile, + Error::PermissionDenied => ReadError::NotForReading, + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::InvalidPathname + | Error::AlreadyExists + | Error::NotADirectory + | Error::IsADirectory + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to ReadError"), + } + } +} + +impl From for WriteError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => WriteError::NotAFile, + Error::PermissionDenied => WriteError::NotForWriting, + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::InvalidPathname + | Error::AlreadyExists + | Error::NotADirectory + | Error::IsADirectory + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to WriteError"), + } + } +} + +impl From for MkdirError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => MkdirError::PathError(PathError::NoSuchFileOrDirectory), + Error::AlreadyExists => MkdirError::AlreadyExists, + Error::PermissionDenied => MkdirError::NoWritePerms, + Error::NotADirectory => MkdirError::PathError(PathError::ComponentNotADirectory), + Error::InvalidPathname => MkdirError::PathError(PathError::InvalidPathname), + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::IsADirectory + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to MkdirError"), + } + } +} + +impl From for ReadDirError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => ReadDirError::NotADirectory, + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::InvalidPathname + | Error::AlreadyExists + | Error::PermissionDenied + | Error::NotADirectory + | Error::IsADirectory + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to ReadDirError"), + } + } +} + +impl From for UnlinkError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => UnlinkError::PathError(PathError::NoSuchFileOrDirectory), + Error::IsADirectory => UnlinkError::IsADirectory, + Error::PermissionDenied => UnlinkError::NoWritePerms, + Error::NotADirectory => UnlinkError::PathError(PathError::ComponentNotADirectory), + Error::InvalidPathname => UnlinkError::PathError(PathError::InvalidPathname), + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::AlreadyExists + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to UnlinkError"), + } + } +} + +impl From for RmdirError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => RmdirError::PathError(PathError::NoSuchFileOrDirectory), + Error::NotADirectory => RmdirError::NotADirectory, + Error::PermissionDenied => RmdirError::NoWritePerms, + Error::InvalidPathname => RmdirError::PathError(PathError::InvalidPathname), + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::AlreadyExists + | Error::IsADirectory + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to RmdirError"), + } + } +} + +impl From for FileStatusError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => FileStatusError::PathError(PathError::NoSuchFileOrDirectory), + Error::InvalidPathname => FileStatusError::PathError(PathError::InvalidPathname), + Error::NotADirectory => FileStatusError::PathError(PathError::ComponentNotADirectory), + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::AlreadyExists + | Error::PermissionDenied + | Error::IsADirectory + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to FileStatusError"), + } + } +} + +impl From for SeekError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => SeekError::ClosedFd, + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::InvalidPathname + | Error::AlreadyExists + | Error::PermissionDenied + | Error::NotADirectory + | Error::IsADirectory + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to SeekError"), + } + } +} + +impl From for TruncateError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => TruncateError::ClosedFd, + Error::IsADirectory => TruncateError::IsDirectory, + Error::PermissionDenied => TruncateError::NotForWriting, + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::InvalidPathname + | Error::AlreadyExists + | Error::NotADirectory + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to TruncateError"), + } + } +} + +impl From for ChmodError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => ChmodError::PathError(PathError::NoSuchFileOrDirectory), + Error::InvalidPathname => ChmodError::PathError(PathError::InvalidPathname), + Error::NotADirectory => ChmodError::PathError(PathError::ComponentNotADirectory), + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::AlreadyExists + | Error::PermissionDenied + | Error::IsADirectory + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to ChmodError"), + } + } +} + +impl From for ChownError { + fn from(e: Error) -> Self { + match e { + Error::NotFound => ChownError::PathError(PathError::NoSuchFileOrDirectory), + Error::InvalidPathname => ChownError::PathError(PathError::InvalidPathname), + Error::NotADirectory => ChownError::PathError(PathError::ComponentNotADirectory), + Error::Io + | Error::InvalidInput + | Error::InvalidResponse + | Error::AlreadyExists + | Error::PermissionDenied + | Error::IsADirectory + | Error::NameTooLong + | Error::Connection + | Error::NotSupported => unimplemented!("convert {e:?} to ChownError"), + } + } +} + /// Convert remote error code to our Error type -impl From for Error { - fn from(ecode: u32) -> Self { +impl From for Error { + fn from(err: Rlerror) -> Self { // Common POSIX error codes const ENOENT: u32 = 2; const EACCES: u32 = 13; @@ -97,7 +329,7 @@ impl From for Error { const ENOSYS: u32 = 38; const EOPNOTSUPP: u32 = 95; - match ecode { + match err.ecode { ENOENT => Error::NotFound, EACCES => Error::PermissionDenied, EEXIST => Error::AlreadyExists, @@ -105,7 +337,9 @@ impl From for Error { EISDIR => Error::IsADirectory, ENAMETOOLONG => Error::NameTooLong, ENOSYS | EOPNOTSUPP => Error::NotSupported, - _ => Error::Remote(ecode), + // Unrecognized remote error codes are mapped to a generic I/O error. + // This loses the specific error code but avoids panicking at runtime. + _ => Error::Io, } } } @@ -129,8 +363,12 @@ pub struct FileSystem< litebox: LiteBox, /// 9P client for protocol operations client: client::Client, - /// Root fid (attached to the root of the remote filesystem) - root_fid: fcall::Fid, + /// Root (attached to the root of the remote filesystem) + root: (fcall::Qid, fcall::Fid, String), + // cwd invariant: always ends with a `/` + current_working_dir: String, + /// Whether `unlinkat` is supported by the server + unlinkat_supported: AtomicBool, } impl @@ -147,7 +385,8 @@ impl, transport: T, msize: u32, - aname: &str, + username: &str, + path: &str, ) -> Result { let client = client::Client::new(transport, msize)?; - let (_, root_fid) = client.attach("", aname)?; + let (qid, fid) = client.attach(username, path)?; Ok(Self { litebox: litebox.clone(), client, - root_fid, + root: (qid, fid, String::from(path)), + current_working_dir: String::from("/"), + unlinkat_supported: AtomicBool::new(true), }) } - /// Parse a path string and split it into path components - fn parse_path(path: &str) -> Vec<&str> { - path.split('/') - .filter(|s| !s.is_empty() && *s != ".") - .collect() + /// Gives the absolute path for `path`, resolving any `.` or `..`s, and making sure to account + /// for any relative paths from current working directory. + /// + /// Note: does NOT account for symlinks. + fn absolute_path(&self, path: impl crate::path::Arg) -> Result { + assert!(self.current_working_dir.ends_with('/')); + let path = path.as_rust_str()?; + if path.starts_with('/') { + // Absolute path + Ok(path.normalized()?) + } else { + // Relative path + Ok((self.current_working_dir.clone() + path.as_rust_str()?).normalized()?) + } } /// Walk to a path and return the fid fn walk_to(&self, path: &str) -> Result { - let components = Self::parse_path(path); + let components: Vec<&str> = path + .normalized_components() + .map_err(|_| Error::InvalidPathname)? + .collect(); if components.is_empty() { // Clone the root fid - self.client.clone_fid(self.root_fid) + self.client.clone_fid(self.root.1) } else { - let (_, fid) = self.client.walk(self.root_fid, &components)?; + let (_, fid) = self.client.walk(self.root.1, &components)?; Ok(fid) } } /// Walk to the parent of a path and return the parent fid and the name fn walk_to_parent<'a>(&self, path: &'a str) -> Result<(fcall::Fid, &'a str), Error> { - let components = Self::parse_path(path); + let components: Vec<&str> = path + .normalized_components() + .map_err(|_| Error::InvalidPathname)? + .collect(); if components.is_empty() { - return Err(Error::InvalidInput); + return Err(Error::InvalidPathname); } let name = components.last().unwrap(); let parent_components = &components[..components.len() - 1]; if parent_components.is_empty() { - let parent_fid = self.client.clone_fid(self.root_fid)?; + let parent_fid = self.client.clone_fid(self.root.1)?; Ok((parent_fid, name)) } else { - let (_, parent_fid) = self.client.walk(self.root_fid, parent_components)?; + let (_, parent_fid) = self.client.walk(self.root.1, parent_components)?; Ok((parent_fid, name)) } } @@ -268,22 +525,91 @@ impl super::FileStatus { let file_type = Self::qid_type_to_file_type(attr.qid.typ); - super::FileStatus { - file_type, - mode: super::Mode::from_bits_truncate(attr.stat.mode), - size: attr.stat.size as usize, - owner: super::UserInfo { - user: attr.stat.uid as u16, - group: attr.stat.gid as u16, - }, - node_info: super::NodeInfo { - dev: attr.stat.rdev as usize, - ino: attr.qid.path as usize, - rdev: NonZeroUsize::new(attr.stat.rdev as usize), - }, - blksize: attr.stat.blksize as usize, + if attr.valid.contains(fcall::GetattrMask::BASIC) { + super::FileStatus { + file_type, + mode: super::Mode::from_bits_truncate(attr.stat.mode), + size: attr.stat.size as usize, + owner: super::UserInfo { + user: attr.stat.uid as u16, + group: attr.stat.gid as u16, + }, + node_info: super::NodeInfo { + dev: DEVICE_ID, + ino: attr.qid.path as usize, + rdev: NonZeroUsize::new(attr.stat.rdev as usize), + }, + blksize: attr.stat.blksize as usize, + } + } else { + super::FileStatus { + file_type, + mode: if attr.valid.contains(fcall::GetattrMask::MODE) { + super::Mode::from_bits_truncate(attr.stat.mode) + } else { + super::Mode::empty() + }, + size: if attr.valid.contains(fcall::GetattrMask::SIZE) { + attr.stat.size as usize + } else { + 0 + }, + owner: super::UserInfo { + user: if attr.valid.contains(fcall::GetattrMask::UID) { + attr.stat.uid as u16 + } else { + 0 + }, + group: if attr.valid.contains(fcall::GetattrMask::GID) { + attr.stat.gid as u16 + } else { + 0 + }, + }, + node_info: super::NodeInfo { + dev: DEVICE_ID, + ino: attr.qid.path as usize, + rdev: if attr.valid.contains(fcall::GetattrMask::RDEV) { + NonZeroUsize::new(attr.stat.rdev as usize) + } else { + None + }, + }, + blksize: if attr.valid.contains(fcall::GetattrMask::BLOCKS) { + attr.stat.blksize as usize + } else { + 0 + }, + } } } + + fn remove_file_or_dir(&self, path: impl crate::path::Arg, is_file: bool) -> Result<(), Error> { + const AT_REMOVEDIR: u32 = 0x200; + + let path = self + .absolute_path(path) + .map_err(|_| Error::InvalidPathname)?; + if self.unlinkat_supported.load(Ordering::SeqCst) { + let (parent_fid, name) = self.walk_to_parent(&path)?; + + let result = + self.client + .unlinkat(parent_fid, name, if is_file { 0 } else { AT_REMOVEDIR }); + let _ = self.client.clunk(parent_fid); + if let Err(Error::NotSupported) = &result { + self.unlinkat_supported.store(false, Ordering::SeqCst); + // fall back to to `remove` + } else { + return result; + } + } + + let fid = self.walk_to(&path)?; + let result = self.client.remove(fid); + self.client.free_fid(fid); + result + } } impl @@ -294,75 +620,46 @@ impl super::FileSystem for FileSystem { + #[expect(clippy::similar_names)] fn open( &self, path: impl crate::path::Arg, flags: super::OFlags, mode: super::Mode, ) -> Result, super::errors::OpenError> { - let path_str = path - .as_rust_str() - .map_err(|_| super::errors::PathError::InvalidPathname)?; + let currently_supported_oflags: OFlags = OFlags::RDONLY + | OFlags::WRONLY + | OFlags::RDWR + | OFlags::CREAT + | OFlags::NOCTTY + | OFlags::EXCL + | OFlags::DIRECTORY + | OFlags::LARGEFILE; + if flags.intersects(currently_supported_oflags.complement()) { + unimplemented!("{flags:?}") + } + let path = self.absolute_path(path)?; + let components: Vec<&str> = path.normalized_components().map_err(|_| OpenError::PathError(PathError::InvalidPathname))?.collect(); let lflags = Self::oflags_to_lopen(flags); let needs_create = flags.contains(super::OFlags::CREAT); - // Determine read/write permissions from flags - let read_allowed = !flags.contains(super::OFlags::WRONLY); - let write_allowed = - flags.contains(super::OFlags::WRONLY) || flags.contains(super::OFlags::RDWR); - - let (fid, qid) = if needs_create { - // Try to walk to the parent and create the file - let (parent_fid, name) = self - .walk_to_parent(path_str) - .map_err(|_| super::errors::PathError::NoSuchFileOrDirectory)?; - - match self.client.create(parent_fid, name, lflags, mode.bits(), 0) { - Ok(r) => { - // create() transforms the parent_fid into the file fid - (parent_fid, r.qid) - } - Err(Error::AlreadyExists) if !flags.contains(super::OFlags::EXCL) => { - // File exists and EXCL is not set, try to open it - let _ = self.client.clunk(parent_fid); - let fid = self - .walk_to(path_str) - .map_err(|_| super::errors::PathError::NoSuchFileOrDirectory)?; - let r = self.client.open(fid, lflags).map_err(|_| { - let _ = self.client.clunk(fid); - super::errors::OpenError::AccessNotAllowed - })?; - (fid, r.qid) - } - Err(Error::AlreadyExists) => { - let _ = self.client.clunk(parent_fid); - return Err(super::errors::OpenError::AlreadyExists); - } - Err(_) => { - let _ = self.client.clunk(parent_fid); - return Err(super::errors::OpenError::AccessNotAllowed); - } - } + let (new_qid, new_fid) = if needs_create { + let (_, dfid) = self + .client + .walk(self.root.1, &components[..components.len() - 1])?; + self.client + .create(dfid, components.last().unwrap(), lflags, mode.bits(), 0)? } else { - // Just walk and open - let fid = self - .walk_to(path_str) - .map_err(|_| super::errors::PathError::NoSuchFileOrDirectory)?; - let r = self.client.open(fid, lflags).map_err(|_| { - let _ = self.client.clunk(fid); - super::errors::OpenError::AccessNotAllowed - })?; - (fid, r.qid) + let (_, new_fid) = self.client.walk(self.root.1, &components)?; + let qid = self.client.open(new_fid, lflags)?; + (qid, new_fid) }; - // Wrap in a file descriptor let descriptor = Descriptor { - fid, + fid: new_fid, offset: AtomicU64::new(0), - read_allowed, - write_allowed, - qid, + qid: new_qid, }; let fd = self.litebox.descriptor_table_mut().insert(descriptor); @@ -370,11 +667,10 @@ impl) -> Result<(), super::errors::CloseError> { - let descriptor_table = self.litebox.descriptor_table(); - if let Some(entry) = descriptor_table.get_entry(fd) { + let entry = self.litebox.descriptor_table_mut().remove(fd); + if let Some(entry) = entry { let _ = self.client.clunk(entry.entry.fid); } - self.litebox.descriptor_table_mut().remove(fd); Ok(()) } @@ -390,27 +686,15 @@ impl o as u64, None => desc.offset.load(Ordering::SeqCst), }; - // Read data - let count = buf - .len() - .min((self.client.msize() - fcall::IOHDRSZ) as usize); - let data = self - .client - .read(desc.fid, read_offset, count as u32) - .map_err(|_| super::errors::ReadError::ClosedFd)?; - - let bytes_read = data.len(); - buf[..bytes_read].copy_from_slice(&data); + // TODO: read might be blocking while holding up the descriptor table lock. + // We should consider releasing the lock before doing the read. + let bytes_read = self.client.read(desc.fid, read_offset, buf)?; // Update offset if not using explicit offset if offset.is_none() { @@ -432,21 +716,15 @@ impl o as u64, None => desc.offset.load(Ordering::SeqCst), }; - // Write data - let bytes_written = self - .client - .write(desc.fid, write_offset, buf) - .map_err(|_| super::errors::WriteError::ClosedFd)?; + // TODO: write might be blocking while holding up the descriptor table lock. + // We should consider releasing the lock before doing the write. + let bytes_written = self.client.write(desc.fid, write_offset, buf)?; // Update offset if not using explicit offset if offset.is_none() { @@ -462,52 +740,25 @@ impl, offset: isize, whence: super::SeekWhence, - ) -> Result { + ) -> Result { let descriptor_table = self.litebox.descriptor_table(); - let entry = descriptor_table - .get_entry(fd) - .ok_or(super::errors::SeekError::ClosedFd)?; + let entry = descriptor_table.get_entry(fd).ok_or(SeekError::ClosedFd)?; let desc = &entry.entry; let current_offset = desc.offset.load(Ordering::SeqCst); - let new_offset = match whence { - super::SeekWhence::RelativeToBeginning => { - if offset < 0 { - return Err(super::errors::SeekError::InvalidOffset); - } - offset as u64 - } - super::SeekWhence::RelativeToCurrentOffset => { - if offset < 0 { - let neg_offset = (-offset) as u64; - if neg_offset > current_offset { - return Err(super::errors::SeekError::InvalidOffset); - } - current_offset - neg_offset - } else { - current_offset + offset as u64 - } - } + let base = match whence { + super::SeekWhence::RelativeToBeginning => 0, + super::SeekWhence::RelativeToCurrentOffset => current_offset, super::SeekWhence::RelativeToEnd => { // Need to get file size - let attr = self - .client - .getattr(desc.fid, fcall::GetattrMask::SIZE) - .map_err(|_| super::errors::SeekError::ClosedFd)?; - let size = attr.stat.size; - - if offset < 0 { - let neg_offset = (-offset) as u64; - if neg_offset > size { - return Err(super::errors::SeekError::InvalidOffset); - } - size - neg_offset - } else { - size + offset as u64 - } + let attr = self.client.getattr(desc.fid, fcall::GetattrMask::SIZE)?; + attr.stat.size } }; + let new_offset = base + .checked_add_signed(offset as i64) + .ok_or(SeekError::InvalidOffset)?; desc.offset.store(new_offset, Ordering::SeqCst); Ok(new_offset as usize) @@ -525,10 +776,6 @@ impl Result<(), super::errors::ChmodError> { - let path_str = path - .as_rust_str() - .map_err(|_| super::errors::PathError::InvalidPathname)?; - - let fid = self.walk_to(path_str).map_err(|e| match e { - Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), - _ => super::errors::ChmodError::ReadOnlyFileSystem, - })?; + let path = self.absolute_path(path)?; + let fid = self.walk_to(&path)?; let stat = fcall::SetAttr { mode: mode.bits(), @@ -579,7 +819,7 @@ impl, group: Option, ) -> Result<(), super::errors::ChownError> { - let path_str = path - .as_rust_str() - .map_err(|_| super::errors::PathError::InvalidPathname)?; - - let fid = self.walk_to(path_str).map_err(|e| match e { - Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), - _ => super::errors::ChownError::ReadOnlyFileSystem, - })?; + let path = self.absolute_path(path)?; + let fid = self.walk_to(&path)?; let mut valid = fcall::SetattrMask::empty(); let uid = match user { @@ -624,78 +858,28 @@ impl Result<(), super::errors::UnlinkError> { - let path_str = path - .as_rust_str() - .map_err(|_| super::errors::PathError::InvalidPathname)?; - - let (parent_fid, name) = self.walk_to_parent(path_str).map_err(|e| match e { - Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), - _ => super::errors::UnlinkError::NoWritePerms, - })?; - - let result = self.client.unlinkat(parent_fid, name, 0); - let _ = self.client.clunk(parent_fid); - - result.map_err(|e| match e { - Error::IsADirectory => super::errors::UnlinkError::IsADirectory, - Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), - Error::PermissionDenied => super::errors::UnlinkError::NoWritePerms, - _ => super::errors::UnlinkError::ReadOnlyFileSystem, - }) + self.remove_file_or_dir(path, true) + .map_err(UnlinkError::from) } - fn mkdir( - &self, - path: impl crate::path::Arg, - mode: super::Mode, - ) -> Result<(), super::errors::MkdirError> { - let path_str = path - .as_rust_str() - .map_err(|_| super::errors::PathError::InvalidPathname)?; + fn mkdir(&self, path: impl crate::path::Arg, mode: super::Mode) -> Result<(), MkdirError> { + let path = self.absolute_path(path)?; - let (parent_fid, name) = self.walk_to_parent(path_str).map_err(|e| match e { - Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), - _ => super::errors::MkdirError::NoWritePerms, - })?; + let (parent_fid, name) = self.walk_to_parent(&path)?; let result = self.client.mkdir(parent_fid, name, mode.bits(), 0); let _ = self.client.clunk(parent_fid); - result.map_err(|e| match e { - Error::AlreadyExists => super::errors::MkdirError::AlreadyExists, - Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), - Error::PermissionDenied => super::errors::MkdirError::NoWritePerms, - _ => super::errors::MkdirError::ReadOnlyFileSystem, - })?; - - Ok(()) + result.map(|_| ()).map_err(MkdirError::from) } - fn rmdir(&self, path: impl crate::path::Arg) -> Result<(), super::errors::RmdirError> { - const AT_REMOVEDIR: u32 = 0x200; - - let path_str = path - .as_rust_str() - .map_err(|_| super::errors::PathError::InvalidPathname)?; - - let (parent_fid, name) = self.walk_to_parent(path_str).map_err(|e| match e { - Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), - _ => super::errors::RmdirError::NoWritePerms, - })?; - - let result = self.client.unlinkat(parent_fid, name, AT_REMOVEDIR); - let _ = self.client.clunk(parent_fid); - - result.map_err(|e| match e { - Error::NotADirectory => super::errors::RmdirError::NotADirectory, - Error::NotFound => super::errors::PathError::NoSuchFileOrDirectory.into(), - Error::PermissionDenied => super::errors::RmdirError::NoWritePerms, - _ => super::errors::RmdirError::ReadOnlyFileSystem, - }) + fn rmdir(&self, path: impl crate::path::Arg) -> Result<(), RmdirError> { + self.remove_file_or_dir(path, false) + .map_err(RmdirError::from) } fn read_dir( @@ -712,17 +896,12 @@ impl = entries .into_iter() - .filter(|e| { - let name = e.name.as_bytes(); - name != b"." && name != b".." - }) .map(|e| { let file_type = if e.typ == fcall::QidType::DIR.bits() { super::FileType::Directory @@ -734,7 +913,7 @@ impl Result { - let path_str = path - .as_rust_str() - .map_err(|_| super::errors::PathError::InvalidPathname)?; - - let fid = self - .walk_to(path_str) - .map_err(|_| super::errors::PathError::NoSuchFileOrDirectory)?; + ) -> Result { + let path = self.absolute_path(path)?; + let fid = self.walk_to(&path)?; let result = self.client.getattr(fid, fcall::GetattrMask::ALL); let _ = self.client.clunk(fid); - let attr = result.map_err(|_| super::errors::PathError::NoSuchFileOrDirectory)?; - Ok(Self::rgetattr_to_file_status(&attr)) + result + .map(|attr| Self::rgetattr_to_file_status(&attr)) + .map_err(FileStatusError::from) } fn fd_file_status( @@ -774,10 +949,7 @@ impl Result; + fn read(&mut self, buf: &mut [u8]) -> Result; /// Read exactly `buf.len()` bytes into the buffer - fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), super::Error> { + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), ReadError> { let mut total_read = 0; while total_read < buf.len() { let n = self.read(&mut buf[total_read..])?; if n == 0 { - return Err(super::Error::Io); + return Err(ReadError); } total_read += n; } @@ -34,15 +37,15 @@ pub trait Write { /// Write bytes from the buffer /// /// Returns the number of bytes written - fn write(&mut self, buf: &[u8]) -> Result; + fn write(&mut self, buf: &[u8]) -> Result; /// Write all bytes from the buffer - fn write_all(&mut self, buf: &[u8]) -> Result<(), super::Error> { + fn write_all(&mut self, buf: &[u8]) -> Result<(), WriteError> { let mut total_written = 0; while total_written < buf.len() { let n = self.write(&buf[total_written..])?; if n == 0 { - return Err(super::Error::Io); + return Err(WriteError); } total_written += n; } @@ -51,19 +54,19 @@ pub trait Write { } /// Write a 9P message to a transport -pub(crate) fn write_message( +pub(super) fn write_message( w: &mut W, buf: &mut Vec, fcall: &super::fcall::TaggedFcall<'_>, -) -> Result<(), super::Error> { +) -> Result<(), WriteError> { fcall.encode_to_buf(buf)?; w.write_all(&buf[..]) } /// Read a 9P message size header (4 bytes) and then the full message -pub(crate) fn read_to_buf(r: &mut R, buf: &mut Vec) -> Result<(), super::Error> { +pub(super) fn read_to_buf(r: &mut R, buf: &mut Vec) -> Result<(), super::Error> { buf.resize(4, 0); - r.read_exact(&mut buf[..])?; + r.read_exact(&mut buf[..]).map_err(|_| super::Error::Io)?; let sz = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize; if sz < 7 { // Minimum message size: size(4) + type(1) + tag(2) @@ -73,15 +76,31 @@ pub(crate) fn read_to_buf(r: &mut R, buf: &mut Vec) -> Result<(), s buf.reserve(sz - buf.len()); } buf.resize(sz, 0); - r.read_exact(&mut buf[4..])?; - Ok(()) + r.read_exact(&mut buf[4..]).map_err(|_| super::Error::Io) } /// Read a 9P message from a transport -pub(crate) fn read_message<'a, R: Read>( +pub(super) fn read_message<'a, R: Read>( r: &mut R, buf: &'a mut Vec, ) -> Result, super::Error> { read_to_buf(r, buf)?; super::fcall::TaggedFcall::decode(&buf[..]) } + +impl Write for &mut [u8] { + fn write(&mut self, buf: &[u8]) -> Result { + let amt = self.len().min(buf.len()); + let (a, b) = core::mem::take(self).split_at_mut(amt); + a.copy_from_slice(&buf[..amt]); + *self = b; + Ok(amt) + } +} + +impl Write for Vec { + fn write(&mut self, buf: &[u8]) -> Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } +} From 79afcd020b112ac81034c0c2c33673bff3ec2597 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Wed, 11 Feb 2026 10:43:31 -0800 Subject: [PATCH 03/16] fix error code --- litebox/src/fs/errors.rs | 24 ++++ litebox/src/fs/layered.rs | 23 +++- litebox/src/fs/nine_p/fcall.rs | 17 ++- litebox/src/fs/nine_p/mod.rs | 180 +++++++------------------- litebox/src/fs/nine_p/transport.rs | 2 +- litebox_common_linux/src/errno/mod.rs | 1 + 6 files changed, 104 insertions(+), 143 deletions(-) diff --git a/litebox/src/fs/errors.rs b/litebox/src/fs/errors.rs index 38089c65f..3e5846596 100644 --- a/litebox/src/fs/errors.rs +++ b/litebox/src/fs/errors.rs @@ -25,6 +25,8 @@ pub enum OpenError { AlreadyExists, #[error("error when truncating: {0}")] TruncateError(#[from] TruncateError), + #[error("I/O error")] + Io, #[error(transparent)] PathError(#[from] PathError), } @@ -44,6 +46,8 @@ pub enum ReadError { NotAFile, #[error("file not open for reading")] NotForReading, + #[error("I/O error")] + Io, } /// Possible errors from [`FileSystem::write`] @@ -56,6 +60,8 @@ pub enum WriteError { NotAFile, #[error("file not open for writing")] NotForWriting, + #[error("I/O error")] + Io, } /// Possible errors from [`FileSystem::seek`] @@ -70,6 +76,8 @@ pub enum SeekError { InvalidOffset, #[error("non-seekable file")] NonSeekable, + #[error("I/O error")] + Io, } /// Possible errors from [`FileSystem::truncate`] @@ -83,6 +91,8 @@ pub enum TruncateError { NotForWriting, #[error("file descriptor points to a terminal device")] IsTerminalDevice, + #[error("I/O error")] + Io, } /// Possible errors from [`FileSystem::chmod`] @@ -96,6 +106,8 @@ pub enum ChmodError { NotTheOwner, #[error("the named file resides on a read-only filesystem")] ReadOnlyFileSystem, + #[error("I/O error")] + Io, #[error(transparent)] PathError(#[from] PathError), } @@ -111,6 +123,8 @@ pub enum ChownError { NotTheOwner, #[error("the named file resides on a read-only filesystem")] ReadOnlyFileSystem, + #[error("I/O error")] + Io, #[error(transparent)] PathError(#[from] PathError), } @@ -125,6 +139,8 @@ pub enum UnlinkError { IsADirectory, #[error("the named file resides on a read-only filesystem")] ReadOnlyFileSystem, + #[error("I/O error")] + Io, #[error(transparent)] PathError(#[from] PathError), } @@ -139,6 +155,8 @@ pub enum MkdirError { AlreadyExists, #[error("the named file resides on a read-only filesystem")] ReadOnlyFileSystem, + #[error("I/O error")] + Io, #[error(transparent)] PathError(#[from] PathError), } @@ -159,6 +177,8 @@ pub enum RmdirError { NotADirectory, #[error("the named file resides on a read-only filesystem")] ReadOnlyFileSystem, + #[error("I/O error")] + Io, #[error(transparent)] PathError(#[from] PathError), } @@ -171,6 +191,8 @@ pub enum ReadDirError { ClosedFd, #[error("fd does not point to a directory")] NotADirectory, + #[error("I/O error")] + Io, } /// Possible errors from [`FileSystem::file_status`] @@ -179,6 +201,8 @@ pub enum ReadDirError { pub enum FileStatusError { #[error("fd has been closed already")] ClosedFd, + #[error("I/O error")] + Io, #[error(transparent)] PathError(#[from] PathError), } diff --git a/litebox/src/fs/layered.rs b/litebox/src/fs/layered.rs index 674f6b4c3..9868bb50b 100644 --- a/litebox/src/fs/layered.rs +++ b/litebox/src/fs/layered.rs @@ -100,6 +100,7 @@ impl Ok(stat.file_type), Err(FileStatusError::ClosedFd) => unreachable!(), + Err(FileStatusError::Io) => Err(PathError::NoSuchFileOrDirectory), Err(FileStatusError::PathError(e)) => Err(e), } } @@ -132,6 +133,7 @@ impl fd, Err(e) => match e { OpenError::AccessNotAllowed => return Err(MigrationError::NoReadPerms), + OpenError::Io => return Err(MigrationError::Io), OpenError::NoWritePerms | OpenError::ReadOnlyFileSystem | OpenError::AlreadyExists @@ -255,6 +258,7 @@ impl unreachable!(), + ReadError::Io => return Err(MigrationError::Io), }, } } @@ -419,6 +423,8 @@ pub enum MigrationError { NotAFile, #[error("no read access permissions")] NoReadPerms, + #[error("I/O error")] + Io, #[error(transparent)] PathError(#[from] PathError), } @@ -525,6 +531,7 @@ impl< } Err(e) => match &e { OpenError::AccessNotAllowed + | OpenError::Io | OpenError::NoWritePerms | OpenError::ReadOnlyFileSystem | OpenError::AlreadyExists @@ -532,7 +539,8 @@ impl< TruncateError::IsDirectory | TruncateError::NotForWriting | TruncateError::IsTerminalDevice - | TruncateError::ClosedFd, + | TruncateError::ClosedFd + | TruncateError::Io, ) | OpenError::PathError( PathError::ComponentNotADirectory @@ -816,6 +824,7 @@ impl< Ok(()) => {} Err(MigrationError::NoReadPerms) => unimplemented!(), Err(MigrationError::NotAFile) => return Err(WriteError::NotAFile), + Err(MigrationError::Io) => return Err(WriteError::Io), Err(MigrationError::PathError(_e)) => unreachable!(), } // As a sanity check, in debug mode, confirm that it is now an upper file @@ -905,6 +914,7 @@ impl< Ok(()) } + Err(TruncateError::Io) => Err(TruncateError::Io), } } else { // The lower level truncate will correctly identify dir/file and handle @@ -924,6 +934,7 @@ impl< Ok(()) => return Ok(()), Err(e) => match e { ChmodError::NotTheOwner + | ChmodError::Io | ChmodError::ReadOnlyFileSystem | ChmodError::PathError( PathError::ComponentNotADirectory @@ -944,6 +955,7 @@ impl< Ok(()) => {} Err(MigrationError::NoReadPerms) => unimplemented!(), Err(MigrationError::NotAFile) => unimplemented!(), + Err(MigrationError::Io) => return Err(ChmodError::Io), Err(MigrationError::PathError(_e)) => unreachable!(), } // Since it has been migrated, we can just re-trigger, causing it to apply to the @@ -962,6 +974,7 @@ impl< Ok(()) => return Ok(()), Err(e) => match e { ChownError::NotTheOwner + | ChownError::Io | ChownError::ReadOnlyFileSystem | ChownError::PathError( PathError::ComponentNotADirectory @@ -982,6 +995,7 @@ impl< Ok(()) => {} Err(MigrationError::NoReadPerms) => unimplemented!(), Err(MigrationError::NotAFile) => unimplemented!(), + Err(MigrationError::Io) => return Err(ChownError::Io), Err(MigrationError::PathError(_e)) => unreachable!(), } // Since it has been migrated, we can just re-trigger, causing it to apply to the @@ -1005,6 +1019,7 @@ impl< } Err(e) => match e { UnlinkError::NoWritePerms + | UnlinkError::Io | UnlinkError::IsADirectory | UnlinkError::ReadOnlyFileSystem | UnlinkError::PathError( @@ -1054,6 +1069,7 @@ impl< } Err(e) => match e { MkdirError::NoWritePerms + | MkdirError::Io | MkdirError::AlreadyExists | MkdirError::ReadOnlyFileSystem | MkdirError::PathError( @@ -1099,6 +1115,7 @@ impl< } OpenError::PathError(pe) => return Err(pe.into()), OpenError::AccessNotAllowed => todo!(), + OpenError::Io => return Err(RmdirError::Io), OpenError::ReadOnlyFileSystem => { return Err(RmdirError::ReadOnlyFileSystem); } @@ -1112,6 +1129,7 @@ impl< let entries = match self.read_dir(&dir_fd) { Ok(entries) => entries, Err(ReadDirError::ClosedFd | ReadDirError::NotADirectory) => unreachable!(), + Err(ReadDirError::Io) => return Err(RmdirError::Io), }; self.close(&dir_fd).expect("close dir fd failed"); // "." and ".." are always present; anything more => not empty. @@ -1135,6 +1153,7 @@ impl< ) => unreachable!(), RmdirError::Busy | RmdirError::NoWritePerms + | RmdirError::Io | RmdirError::PathError(PathError::NoSearchPerms { .. }) => return Err(e), } } @@ -1161,6 +1180,7 @@ impl< ) => unreachable!(), RmdirError::Busy | RmdirError::NoWritePerms + | RmdirError::Io | RmdirError::PathError(PathError::NoSearchPerms { .. }) => return Err(e), } } @@ -1278,6 +1298,7 @@ impl< // None of these can be handled by lower level, just quit out early return Err(e); } + FileStatusError::Io => return Err(e), FileStatusError::PathError( PathError::NoSuchFileOrDirectory | PathError::MissingComponent, ) => { diff --git a/litebox/src/fs/nine_p/fcall.rs b/litebox/src/fs/nine_p/fcall.rs index 67dfed6a0..32a88a6f4 100644 --- a/litebox/src/fs/nine_p/fcall.rs +++ b/litebox/src/fs/nine_p/fcall.rs @@ -206,7 +206,6 @@ impl<'a> FcallStr<'a> { pub fn len(&self) -> usize { self.as_bytes().len() } - } impl<'a, T: ?Sized + AsRef<[u8]>> From<&'a T> for FcallStr<'a> { @@ -1464,7 +1463,7 @@ impl<'a> TaggedFcall<'a> { /// Decode a message from a buffer pub fn decode(buf: &'a [u8]) -> Result, super::Error> { if buf.len() < 7 { - return Err(super::Error::InvalidInput); + return Err(super::Error::InvalidResponse); } let mut decoder = FcallDecoder { buf: &buf[4..] }; @@ -1990,7 +1989,7 @@ impl<'b> FcallDecoder<'b> { self.buf = &self.buf[1..]; Ok(*v) } else { - Err(super::Error::InvalidInput) + Err(super::Error::InvalidResponse) } } @@ -2000,7 +1999,7 @@ impl<'b> FcallDecoder<'b> { self.buf = &self.buf[2..]; Ok(v) } else { - Err(super::Error::InvalidInput) + Err(super::Error::InvalidResponse) } } @@ -2010,7 +2009,7 @@ impl<'b> FcallDecoder<'b> { self.buf = &self.buf[4..]; Ok(v) } else { - Err(super::Error::InvalidInput) + Err(super::Error::InvalidResponse) } } @@ -2020,7 +2019,7 @@ impl<'b> FcallDecoder<'b> { self.buf = &self.buf[8..]; Ok(v) } else { - Err(super::Error::InvalidInput) + Err(super::Error::InvalidResponse) } } @@ -2031,7 +2030,7 @@ impl<'b> FcallDecoder<'b> { self.buf = &self.buf[n..]; Ok(v) } else { - Err(super::Error::InvalidInput) + Err(super::Error::InvalidResponse) } } @@ -2042,7 +2041,7 @@ impl<'b> FcallDecoder<'b> { self.buf = &self.buf[n..]; Ok(Cow::from(v)) } else { - Err(super::Error::InvalidInput) + Err(super::Error::InvalidResponse) } } @@ -2396,7 +2395,7 @@ impl<'b> FcallDecoder<'b> { fid: self.decode_u32()?, }), Some(FcallType::Rremove) => Fcall::Rremove(Rremove {}), - None => return Err(super::Error::InvalidInput), + None => return Err(super::Error::InvalidResponse), }; Ok(TaggedFcall { tag, fcall }) } diff --git a/litebox/src/fs/nine_p/mod.rs b/litebox/src/fs/nine_p/mod.rs index 8c71191ab..c7f225a4d 100644 --- a/litebox/src/fs/nine_p/mod.rs +++ b/litebox/src/fs/nine_p/mod.rs @@ -41,51 +41,42 @@ const DEVICE_ID: usize = u32::from_le_bytes(*b"NINE") as usize; /// Error type for 9P operations #[derive(Debug, Error)] pub enum Error { - /// I/O error during transport #[error("I/O error")] Io, - /// Invalid input (e.g., malformed protocol message) - #[error("Invalid input")] - InvalidInput, - #[error("Invalid response from server")] InvalidResponse, #[error("Invalid pathname")] InvalidPathname, - /// Path not found #[error("Path not found")] NotFound, - /// File already exists #[error("File already exists")] AlreadyExists, - /// Permission denied #[error("Permission denied")] PermissionDenied, - /// Not a directory #[error("Not a directory")] NotADirectory, - /// Is a directory #[error("Is a directory")] IsADirectory, - /// Name too long + #[error("Directory not empty")] + NotEmpty, + #[error("Name too long")] NameTooLong, - /// Connection error - #[error("Connection error")] - Connection, - - /// Operation not supported #[error("Operation not supported")] NotSupported, + + /// Unrecognized remote error code not mapped to a specific variant + #[error("Remote error (errno={0})")] + RemoteError(u32), } impl From for OpenError { @@ -96,13 +87,8 @@ impl From for OpenError { Error::PermissionDenied => OpenError::AccessNotAllowed, Error::NotADirectory => OpenError::PathError(PathError::ComponentNotADirectory), Error::InvalidPathname => OpenError::PathError(PathError::InvalidPathname), - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::IsADirectory - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to OpenError"), + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => OpenError::Io, + _ => unimplemented!("convert {e:?} to OpenError"), } } } @@ -110,18 +96,10 @@ impl From for OpenError { impl From for ReadError { fn from(e: Error) -> Self { match e { - Error::NotFound => ReadError::NotAFile, + Error::NotFound | Error::IsADirectory => ReadError::NotAFile, Error::PermissionDenied => ReadError::NotForReading, - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::InvalidPathname - | Error::AlreadyExists - | Error::NotADirectory - | Error::IsADirectory - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to ReadError"), + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => ReadError::Io, + _ => unimplemented!("convert {e:?} to ReadError"), } } } @@ -129,18 +107,10 @@ impl From for ReadError { impl From for WriteError { fn from(e: Error) -> Self { match e { - Error::NotFound => WriteError::NotAFile, + Error::NotFound | Error::IsADirectory => WriteError::NotAFile, Error::PermissionDenied => WriteError::NotForWriting, - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::InvalidPathname - | Error::AlreadyExists - | Error::NotADirectory - | Error::IsADirectory - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to WriteError"), + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => WriteError::Io, + _ => unimplemented!("convert {e:?} to WriteError"), } } } @@ -153,13 +123,8 @@ impl From for MkdirError { Error::PermissionDenied => MkdirError::NoWritePerms, Error::NotADirectory => MkdirError::PathError(PathError::ComponentNotADirectory), Error::InvalidPathname => MkdirError::PathError(PathError::InvalidPathname), - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::IsADirectory - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to MkdirError"), + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => MkdirError::Io, + _ => unimplemented!("convert {e:?} to MkdirError"), } } } @@ -167,18 +132,9 @@ impl From for MkdirError { impl From for ReadDirError { fn from(e: Error) -> Self { match e { - Error::NotFound => ReadDirError::NotADirectory, - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::InvalidPathname - | Error::AlreadyExists - | Error::PermissionDenied - | Error::NotADirectory - | Error::IsADirectory - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to ReadDirError"), + Error::NotFound | Error::NotADirectory => ReadDirError::NotADirectory, + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => ReadDirError::Io, + _ => unimplemented!("convert {e:?} to ReadDirError"), } } } @@ -191,13 +147,8 @@ impl From for UnlinkError { Error::PermissionDenied => UnlinkError::NoWritePerms, Error::NotADirectory => UnlinkError::PathError(PathError::ComponentNotADirectory), Error::InvalidPathname => UnlinkError::PathError(PathError::InvalidPathname), - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::AlreadyExists - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to UnlinkError"), + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => UnlinkError::Io, + _ => unimplemented!("convert {e:?} to UnlinkError"), } } } @@ -209,14 +160,9 @@ impl From for RmdirError { Error::NotADirectory => RmdirError::NotADirectory, Error::PermissionDenied => RmdirError::NoWritePerms, Error::InvalidPathname => RmdirError::PathError(PathError::InvalidPathname), - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::AlreadyExists - | Error::IsADirectory - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to RmdirError"), + Error::NotEmpty => RmdirError::NotEmpty, + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => RmdirError::Io, + _ => unimplemented!("convert {e:?} to RmdirError"), } } } @@ -227,15 +173,8 @@ impl From for FileStatusError { Error::NotFound => FileStatusError::PathError(PathError::NoSuchFileOrDirectory), Error::InvalidPathname => FileStatusError::PathError(PathError::InvalidPathname), Error::NotADirectory => FileStatusError::PathError(PathError::ComponentNotADirectory), - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::AlreadyExists - | Error::PermissionDenied - | Error::IsADirectory - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to FileStatusError"), + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => FileStatusError::Io, + _ => unimplemented!("convert {e:?} to FileStatusError"), } } } @@ -244,17 +183,8 @@ impl From for SeekError { fn from(e: Error) -> Self { match e { Error::NotFound => SeekError::ClosedFd, - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::InvalidPathname - | Error::AlreadyExists - | Error::PermissionDenied - | Error::NotADirectory - | Error::IsADirectory - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to SeekError"), + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => SeekError::Io, + _ => unimplemented!("convert {e:?} to SeekError"), } } } @@ -265,15 +195,8 @@ impl From for TruncateError { Error::NotFound => TruncateError::ClosedFd, Error::IsADirectory => TruncateError::IsDirectory, Error::PermissionDenied => TruncateError::NotForWriting, - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::InvalidPathname - | Error::AlreadyExists - | Error::NotADirectory - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to TruncateError"), + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => TruncateError::Io, + _ => unimplemented!("convert {e:?} to TruncateError"), } } } @@ -284,15 +207,9 @@ impl From for ChmodError { Error::NotFound => ChmodError::PathError(PathError::NoSuchFileOrDirectory), Error::InvalidPathname => ChmodError::PathError(PathError::InvalidPathname), Error::NotADirectory => ChmodError::PathError(PathError::ComponentNotADirectory), - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::AlreadyExists - | Error::PermissionDenied - | Error::IsADirectory - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to ChmodError"), + Error::PermissionDenied => ChmodError::NotTheOwner, + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => ChmodError::Io, + _ => unimplemented!("convert {e:?} to ChmodError"), } } } @@ -303,15 +220,9 @@ impl From for ChownError { Error::NotFound => ChownError::PathError(PathError::NoSuchFileOrDirectory), Error::InvalidPathname => ChownError::PathError(PathError::InvalidPathname), Error::NotADirectory => ChownError::PathError(PathError::ComponentNotADirectory), - Error::Io - | Error::InvalidInput - | Error::InvalidResponse - | Error::AlreadyExists - | Error::PermissionDenied - | Error::IsADirectory - | Error::NameTooLong - | Error::Connection - | Error::NotSupported => unimplemented!("convert {e:?} to ChownError"), + Error::PermissionDenied => ChownError::NotTheOwner, + Error::Io | Error::InvalidResponse | Error::RemoteError(_) => ChownError::Io, + _ => unimplemented!("convert {e:?} to ChownError"), } } } @@ -320,26 +231,29 @@ impl From for ChownError { impl From for Error { fn from(err: Rlerror) -> Self { // Common POSIX error codes + const EPERM: u32 = 1; const ENOENT: u32 = 2; + const EIO: u32 = 5; const EACCES: u32 = 13; const EEXIST: u32 = 17; const ENOTDIR: u32 = 20; const EISDIR: u32 = 21; const ENAMETOOLONG: u32 = 36; const ENOSYS: u32 = 38; + const ENOTEMPTY: u32 = 39; const EOPNOTSUPP: u32 = 95; match err.ecode { + EPERM | EACCES => Error::PermissionDenied, ENOENT => Error::NotFound, - EACCES => Error::PermissionDenied, + EIO => Error::Io, EEXIST => Error::AlreadyExists, ENOTDIR => Error::NotADirectory, EISDIR => Error::IsADirectory, ENAMETOOLONG => Error::NameTooLong, ENOSYS | EOPNOTSUPP => Error::NotSupported, - // Unrecognized remote error codes are mapped to a generic I/O error. - // This loses the specific error code but avoids panicking at runtime. - _ => Error::Io, + ENOTEMPTY => Error::NotEmpty, + _ => Error::RemoteError(err.ecode), } } } @@ -620,7 +534,6 @@ impl super::FileSystem for FileSystem { - #[expect(clippy::similar_names)] fn open( &self, path: impl crate::path::Arg, @@ -640,7 +553,10 @@ impl = path.normalized_components().map_err(|_| OpenError::PathError(PathError::InvalidPathname))?.collect(); + let components: Vec<&str> = path + .normalized_components() + .map_err(|_| OpenError::PathError(PathError::InvalidPathname))? + .collect(); let lflags = Self::oflags_to_lopen(flags); let needs_create = flags.contains(super::OFlags::CREAT); diff --git a/litebox/src/fs/nine_p/transport.rs b/litebox/src/fs/nine_p/transport.rs index ecb6110c6..7e40988f3 100644 --- a/litebox/src/fs/nine_p/transport.rs +++ b/litebox/src/fs/nine_p/transport.rs @@ -70,7 +70,7 @@ pub(super) fn read_to_buf(r: &mut R, buf: &mut Vec) -> Result<(), s let sz = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize; if sz < 7 { // Minimum message size: size(4) + type(1) + tag(2) - return Err(super::Error::InvalidInput); + return Err(super::Error::InvalidResponse); } if sz > buf.capacity() { buf.reserve(sz - buf.len()); diff --git a/litebox_common_linux/src/errno/mod.rs b/litebox_common_linux/src/errno/mod.rs index ef025206d..06290f2a5 100644 --- a/litebox_common_linux/src/errno/mod.rs +++ b/litebox_common_linux/src/errno/mod.rs @@ -555,6 +555,7 @@ impl From for Errno { litebox::fs::errors::TruncateError::NotForWriting => Errno::EACCES, litebox::fs::errors::TruncateError::IsTerminalDevice => Errno::EINVAL, litebox::fs::errors::TruncateError::ClosedFd => Errno::EBADF, + litebox::fs::errors::TruncateError::Io => Errno::EIO, } } } From 0a83435403a82a67d9f69a97062bc5a09c4f2a1c Mon Sep 17 00:00:00 2001 From: weitengchen Date: Mon, 9 Feb 2026 00:18:31 -0800 Subject: [PATCH 04/16] add test --- litebox/Cargo.toml | 3 + litebox/src/fs/nine_p/mod.rs | 3 + litebox/src/fs/nine_p/tests.rs | 390 +++++++++++++++++++++++++++++++++ 3 files changed, 396 insertions(+) create mode 100644 litebox/src/fs/nine_p/tests.rs diff --git a/litebox/Cargo.toml b/litebox/Cargo.toml index c115a7110..9a8b2e401 100644 --- a/litebox/Cargo.toml +++ b/litebox/Cargo.toml @@ -36,3 +36,6 @@ enforce_singleton_litebox_instance = [] [lints] workspace = true + +[dev-dependencies] +tempfile = "3" diff --git a/litebox/src/fs/nine_p/mod.rs b/litebox/src/fs/nine_p/mod.rs index c7f225a4d..54bfe67b2 100644 --- a/litebox/src/fs/nine_p/mod.rs +++ b/litebox/src/fs/nine_p/mod.rs @@ -36,6 +36,9 @@ mod fcall; pub mod transport; +#[cfg(test)] +mod tests; + const DEVICE_ID: usize = u32::from_le_bytes(*b"NINE") as usize; /// Error type for 9P operations diff --git a/litebox/src/fs/nine_p/tests.rs b/litebox/src/fs/nine_p/tests.rs new file mode 100644 index 000000000..2d32382ff --- /dev/null +++ b/litebox/src/fs/nine_p/tests.rs @@ -0,0 +1,390 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +extern crate std; + +use std::io::{Read as _, Write as _}; +use std::net::{TcpListener, TcpStream}; +use std::path::Path; + +use crate::fs::{FileSystem as _, Mode, OFlags}; +use crate::platform::mock::MockPlatform; + +use super::transport; + +// --------------------------------------------------------------------------- +// Transport adapter: implement litebox 9P transport traits for TcpStream +// --------------------------------------------------------------------------- + +/// A wrapper around `TcpStream` that implements the litebox 9P transport traits. +struct TcpTransport { + stream: TcpStream, +} + +impl TcpTransport { + fn connect(addr: &str) -> Self { + let stream = TcpStream::connect(addr).expect("failed to connect to 9P server"); + Self { stream } + } +} + +impl transport::Read for TcpTransport { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.stream.read(buf).map_err(|_| transport::ReadError) + } +} + +impl transport::Write for TcpTransport { + fn write(&mut self, buf: &[u8]) -> Result { + self.stream.write(buf).map_err(|_| transport::WriteError) + } +} + +// --------------------------------------------------------------------------- +// diod server management +// --------------------------------------------------------------------------- + +/// Find a free TCP port by binding to port 0. +fn find_free_port() -> u16 { + let listener = TcpListener::bind("127.0.0.1:0").expect("failed to bind to port 0"); + listener.local_addr().unwrap().port() +} + +/// A running `diod` 9P server instance that exports a temporary directory. +struct DiodServer { + child: std::process::Child, + port: u16, + _export_dir: tempfile::TempDir, + export_path: std::path::PathBuf, +} + +impl DiodServer { + /// Start a new `diod` server exporting a fresh temporary directory. + fn start() -> Self { + let export_dir = tempfile::tempdir().expect("failed to create temp dir"); + let export_path = export_dir.path().to_path_buf(); + let port = find_free_port(); + + let child = std::process::Command::new("diod") + .args([ + "--foreground", + "--no-auth", + "--export", + export_dir.path().to_str().unwrap(), + "--listen", + &std::format!("127.0.0.1:{port}"), + "--nwthreads", + "1", + ]) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("failed to start diod – is it installed? (`apt install diod`)"); + + // Give the server a moment to start listening + std::thread::sleep(std::time::Duration::from_millis(500)); + + Self { + child, + port, + _export_dir: export_dir, + export_path, + } + } + + /// TCP address of the server (e.g., "127.0.0.1:12345"). + fn addr(&self) -> std::string::String { + std::format!("127.0.0.1:{}", self.port) + } + + /// Path to the exported directory on the host. + fn export_path(&self) -> &Path { + &self.export_path + } +} + +impl Drop for DiodServer { + fn drop(&mut self) { + let _ = self.child.kill(); + let _ = self.child.wait(); + } +} + +// --------------------------------------------------------------------------- +// Helper: create a connected 9P filesystem +// --------------------------------------------------------------------------- + +fn connect_9p( + litebox: &crate::LiteBox, + server: &DiodServer, +) -> super::FileSystem { + let transport = TcpTransport::connect(&server.addr()); + let aname = server.export_path().to_str().unwrap(); + let username = std::env::var("USER") + .or_else(|_| std::env::var("LOGNAME")) + .unwrap_or_else(|_| std::string::String::from("nobody")); + super::FileSystem::new(litebox, transport, 65536, &username, aname) + .expect("failed to create 9P filesystem") +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[test] +fn test_nine_p_create_and_read_file() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + let fs = connect_9p(&litebox, &server); + + // Create a file and write to it + let fd = fs + .open("/hello.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) + .expect("failed to create file via 9P"); + + let data = b"Hello from litebox 9P!"; + let written = fs.write(&fd, data, None).expect("failed to write via 9P"); + assert_eq!(written, data.len()); + + fs.close(&fd).expect("failed to close file"); + + // Verify the file exists on the host + let host_path = server.export_path().join("hello.txt"); + assert!(host_path.exists(), "file should exist on host"); + let host_content = std::fs::read_to_string(&host_path).unwrap(); + assert_eq!(host_content, "Hello from litebox 9P!"); + + // Read the file back through 9P + let fd = fs + .open("/hello.txt", OFlags::RDONLY, Mode::empty()) + .expect("failed to open file for reading via 9P"); + + let mut buf = alloc::vec![0u8; 256]; + let bytes_read = fs.read(&fd, &mut buf, None).expect("failed to read via 9P"); + assert_eq!(&buf[..bytes_read], data); + + fs.close(&fd).expect("failed to close file"); +} + +#[test] +fn test_nine_p_mkdir_and_readdir() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + let fs = connect_9p(&litebox, &server); + + // Create directories + fs.mkdir("/subdir", Mode::RWXU) + .expect("failed to mkdir via 9P"); + fs.mkdir("/subdir/nested", Mode::RWXU) + .expect("failed to mkdir nested via 9P"); + + // Create a file inside the subdirectory + let fd = fs + .open( + "/subdir/file.txt", + OFlags::CREAT | OFlags::WRONLY, + Mode::RWXU, + ) + .expect("failed to create file in subdir"); + fs.write(&fd, b"nested content", None).unwrap(); + fs.close(&fd).unwrap(); + + // Read the root directory + let fd = fs + .open("/", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()) + .expect("failed to open root dir"); + let entries = fs.read_dir(&fd).expect("failed to readdir root"); + fs.close(&fd).unwrap(); + + let names: alloc::vec::Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect(); + assert!( + names.contains(&"subdir"), + "root should contain 'subdir', got: {names:?}" + ); + + // Read the subdirectory + let fd = fs + .open("/subdir", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()) + .expect("failed to open subdir"); + let entries = fs.read_dir(&fd).expect("failed to readdir subdir"); + fs.close(&fd).unwrap(); + + let names: alloc::vec::Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect(); + assert!( + names.contains(&"nested"), + "subdir should contain 'nested', got: {names:?}" + ); + assert!( + names.contains(&"file.txt"), + "subdir should contain 'file.txt', got: {names:?}" + ); +} + +#[test] +fn test_nine_p_unlink_and_rmdir() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + let fs = connect_9p(&litebox, &server); + + // Create a file, then delete it + let fd = fs + .open("/to_delete.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) + .expect("failed to create file"); + fs.close(&fd).unwrap(); + + fs.unlink("/to_delete.txt") + .expect("failed to unlink file via 9P"); + + // Verify the file is gone + assert!( + fs.open("/to_delete.txt", OFlags::RDONLY, Mode::empty()) + .is_err(), + "file should no longer exist" + ); + + // Create a directory, then remove it + fs.mkdir("/to_remove", Mode::RWXU).expect("failed to mkdir"); + fs.rmdir("/to_remove").expect("failed to rmdir via 9P"); + + // Verify the directory is gone on the host + assert!( + !server.export_path().join("to_remove").exists(), + "directory should no longer exist on host" + ); +} + +#[test] +fn test_nine_p_file_status() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + let fs = connect_9p(&litebox, &server); + + // Create a file with known content + let fd = fs + .open( + "/status_test.txt", + OFlags::CREAT | OFlags::WRONLY, + Mode::RWXU, + ) + .expect("failed to create file"); + let data = b"1234567890"; + fs.write(&fd, data, None).unwrap(); + fs.close(&fd).unwrap(); + + // Check file_status via path + let status = fs + .file_status("/status_test.txt") + .expect("failed to stat file"); + assert_eq!( + status.file_type, + crate::fs::FileType::RegularFile, + "should be a regular file" + ); + assert_eq!(status.size, 10, "file size should be 10 bytes"); + + // Check directory status + fs.mkdir("/stat_dir", Mode::RWXU).unwrap(); + let status = fs.file_status("/stat_dir").expect("failed to stat dir"); + assert_eq!( + status.file_type, + crate::fs::FileType::Directory, + "should be a directory" + ); +} + +#[test] +fn test_nine_p_seek_and_partial_read() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + let fs = connect_9p(&litebox, &server); + + // Write a file with known content + let fd = fs + .open("/seek_test.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) + .expect("failed to create file"); + fs.write(&fd, b"ABCDEFGHIJ", None).unwrap(); + fs.close(&fd).unwrap(); + + // Open for reading and seek + let fd = fs + .open("/seek_test.txt", OFlags::RDONLY, Mode::empty()) + .expect("failed to open file for reading"); + + // Seek to offset 5 + let pos = fs + .seek(&fd, 5, crate::fs::SeekWhence::RelativeToBeginning) + .expect("failed to seek"); + assert_eq!(pos, 5); + + // Read from offset 5 → should get "FGHIJ" + let mut buf = alloc::vec![0u8; 10]; + let n = fs.read(&fd, &mut buf, None).expect("failed to read"); + assert_eq!(&buf[..n], b"FGHIJ"); + + fs.close(&fd).unwrap(); +} + +#[test] +fn test_nine_p_truncate() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + let fs = connect_9p(&litebox, &server); + + // Write a file + let fd = fs + .open("/trunc_test.txt", OFlags::CREAT | OFlags::RDWR, Mode::RWXU) + .expect("failed to create file"); + fs.write(&fd, b"Hello, World!", None).unwrap(); + + // Truncate to 5 bytes + fs.truncate(&fd, 5, true) + .expect("failed to truncate via 9P"); + fs.close(&fd).unwrap(); + + // Verify on host + let content = std::fs::read_to_string(server.export_path().join("trunc_test.txt")).unwrap(); + assert_eq!(content, "Hello"); +} + +#[test] +fn test_nine_p_host_files_visible() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + + // Pre-populate some files on the host side + std::fs::write(server.export_path().join("host_file.txt"), "from host").unwrap(); + std::fs::create_dir(server.export_path().join("host_dir")).unwrap(); + std::fs::write( + server.export_path().join("host_dir/inner.txt"), + "inner content", + ) + .unwrap(); + + let fs = connect_9p(&litebox, &server); + + // Read file created on the host through 9P + let fd = fs + .open("/host_file.txt", OFlags::RDONLY, Mode::empty()) + .expect("failed to open host file via 9P"); + let mut buf = alloc::vec![0u8; 256]; + let n = fs.read(&fd, &mut buf, None).unwrap(); + assert_eq!(&buf[..n], b"from host"); + fs.close(&fd).unwrap(); + + // List host directory through 9P + let fd = fs + .open( + "/host_dir", + OFlags::RDONLY | OFlags::DIRECTORY, + Mode::empty(), + ) + .expect("failed to open host dir via 9P"); + let entries = fs.read_dir(&fd).unwrap(); + fs.close(&fd).unwrap(); + + let names: alloc::vec::Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect(); + assert!( + names.contains(&"inner.txt"), + "host_dir should contain 'inner.txt', got: {names:?}" + ); +} From c80537c06eb811fa8eb1978a3ac43feff92d4e06 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Mon, 9 Feb 2026 17:05:16 -0800 Subject: [PATCH 05/16] use 9p in linux shim --- litebox/src/fs/nine_p/client.rs | 17 +- litebox/src/fs/nine_p/fcall.rs | 635 +++++++++++------------ litebox/src/fs/nine_p/mod.rs | 194 ++++--- litebox/src/fs/nine_p/transport.rs | 2 +- litebox_shim_linux/Cargo.toml | 1 + litebox_shim_linux/src/lib.rs | 1 + litebox_shim_linux/src/nine_p.rs | 443 ++++++++++++++++ litebox_shim_linux/src/syscalls/net.rs | 264 +++------- litebox_shim_linux/src/syscalls/tests.rs | 20 +- 9 files changed, 983 insertions(+), 594 deletions(-) create mode 100644 litebox_shim_linux/src/nine_p.rs diff --git a/litebox/src/fs/nine_p/client.rs b/litebox/src/fs/nine_p/client.rs index 8947e3a0f..d7966d3d0 100644 --- a/litebox/src/fs/nine_p/client.rs +++ b/litebox/src/fs/nine_p/client.rs @@ -6,7 +6,7 @@ //! This module provides a high-level client for the 9P2000.L protocol. use alloc::vec::Vec; -use core::sync::atomic::{AtomicU32, Ordering}; +use core::sync::atomic::{AtomicU16, Ordering}; use crate::sync::{Mutex, RawSyncPrimitivesProvider}; @@ -95,7 +95,7 @@ pub(super) struct Client { /// Fid generator fids: FidGenerator, /// Next tag for synchronous operations - next_tag: AtomicU32, + next_tag: AtomicU16, } impl Client { @@ -115,7 +115,7 @@ impl Client { transport::write_message( &mut transport, &mut wbuf, - &TaggedFcall { + TaggedFcall { tag: fcall::NOTAG, fcall: Fcall::Tversion(fcall::Tversion { msize: bufsize, @@ -152,7 +152,7 @@ impl Client { write_state: Mutex::new(ClientWriteState { transport, wbuf }), rbuf: Mutex::new(rbuf), fids: FidGenerator::new(), - next_tag: AtomicU32::new(1), + next_tag: AtomicU16::new(1), }) } @@ -160,11 +160,14 @@ impl Client { /// /// TODO: support async operations fn fcall(&self, fcall: Fcall<'_>) -> Result, Error> { - let tag = self.next_tag.fetch_add(1, Ordering::Relaxed) as u16; + let tag = self.next_tag.fetch_add(1, Ordering::Relaxed); + if tag == fcall::NOTAG { + todo!("tag wraparound"); + } let mut write_state = self.write_state.lock(); let ClientWriteState { transport, wbuf } = &mut *write_state; - transport::write_message(transport, wbuf, &TaggedFcall { tag, fcall }) + transport::write_message(transport, wbuf, TaggedFcall { tag, fcall }) .map_err(|_| Error::Io)?; let mut rbuf = self.rbuf.lock(); @@ -331,7 +334,7 @@ impl Client { match self.fcall(Fcall::Tread(fcall::Tread { fid, offset, - count: count as u32, + count: u32::try_from(count).expect("count exceeds u32"), }))? { Fcall::Rread(fcall::Rread { data }) => { buf[..data.len()].copy_from_slice(&data); diff --git a/litebox/src/fs/nine_p/fcall.rs b/litebox/src/fs/nine_p/fcall.rs index 32a88a6f4..e3a13c2c6 100644 --- a/litebox/src/fs/nine_p/fcall.rs +++ b/litebox/src/fs/nine_p/fcall.rs @@ -44,7 +44,7 @@ bitflags! { /// Same as Linux's open flags. /// https://elixir.bootlin.com/linux/v6.12/source/include/net/9p/9p.h#L263 #[derive(Clone, Copy, Debug, PartialEq, Eq)] - pub struct LOpenFlags: u32 { + pub(super) struct LOpenFlags: u32 { const O_RDONLY = 0; const O_WRONLY = 1; const O_RDWR = 2; @@ -70,7 +70,7 @@ bitflags! { bitflags! { /// File lock type, Flock.typ #[derive(Clone, Copy, Debug, PartialEq, Eq)] - pub struct LockType: u8 { + pub(super) struct LockType: u8 { const RDLOCK = 0; const WRLOCK = 1; const UNLOCK = 2; @@ -80,7 +80,7 @@ bitflags! { bitflags! { /// File lock flags, Flock.flags #[derive(Clone, Copy, Debug, PartialEq, Eq)] - pub struct LockFlag: u32 { + pub(super) struct LockFlag: u32 { /// Blocking request const BLOCK = 1; /// Reserved for future use @@ -91,7 +91,7 @@ bitflags! { bitflags! { /// File lock status #[derive(Clone, Copy, Debug, PartialEq, Eq)] - pub struct LockStatus: u8 { + pub(super) struct LockStatus: u8 { const SUCCESS = 0; const BLOCKED = 1; const ERROR = 2; @@ -107,7 +107,7 @@ bitflags! { /// # Protocol /// 9P2000/9P2000.L #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] - pub struct QidType: u8 { + pub(super) struct QidType: u8 { /// Type bit for directories const DIR = 0x80; /// Type bit for append only files @@ -135,7 +135,7 @@ bitflags! { /// # Protocol /// 9P2000.L #[derive(Clone, Copy, Debug, PartialEq, Eq)] - pub struct GetattrMask: u64 { + pub(super) struct GetattrMask: u64 { const MODE = 0x00000001; const NLINK = 0x00000002; const UID = 0x00000004; @@ -168,7 +168,7 @@ bitflags! { /// # Protocol /// 9P2000.L #[derive(Clone, Copy, Debug, PartialEq, Eq)] - pub struct SetattrMask: u32 { + pub(super) struct SetattrMask: u32 { const MODE = 0x00000001; const UID = 0x00000002; const GID = 0x00000004; @@ -190,7 +190,7 @@ pub(super) enum FcallStr<'a> { impl<'a> FcallStr<'a> { /// Get the bytes of the string - pub fn as_bytes(&'a self) -> &'a [u8] { + pub(super) fn as_bytes(&'a self) -> &'a [u8] { match self { FcallStr::Owned(b) => b, FcallStr::Borrowed(b) => b, @@ -198,12 +198,12 @@ impl<'a> FcallStr<'a> { } /// Create a static (owned) copy of this string - pub fn clone_static(&self) -> FcallStr<'static> { + fn clone_static(&self) -> FcallStr<'static> { FcallStr::Owned(self.as_bytes().to_vec()) } /// Get the length of the string - pub fn len(&self) -> usize { + fn len(&self) -> usize { self.as_bytes().len() } } @@ -217,45 +217,24 @@ impl<'a, T: ?Sized + AsRef<[u8]>> From<&'a T> for FcallStr<'a> { /// Directory entry data container #[derive(Clone, Debug)] pub(super) struct DirEntryData<'a> { - pub data: Vec>, + pub(super) data: Vec>, } impl<'a> DirEntryData<'a> { - /// Create a new empty directory entry data - pub fn new() -> DirEntryData<'a> { - Self::with(Vec::new()) - } - /// Create directory entry data from a vector - pub fn with(v: Vec>) -> DirEntryData<'a> { + fn with(v: Vec>) -> DirEntryData<'a> { DirEntryData { data: v } } - /// Get the directory entries - pub fn data(&self) -> &[DirEntry<'_>] { - &self.data - } - /// Calculate the total size of all entries - pub fn size(&self) -> u64 { + fn size(&self) -> u64 { self.data.iter().fold(0, |a, e| a + e.size()) } - - /// Push a new entry - pub fn push(&mut self, entry: DirEntry<'a>) { - self.data.push(entry); - } -} - -impl Default for DirEntryData<'_> { - fn default() -> Self { - Self::new() - } } /// 9P message types #[derive(Copy, Clone, Debug)] -pub(super) enum FcallType { +enum FcallType { // 9P2000.L Rlerror = 7, Tstatfs = 8, @@ -320,7 +299,7 @@ pub(super) enum FcallType { impl FcallType { /// Convert a u8 to FcallType - pub fn from_u8(v: u8) -> Option { + fn from_u8(v: u8) -> Option { match v { // 9P2000.L 7 => Some(FcallType::Rlerror), @@ -390,74 +369,74 @@ impl FcallType { /// Unique identifier for a file #[derive(Clone, Debug, Copy)] pub(super) struct Qid { - pub typ: QidType, - pub version: u32, - pub path: u64, + pub(super) typ: QidType, + pub(super) version: u32, + pub(super) path: u64, } /// File system statistics #[derive(Clone, Debug, Copy)] -pub(super) struct Statfs { - pub typ: u32, - pub bsize: u32, - pub blocks: u64, - pub bfree: u64, - pub bavail: u64, - pub files: u64, - pub ffree: u64, - pub fsid: u64, - pub namelen: u32, +struct Statfs { + typ: u32, + bsize: u32, + blocks: u64, + bfree: u64, + bavail: u64, + files: u64, + ffree: u64, + fsid: u64, + namelen: u32, } /// Time structure #[derive(Clone, Debug, Copy, Default)] pub(super) struct Time { - pub sec: u64, - pub nsec: u64, + sec: u64, + nsec: u64, } /// File attributes #[derive(Clone, Debug, Copy)] pub(super) struct Stat { - pub mode: u32, - pub uid: u32, - pub gid: u32, - pub nlink: u64, - pub rdev: u64, - pub size: u64, - pub blksize: u64, - pub blocks: u64, - pub atime: Time, - pub mtime: Time, - pub ctime: Time, - pub btime: Time, - pub generation: u64, - pub data_version: u64, + pub(super)mode: u32, + pub(super) uid: u32, + pub(super) gid: u32, + pub(super) nlink: u64, + pub(super) rdev: u64, + pub(super) size: u64, + pub(super) blksize: u64, + pub(super) blocks: u64, + pub(super) atime: Time, + pub(super) mtime: Time, + pub(super) ctime: Time, + pub(super) btime: Time, + pub(super) generation: u64, + pub(super) data_version: u64, } /// Set file attributes #[derive(Clone, Debug, Copy, Default)] pub(super) struct SetAttr { - pub mode: u32, - pub uid: u32, - pub gid: u32, - pub size: u64, - pub atime: Time, - pub mtime: Time, + pub(super) mode: u32, + pub(super) uid: u32, + pub(super) gid: u32, + pub(super) size: u64, + pub(super) atime: Time, + pub(super) mtime: Time, } /// Directory entry #[derive(Clone, Debug)] pub(super) struct DirEntry<'a> { - pub qid: Qid, - pub offset: u64, - pub typ: u8, - pub name: FcallStr<'a>, + pub(super) qid: Qid, + pub(super) offset: u64, + pub(super) typ: u8, + pub(super) name: FcallStr<'a>, } impl DirEntry<'_> { /// Calculate the size of this entry when encoded - pub fn size(&self) -> u64 { + fn size(&self) -> u64 { (13 + 8 + 1 + 2 + self.name.len()) as u64 } } @@ -475,12 +454,12 @@ pub(super) struct Flock<'a> { /// Get lock request #[derive(Clone, Debug)] -pub(super) struct Getlock<'a> { - pub typ: LockType, - pub start: u64, - pub length: u64, - pub proc_id: u32, - pub client_id: FcallStr<'a>, +struct Getlock<'a> { + typ: LockType, + start: u64, + length: u64, + proc_id: u32, + client_id: FcallStr<'a>, } // ============================================================================ @@ -502,15 +481,15 @@ impl Display for Rlerror { /// Attach request #[derive(Clone, Debug)] pub(super) struct Tattach<'a> { - pub fid: u32, - pub afid: u32, - pub uname: FcallStr<'a>, - pub aname: FcallStr<'a>, - pub n_uname: u32, + pub(super) fid: u32, + pub(super) afid: u32, + pub(super) uname: FcallStr<'a>, + pub(super) aname: FcallStr<'a>, + pub(super) n_uname: u32, } impl Tattach<'_> { - pub fn clone_static(&self) -> Tattach<'static> { + fn clone_static(&self) -> Tattach<'static> { Tattach { afid: self.afid, fid: self.fid, @@ -524,47 +503,47 @@ impl Tattach<'_> { /// Attach response #[derive(Clone, Debug)] pub(super) struct Rattach { - pub qid: Qid, + pub(super) qid: Qid, } /// Statfs request #[derive(Clone, Debug)] pub(super) struct Tstatfs { - pub fid: u32, + fid: u32, } /// Statfs response #[derive(Clone, Debug)] pub(super) struct Rstatfs { - pub statfs: Statfs, + statfs: Statfs, } /// Open request #[derive(Clone, Debug)] pub(super) struct Tlopen { - pub fid: u32, - pub flags: LOpenFlags, + pub(super) fid: u32, + pub(super) flags: LOpenFlags, } /// Open response #[derive(Clone, Debug)] pub(super) struct Rlopen { - pub qid: Qid, - pub iounit: u32, + pub(super) qid: Qid, + pub(super) iounit: u32, } /// Create request #[derive(Clone, Debug)] pub(super) struct Tlcreate<'a> { - pub fid: u32, - pub name: FcallStr<'a>, - pub flags: LOpenFlags, - pub mode: u32, - pub gid: u32, + pub(super) fid: u32, + pub(super) name: FcallStr<'a>, + pub(super) flags: LOpenFlags, + pub(super) mode: u32, + pub(super) gid: u32, } impl<'a> Tlcreate<'a> { - pub fn clone_static(&'a self) -> Tlcreate<'static> { + fn clone_static(&self) -> Tlcreate<'static> { Tlcreate { fid: self.fid, flags: self.flags, @@ -578,21 +557,21 @@ impl<'a> Tlcreate<'a> { /// Create response #[derive(Clone, Debug)] pub(super) struct Rlcreate { - pub qid: Qid, - pub iounit: u32, + pub(super) qid: Qid, + pub(super) iounit: u32, } /// Symlink request #[derive(Clone, Debug)] pub(super) struct Tsymlink<'a> { - pub fid: u32, - pub name: FcallStr<'a>, - pub symtgt: FcallStr<'a>, - pub gid: u32, + fid: u32, + name: FcallStr<'a>, + symtgt: FcallStr<'a>, + gid: u32, } impl<'a> Tsymlink<'a> { - pub fn clone_static(&'a self) -> Tsymlink<'static> { + fn clone_static(&'a self) -> Tsymlink<'static> { Tsymlink { fid: self.fid, name: self.name.clone_static(), @@ -605,22 +584,22 @@ impl<'a> Tsymlink<'a> { /// Symlink response #[derive(Clone, Debug)] pub(super) struct Rsymlink { - pub qid: Qid, + qid: Qid, } /// Mknod request #[derive(Clone, Debug)] pub(super) struct Tmknod<'a> { - pub dfid: u32, - pub name: FcallStr<'a>, - pub mode: u32, - pub major: u32, - pub minor: u32, - pub gid: u32, + dfid: u32, + name: FcallStr<'a>, + mode: u32, + major: u32, + minor: u32, + gid: u32, } impl<'a> Tmknod<'a> { - pub fn clone_static(&'a self) -> Tmknod<'static> { + fn clone_static(&'a self) -> Tmknod<'static> { Tmknod { dfid: self.dfid, gid: self.gid, @@ -635,19 +614,19 @@ impl<'a> Tmknod<'a> { /// Mknod response #[derive(Clone, Debug)] pub(super) struct Rmknod { - pub qid: Qid, + qid: Qid, } /// Rename request #[derive(Clone, Debug)] pub(super) struct Trename<'a> { - pub fid: u32, - pub dfid: u32, - pub name: FcallStr<'a>, + pub(super) fid: u32, + pub(super) dfid: u32, + pub(super) name: FcallStr<'a>, } impl<'a> Trename<'a> { - pub fn clone_static(&'a self) -> Trename<'static> { + fn clone_static(&self) -> Trename<'static> { Trename { fid: self.fid, dfid: self.dfid, @@ -663,17 +642,17 @@ pub(super) struct Rrename {} /// Readlink request #[derive(Clone, Debug)] pub(super) struct Treadlink { - pub fid: u32, + fid: u32, } /// Readlink response #[derive(Clone, Debug)] pub(super) struct Rreadlink<'a> { - pub target: FcallStr<'a>, + target: FcallStr<'a>, } impl<'a> Rreadlink<'a> { - pub fn clone_static(&'a self) -> Rreadlink<'static> { + fn clone_static(&'a self) -> Rreadlink<'static> { Rreadlink { target: self.target.clone_static(), } @@ -683,24 +662,24 @@ impl<'a> Rreadlink<'a> { /// Getattr request #[derive(Clone, Debug)] pub(super) struct Tgetattr { - pub fid: u32, - pub req_mask: GetattrMask, + pub(super) fid: u32, + pub(super) req_mask: GetattrMask, } /// Getattr response #[derive(Clone, Debug)] pub(super) struct Rgetattr { - pub valid: GetattrMask, - pub qid: Qid, - pub stat: Stat, + pub(super) valid: GetattrMask, + pub(super) qid: Qid, + pub(super) stat: Stat, } /// Setattr request #[derive(Clone, Debug)] pub(super) struct Tsetattr { - pub fid: u32, - pub valid: SetattrMask, - pub stat: SetAttr, + pub(super) fid: u32, + pub(super) valid: SetattrMask, + pub(super) stat: SetAttr, } /// Setattr response @@ -710,13 +689,13 @@ pub(super) struct Rsetattr {} /// Xattr walk request #[derive(Clone, Debug)] pub(super) struct Txattrwalk<'a> { - pub fid: u32, - pub new_fid: u32, - pub name: FcallStr<'a>, + fid: u32, + new_fid: u32, + name: FcallStr<'a>, } impl<'a> Txattrwalk<'a> { - pub fn clone_static(&'a self) -> Txattrwalk<'static> { + fn clone_static(&'a self) -> Txattrwalk<'static> { Txattrwalk { fid: self.fid, new_fid: self.new_fid, @@ -728,20 +707,20 @@ impl<'a> Txattrwalk<'a> { /// Xattr walk response #[derive(Clone, Debug)] pub(super) struct Rxattrwalk { - pub size: u64, + size: u64, } /// Xattr create request #[derive(Clone, Debug)] pub(super) struct Txattrcreate<'a> { - pub fid: u32, - pub name: FcallStr<'a>, - pub attr_size: u64, - pub flags: u32, + fid: u32, + name: FcallStr<'a>, + attr_size: u64, + flags: u32, } impl<'a> Txattrcreate<'a> { - pub fn clone_static(&'a self) -> Txattrcreate<'static> { + fn clone_static(&'a self) -> Txattrcreate<'static> { Txattrcreate { fid: self.fid, name: self.name.clone_static(), @@ -758,19 +737,19 @@ pub(super) struct Rxattrcreate {} /// Readdir request #[derive(Clone, Debug)] pub(super) struct Treaddir { - pub fid: u32, - pub offset: u64, - pub count: u32, + pub(super) fid: u32, + pub(super) offset: u64, + pub(super) count: u32, } /// Readdir response #[derive(Clone, Debug)] pub(super) struct Rreaddir<'a> { - pub data: DirEntryData<'a>, + pub(super) data: DirEntryData<'a>, } impl<'a> Rreaddir<'a> { - pub fn clone_static(&'a self) -> Rreaddir<'static> { + fn clone_static(&'a self) -> Rreaddir<'static> { Rreaddir { data: DirEntryData { data: self @@ -792,8 +771,8 @@ impl<'a> Rreaddir<'a> { /// Fsync request #[derive(Clone, Debug)] pub(super) struct Tfsync { - pub fid: u32, - pub datasync: u32, + pub(super) fid: u32, + pub(super) datasync: u32, } /// Fsync response @@ -803,12 +782,12 @@ pub(super) struct Rfsync {} /// Lock request #[derive(Clone, Debug)] pub(super) struct Tlock<'a> { - pub fid: u32, - pub flock: Flock<'a>, + fid: u32, + flock: Flock<'a>, } impl<'a> Tlock<'a> { - pub fn clone_static(&'a self) -> Tlock<'static> { + fn clone_static(&'a self) -> Tlock<'static> { Tlock { fid: self.fid, flock: Flock { @@ -826,18 +805,18 @@ impl<'a> Tlock<'a> { /// Lock response #[derive(Clone, Debug)] pub(super) struct Rlock { - pub status: LockStatus, + status: LockStatus, } /// Getlock request #[derive(Clone, Debug)] pub(super) struct Tgetlock<'a> { - pub fid: u32, - pub flock: Getlock<'a>, + fid: u32, + flock: Getlock<'a>, } impl<'a> Tgetlock<'a> { - pub fn clone_static(&'a self) -> Tgetlock<'static> { + fn clone_static(&'a self) -> Tgetlock<'static> { Tgetlock { fid: self.fid, flock: Getlock { @@ -854,11 +833,11 @@ impl<'a> Tgetlock<'a> { /// Getlock response #[derive(Clone, Debug)] pub(super) struct Rgetlock<'a> { - pub flock: Getlock<'a>, + flock: Getlock<'a>, } impl<'a> Rgetlock<'a> { - pub fn clone_static(&'a self) -> Rgetlock<'static> { + fn clone_static(&'a self) -> Rgetlock<'static> { Rgetlock { flock: Getlock { typ: self.flock.typ, @@ -874,13 +853,13 @@ impl<'a> Rgetlock<'a> { /// Link request #[derive(Clone, Debug)] pub(super) struct Tlink<'a> { - pub dfid: u32, - pub fid: u32, - pub name: FcallStr<'a>, + dfid: u32, + fid: u32, + name: FcallStr<'a>, } impl<'a> Tlink<'a> { - pub fn clone_static(&'a self) -> Tlink<'static> { + fn clone_static(&'a self) -> Tlink<'static> { Tlink { fid: self.fid, dfid: self.dfid, @@ -896,14 +875,14 @@ pub(super) struct Rlink {} /// Mkdir request #[derive(Clone, Debug)] pub(super) struct Tmkdir<'a> { - pub dfid: u32, - pub name: FcallStr<'a>, - pub mode: u32, - pub gid: u32, + pub(super) dfid: u32, + pub(super) name: FcallStr<'a>, + pub(super) mode: u32, + pub(super) gid: u32, } impl<'a> Tmkdir<'a> { - pub fn clone_static(&'a self) -> Tmkdir<'static> { + fn clone_static(&'a self) -> Tmkdir<'static> { Tmkdir { dfid: self.dfid, gid: self.gid, @@ -916,20 +895,20 @@ impl<'a> Tmkdir<'a> { /// Mkdir response #[derive(Clone, Debug)] pub(super) struct Rmkdir { - pub qid: Qid, + pub(super) qid: Qid, } /// Renameat request #[derive(Clone, Debug)] pub(super) struct Trenameat<'a> { - pub olddfid: u32, - pub oldname: FcallStr<'a>, - pub newdfid: u32, - pub newname: FcallStr<'a>, + olddfid: u32, + oldname: FcallStr<'a>, + newdfid: u32, + newname: FcallStr<'a>, } impl<'a> Trenameat<'a> { - pub fn clone_static(&'a self) -> Trenameat<'static> { + fn clone_static(&'a self) -> Trenameat<'static> { Trenameat { newdfid: self.newdfid, olddfid: self.olddfid, @@ -946,13 +925,13 @@ pub(super) struct Rrenameat {} /// Unlinkat request #[derive(Clone, Debug)] pub(super) struct Tunlinkat<'a> { - pub dfid: u32, - pub name: FcallStr<'a>, - pub flags: u32, + pub(super) dfid: u32, + pub(super) name: FcallStr<'a>, + pub(super) flags: u32, } impl<'a> Tunlinkat<'a> { - pub fn clone_static(&'a self) -> Tunlinkat<'static> { + fn clone_static(&'a self) -> Tunlinkat<'static> { Tunlinkat { dfid: self.dfid, flags: self.flags, @@ -968,14 +947,14 @@ pub(super) struct Runlinkat {} /// Auth request #[derive(Clone, Debug)] pub(super) struct Tauth<'a> { - pub afid: u32, - pub uname: FcallStr<'a>, - pub aname: FcallStr<'a>, - pub n_uname: u32, + afid: u32, + uname: FcallStr<'a>, + aname: FcallStr<'a>, + n_uname: u32, } impl<'a> Tauth<'a> { - pub fn clone_static(&'a self) -> Tauth<'static> { + fn clone_static(&'a self) -> Tauth<'static> { Tauth { afid: self.afid, n_uname: self.n_uname, @@ -988,18 +967,18 @@ impl<'a> Tauth<'a> { /// Auth response #[derive(Clone, Debug)] pub(super) struct Rauth { - pub aqid: Qid, + aqid: Qid, } /// Version request #[derive(Clone, Debug)] pub(super) struct Tversion<'a> { - pub msize: u32, - pub version: FcallStr<'a>, + pub(super) msize: u32, + pub(super) version: FcallStr<'a>, } impl<'a> Tversion<'a> { - pub fn clone_static(&'a self) -> Tversion<'static> { + fn clone_static(&'a self) -> Tversion<'static> { Tversion { msize: self.msize, version: self.version.clone_static(), @@ -1010,12 +989,12 @@ impl<'a> Tversion<'a> { /// Version response #[derive(Clone, Debug)] pub(super) struct Rversion<'a> { - pub msize: u32, - pub version: FcallStr<'a>, + pub(super) msize: u32, + pub(super) version: FcallStr<'a>, } impl<'a> Rversion<'a> { - pub fn clone_static(&'a self) -> Rversion<'static> { + fn clone_static(&'a self) -> Rversion<'static> { Rversion { msize: self.msize, version: self.version.clone_static(), @@ -1026,7 +1005,7 @@ impl<'a> Rversion<'a> { /// Flush request #[derive(Clone, Debug)] pub(super) struct Tflush { - pub oldtag: u16, + oldtag: u16, } /// Flush response @@ -1036,13 +1015,13 @@ pub(super) struct Rflush {} /// Walk request #[derive(Clone, Debug)] pub(super) struct Twalk<'a> { - pub fid: u32, - pub new_fid: u32, - pub wnames: Vec>, + pub(super) fid: u32, + pub(super) new_fid: u32, + pub(super) wnames: Vec>, } impl<'a> Twalk<'a> { - pub fn clone_static(&'a self) -> Twalk<'static> { + fn clone_static(&'a self) -> Twalk<'static> { Twalk { fid: self.fid, new_fid: self.new_fid, @@ -1054,25 +1033,25 @@ impl<'a> Twalk<'a> { /// Walk response #[derive(Clone, Debug)] pub(super) struct Rwalk { - pub wqids: Vec, + pub(super) wqids: Vec, } /// Read request #[derive(Clone, Debug)] pub(super) struct Tread { - pub fid: u32, - pub offset: u64, - pub count: u32, + pub(super) fid: u32, + pub(super) offset: u64, + pub(super) count: u32, } /// Read response #[derive(Clone, Debug)] pub(super) struct Rread<'a> { - pub data: Cow<'a, [u8]>, + pub(super) data: Cow<'a, [u8]>, } impl<'a> Rread<'a> { - pub fn clone_static(&'a self) -> Rread<'static> { + fn clone_static(&'a self) -> Rread<'static> { Rread { data: Cow::from(self.data.clone().into_owned()), } @@ -1082,13 +1061,13 @@ impl<'a> Rread<'a> { /// Write request #[derive(Clone, Debug)] pub(super) struct Twrite<'a> { - pub fid: u32, - pub offset: u64, - pub data: Cow<'a, [u8]>, + pub(super) fid: u32, + pub(super) offset: u64, + pub(super) data: Cow<'a, [u8]>, } impl<'a> Twrite<'a> { - pub fn clone_static(&'a self) -> Twrite<'static> { + fn clone_static(&'a self) -> Twrite<'static> { Twrite { fid: self.fid, offset: self.offset, @@ -1100,13 +1079,13 @@ impl<'a> Twrite<'a> { /// Write response #[derive(Clone, Debug)] pub(super) struct Rwrite { - pub count: u32, + pub(super) count: u32, } /// Clunk request #[derive(Clone, Debug)] pub(super) struct Tclunk { - pub fid: u32, + pub(super) fid: u32, } /// Clunk response @@ -1116,7 +1095,7 @@ pub(super) struct Rclunk {} /// Remove request #[derive(Clone, Debug)] pub(super) struct Tremove { - pub fid: u32, + pub(super) fid: u32, } /// Remove response @@ -1191,7 +1170,7 @@ pub(super) enum Fcall<'a> { impl Fcall<'_> { /// Create a static (owned) copy of this Fcall - pub fn clone_static(&self) -> Fcall<'static> { + pub(super) fn clone_static(&self) -> Fcall<'static> { match self { Fcall::Rlerror(v) => Fcall::Rlerror(v.clone()), Fcall::Tattach(v) => Fcall::Tattach(v.clone_static()), @@ -1446,22 +1425,24 @@ pub(super) struct TaggedFcall<'a> { impl<'a> TaggedFcall<'a> { /// Encode the message to a buffer - pub fn encode_to_buf(&self, buf: &mut Vec) -> Result<(), transport::WriteError> { + pub(super) fn encode_to_buf(self, buf: &mut Vec) -> Result<(), transport::WriteError> { + let TaggedFcall { tag, fcall } = self; + buf.clear(); buf.resize(4, 0); // Reserve space for size // Encode the message directly to the buffer (appending after the size field) - encode_fcall(buf, &self.tag, &self.fcall)?; + encode_fcall(buf, tag, fcall)?; // Write the size at the beginning - let size = buf.len() as u32; + let size = u32::try_from(buf.len()).expect("buffer length exceeds u32"); buf[0..4].copy_from_slice(&size.to_le_bytes()); Ok(()) } /// Decode a message from a buffer - pub fn decode(buf: &'a [u8]) -> Result, super::Error> { + pub(super) fn decode(buf: &'a [u8]) -> Result, super::Error> { if buf.len() < 7 { return Err(super::Error::InvalidResponse); } @@ -1492,63 +1473,66 @@ fn encode_u64(w: &mut W, v: u64) -> Result<(), transport::WriteError> } fn encode_str(w: &mut W, v: &FcallStr<'_>) -> Result<(), transport::WriteError> { - encode_u16(w, v.len() as u16)?; + encode_u16(w, u16::try_from(v.len()).expect("str length exceeds u16"))?; w.write_all(v.as_bytes()) } fn encode_data_buf(w: &mut W, v: &[u8]) -> Result<(), transport::WriteError> { - encode_u32(w, v.len() as u32)?; + encode_u32( + w, + u32::try_from(v.len()).expect("data buffer length exceeds u32"), + )?; w.write_all(v) } fn encode_vec_str(w: &mut W, v: &[FcallStr<'_>]) -> Result<(), transport::WriteError> { - encode_u16(w, v.len() as u16)?; + encode_u16(w, u16::try_from(v.len()).expect("vec length exceeds u16"))?; for s in v { encode_str(w, s)?; } Ok(()) } -fn encode_vec_qid(w: &mut W, v: &[Qid]) -> Result<(), transport::WriteError> { - encode_u16(w, v.len() as u16)?; +fn encode_vec_qid(w: &mut W, v: Vec) -> Result<(), transport::WriteError> { + encode_u16(w, u16::try_from(v.len()).expect("vec length exceeds u16"))?; for q in v { encode_qid(w, q)?; } Ok(()) } -fn encode_qidtype(w: &mut W, v: &QidType) -> Result<(), transport::WriteError> { +fn encode_qidtype(w: &mut W, v: QidType) -> Result<(), transport::WriteError> { encode_u8(w, v.bits()) } -fn encode_locktype(w: &mut W, v: &LockType) -> Result<(), transport::WriteError> { +fn encode_locktype(w: &mut W, v: LockType) -> Result<(), transport::WriteError> { encode_u8(w, v.bits()) } -fn encode_lockstatus(w: &mut W, v: &LockStatus) -> Result<(), transport::WriteError> { +fn encode_lockstatus(w: &mut W, v: LockStatus) -> Result<(), transport::WriteError> { encode_u8(w, v.bits()) } -fn encode_lockflag(w: &mut W, v: &LockFlag) -> Result<(), transport::WriteError> { +fn encode_lockflag(w: &mut W, v: LockFlag) -> Result<(), transport::WriteError> { encode_u32(w, v.bits()) } -fn encode_getattrmask(w: &mut W, v: &GetattrMask) -> Result<(), transport::WriteError> { +fn encode_getattrmask(w: &mut W, v: GetattrMask) -> Result<(), transport::WriteError> { encode_u64(w, v.bits()) } -fn encode_setattrmask(w: &mut W, v: &SetattrMask) -> Result<(), transport::WriteError> { +fn encode_setattrmask(w: &mut W, v: SetattrMask) -> Result<(), transport::WriteError> { encode_u32(w, v.bits()) } -fn encode_qid(w: &mut W, v: &Qid) -> Result<(), transport::WriteError> { - encode_qidtype(w, &v.typ)?; +fn encode_qid(w: &mut W, v: Qid) -> Result<(), transport::WriteError> { + encode_qidtype(w, v.typ)?; encode_u32(w, v.version)?; encode_u64(w, v.path)?; Ok(()) } -fn encode_statfs(w: &mut W, v: &Statfs) -> Result<(), transport::WriteError> { +fn encode_statfs(w: &mut W, v: Statfs) -> Result<(), transport::WriteError> { encode_u32(w, v.typ)?; encode_u32(w, v.bsize)?; encode_u64(w, v.blocks)?; @@ -1561,13 +1545,13 @@ fn encode_statfs(w: &mut W, v: &Statfs) -> Result<(), transport::Write Ok(()) } -fn encode_time(w: &mut W, v: &Time) -> Result<(), transport::WriteError> { +fn encode_time(w: &mut W, v: Time) -> Result<(), transport::WriteError> { encode_u64(w, v.sec)?; encode_u64(w, v.nsec)?; Ok(()) } -fn encode_stat(w: &mut W, v: &Stat) -> Result<(), transport::WriteError> { +fn encode_stat(w: &mut W, v: Stat) -> Result<(), transport::WriteError> { encode_u32(w, v.mode)?; encode_u32(w, v.uid)?; encode_u32(w, v.gid)?; @@ -1576,47 +1560,50 @@ fn encode_stat(w: &mut W, v: &Stat) -> Result<(), transport::WriteErro encode_u64(w, v.size)?; encode_u64(w, v.blksize)?; encode_u64(w, v.blocks)?; - encode_time(w, &v.atime)?; - encode_time(w, &v.mtime)?; - encode_time(w, &v.ctime)?; - encode_time(w, &v.btime)?; + encode_time(w, v.atime)?; + encode_time(w, v.mtime)?; + encode_time(w, v.ctime)?; + encode_time(w, v.btime)?; encode_u64(w, v.generation)?; encode_u64(w, v.data_version)?; Ok(()) } -fn encode_setattr(w: &mut W, v: &SetAttr) -> Result<(), transport::WriteError> { +fn encode_setattr(w: &mut W, v: SetAttr) -> Result<(), transport::WriteError> { encode_u32(w, v.mode)?; encode_u32(w, v.uid)?; encode_u32(w, v.gid)?; encode_u64(w, v.size)?; - encode_time(w, &v.atime)?; - encode_time(w, &v.mtime)?; + encode_time(w, v.atime)?; + encode_time(w, v.mtime)?; Ok(()) } fn encode_direntrydata( w: &mut W, - v: &DirEntryData<'_>, + v: DirEntryData<'_>, ) -> Result<(), transport::WriteError> { - encode_u32(w, v.size() as u32)?; - for e in &v.data { + encode_u32( + w, + u32::try_from(v.size()).expect("direntrydata size exceeds u32"), + )?; + for e in v.data { encode_direntry(w, e)?; } Ok(()) } -fn encode_direntry(w: &mut W, v: &DirEntry<'_>) -> Result<(), transport::WriteError> { - encode_qid(w, &v.qid)?; +fn encode_direntry(w: &mut W, v: DirEntry<'_>) -> Result<(), transport::WriteError> { + encode_qid(w, v.qid)?; encode_u64(w, v.offset)?; encode_u8(w, v.typ)?; encode_str(w, &v.name)?; Ok(()) } -fn encode_flock(w: &mut W, v: &Flock<'_>) -> Result<(), transport::WriteError> { - encode_locktype(w, &v.typ)?; - encode_lockflag(w, &v.flags)?; +fn encode_flock(w: &mut W, v: Flock<'_>) -> Result<(), transport::WriteError> { + encode_locktype(w, v.typ)?; + encode_lockflag(w, v.flags)?; encode_u64(w, v.start)?; encode_u64(w, v.length)?; encode_u32(w, v.proc_id)?; @@ -1624,8 +1611,8 @@ fn encode_flock(w: &mut W, v: &Flock<'_>) -> Result<(), transport::Wri Ok(()) } -fn encode_getlock(w: &mut W, v: &Getlock<'_>) -> Result<(), transport::WriteError> { - encode_locktype(w, &v.typ)?; +fn encode_getlock(w: &mut W, v: Getlock<'_>) -> Result<(), transport::WriteError> { + encode_locktype(w, v.typ)?; encode_u64(w, v.start)?; encode_u64(w, v.length)?; encode_u32(w, v.proc_id)?; @@ -1635,18 +1622,18 @@ fn encode_getlock(w: &mut W, v: &Getlock<'_>) -> Result<(), transport: fn encode_fcall( w: &mut W, - tag: &u16, - fcall: &Fcall<'_>, + tag: u16, + fcall: Fcall<'_>, ) -> Result<(), transport::WriteError> { match fcall { Fcall::Rlerror(v) => { encode_u8(w, FcallType::Rlerror as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.ecode)?; } Fcall::Tattach(v) => { encode_u8(w, FcallType::Tattach as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_u32(w, v.afid)?; encode_str(w, &v.uname)?; @@ -1655,34 +1642,34 @@ fn encode_fcall( } Fcall::Rattach(v) => { encode_u8(w, FcallType::Rattach as u8)?; - encode_u16(w, *tag)?; - encode_qid(w, &v.qid)?; + encode_u16(w, tag)?; + encode_qid(w, v.qid)?; } Fcall::Tstatfs(v) => { encode_u8(w, FcallType::Tstatfs as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; } Fcall::Rstatfs(v) => { encode_u8(w, FcallType::Rstatfs as u8)?; - encode_u16(w, *tag)?; - encode_statfs(w, &v.statfs)?; + encode_u16(w, tag)?; + encode_statfs(w, v.statfs)?; } Fcall::Tlopen(v) => { encode_u8(w, FcallType::Tlopen as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_u32(w, v.flags.bits())?; } Fcall::Rlopen(v) => { encode_u8(w, FcallType::Rlopen as u8)?; - encode_u16(w, *tag)?; - encode_qid(w, &v.qid)?; + encode_u16(w, tag)?; + encode_qid(w, v.qid)?; encode_u32(w, v.iounit)?; } Fcall::Tlcreate(v) => { encode_u8(w, FcallType::Tlcreate as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_str(w, &v.name)?; encode_u32(w, v.flags.bits())?; @@ -1691,13 +1678,13 @@ fn encode_fcall( } Fcall::Rlcreate(v) => { encode_u8(w, FcallType::Rlcreate as u8)?; - encode_u16(w, *tag)?; - encode_qid(w, &v.qid)?; + encode_u16(w, tag)?; + encode_qid(w, v.qid)?; encode_u32(w, v.iounit)?; } Fcall::Tsymlink(v) => { encode_u8(w, FcallType::Tsymlink as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_str(w, &v.name)?; encode_str(w, &v.symtgt)?; @@ -1705,12 +1692,12 @@ fn encode_fcall( } Fcall::Rsymlink(v) => { encode_u8(w, FcallType::Rsymlink as u8)?; - encode_u16(w, *tag)?; - encode_qid(w, &v.qid)?; + encode_u16(w, tag)?; + encode_qid(w, v.qid)?; } Fcall::Tmknod(v) => { encode_u8(w, FcallType::Tmknod as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.dfid)?; encode_str(w, &v.name)?; encode_u32(w, v.mode)?; @@ -1720,69 +1707,69 @@ fn encode_fcall( } Fcall::Rmknod(v) => { encode_u8(w, FcallType::Rmknod as u8)?; - encode_u16(w, *tag)?; - encode_qid(w, &v.qid)?; + encode_u16(w, tag)?; + encode_qid(w, v.qid)?; } Fcall::Trename(v) => { encode_u8(w, FcallType::Trename as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_u32(w, v.dfid)?; encode_str(w, &v.name)?; } Fcall::Rrename(_) => { encode_u8(w, FcallType::Rrename as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; } Fcall::Treadlink(v) => { encode_u8(w, FcallType::Treadlink as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; } Fcall::Rreadlink(v) => { encode_u8(w, FcallType::Rreadlink as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_str(w, &v.target)?; } Fcall::Tgetattr(v) => { encode_u8(w, FcallType::Tgetattr as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; - encode_getattrmask(w, &v.req_mask)?; + encode_getattrmask(w, v.req_mask)?; } Fcall::Rgetattr(v) => { encode_u8(w, FcallType::Rgetattr as u8)?; - encode_u16(w, *tag)?; - encode_getattrmask(w, &v.valid)?; - encode_qid(w, &v.qid)?; - encode_stat(w, &v.stat)?; + encode_u16(w, tag)?; + encode_getattrmask(w, v.valid)?; + encode_qid(w, v.qid)?; + encode_stat(w, v.stat)?; } Fcall::Tsetattr(v) => { encode_u8(w, FcallType::Tsetattr as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; - encode_setattrmask(w, &v.valid)?; - encode_setattr(w, &v.stat)?; + encode_setattrmask(w, v.valid)?; + encode_setattr(w, v.stat)?; } Fcall::Rsetattr(_) => { encode_u8(w, FcallType::Rsetattr as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; } Fcall::Txattrwalk(v) => { encode_u8(w, FcallType::Txattrwalk as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_u32(w, v.new_fid)?; encode_str(w, &v.name)?; } Fcall::Rxattrwalk(v) => { encode_u8(w, FcallType::Rxattrwalk as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u64(w, v.size)?; } Fcall::Txattrcreate(v) => { encode_u8(w, FcallType::Txattrcreate as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_str(w, &v.name)?; encode_u64(w, v.attr_size)?; @@ -1790,66 +1777,66 @@ fn encode_fcall( } Fcall::Rxattrcreate(_) => { encode_u8(w, FcallType::Rxattrcreate as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; } Fcall::Treaddir(v) => { encode_u8(w, FcallType::Treaddir as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_u64(w, v.offset)?; encode_u32(w, v.count)?; } Fcall::Rreaddir(v) => { encode_u8(w, FcallType::Rreaddir as u8)?; - encode_u16(w, *tag)?; - encode_direntrydata(w, &v.data)?; + encode_u16(w, tag)?; + encode_direntrydata(w, v.data)?; } Fcall::Tfsync(v) => { encode_u8(w, FcallType::Tfsync as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_u32(w, v.datasync)?; } Fcall::Rfsync(_) => { encode_u8(w, FcallType::Rfsync as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; } Fcall::Tlock(v) => { encode_u8(w, FcallType::Tlock as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; - encode_flock(w, &v.flock)?; + encode_flock(w, v.flock)?; } Fcall::Rlock(v) => { encode_u8(w, FcallType::Rlock as u8)?; - encode_u16(w, *tag)?; - encode_lockstatus(w, &v.status)?; + encode_u16(w, tag)?; + encode_lockstatus(w, v.status)?; } Fcall::Tgetlock(v) => { encode_u8(w, FcallType::Tgetlock as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; - encode_getlock(w, &v.flock)?; + encode_getlock(w, v.flock)?; } Fcall::Rgetlock(v) => { encode_u8(w, FcallType::Rgetlock as u8)?; - encode_u16(w, *tag)?; - encode_getlock(w, &v.flock)?; + encode_u16(w, tag)?; + encode_getlock(w, v.flock)?; } Fcall::Tlink(v) => { encode_u8(w, FcallType::Tlink as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.dfid)?; encode_u32(w, v.fid)?; encode_str(w, &v.name)?; } Fcall::Rlink(_) => { encode_u8(w, FcallType::Rlink as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; } Fcall::Tmkdir(v) => { encode_u8(w, FcallType::Tmkdir as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.dfid)?; encode_str(w, &v.name)?; encode_u32(w, v.mode)?; @@ -1857,12 +1844,12 @@ fn encode_fcall( } Fcall::Rmkdir(v) => { encode_u8(w, FcallType::Rmkdir as u8)?; - encode_u16(w, *tag)?; - encode_qid(w, &v.qid)?; + encode_u16(w, tag)?; + encode_qid(w, v.qid)?; } Fcall::Trenameat(v) => { encode_u8(w, FcallType::Trenameat as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.olddfid)?; encode_str(w, &v.oldname)?; encode_u32(w, v.newdfid)?; @@ -1870,22 +1857,22 @@ fn encode_fcall( } Fcall::Rrenameat(_) => { encode_u8(w, FcallType::Rrenameat as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; } Fcall::Tunlinkat(v) => { encode_u8(w, FcallType::Tunlinkat as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.dfid)?; encode_str(w, &v.name)?; encode_u32(w, v.flags)?; } Fcall::Runlinkat(_) => { encode_u8(w, FcallType::Runlinkat as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; } Fcall::Tauth(v) => { encode_u8(w, FcallType::Tauth as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.afid)?; encode_str(w, &v.uname)?; encode_str(w, &v.aname)?; @@ -1893,83 +1880,83 @@ fn encode_fcall( } Fcall::Rauth(v) => { encode_u8(w, FcallType::Rauth as u8)?; - encode_u16(w, *tag)?; - encode_qid(w, &v.aqid)?; + encode_u16(w, tag)?; + encode_qid(w, v.aqid)?; } Fcall::Tversion(v) => { encode_u8(w, FcallType::Tversion as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.msize)?; encode_str(w, &v.version)?; } Fcall::Rversion(v) => { encode_u8(w, FcallType::Rversion as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.msize)?; encode_str(w, &v.version)?; } Fcall::Tflush(v) => { encode_u8(w, FcallType::Tflush as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u16(w, v.oldtag)?; } Fcall::Rflush(_) => { encode_u8(w, FcallType::Rflush as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; } Fcall::Twalk(v) => { encode_u8(w, FcallType::Twalk as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_u32(w, v.new_fid)?; encode_vec_str(w, &v.wnames)?; } Fcall::Rwalk(v) => { encode_u8(w, FcallType::Rwalk as u8)?; - encode_u16(w, *tag)?; - encode_vec_qid(w, &v.wqids)?; + encode_u16(w, tag)?; + encode_vec_qid(w, v.wqids)?; } Fcall::Tread(v) => { encode_u8(w, FcallType::Tread as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_u64(w, v.offset)?; encode_u32(w, v.count)?; } Fcall::Rread(v) => { encode_u8(w, FcallType::Rread as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_data_buf(w, &v.data)?; } Fcall::Twrite(v) => { encode_u8(w, FcallType::Twrite as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; encode_u64(w, v.offset)?; encode_data_buf(w, &v.data)?; } Fcall::Rwrite(v) => { encode_u8(w, FcallType::Rwrite as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.count)?; } Fcall::Tclunk(v) => { encode_u8(w, FcallType::Tclunk as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; } Fcall::Rclunk(_) => { encode_u8(w, FcallType::Rclunk as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; } Fcall::Tremove(v) => { encode_u8(w, FcallType::Tremove as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; encode_u32(w, v.fid)?; } Fcall::Rremove(_) => { encode_u8(w, FcallType::Rremove as u8)?; - encode_u16(w, *tag)?; + encode_u16(w, tag)?; } } Ok(()) diff --git a/litebox/src/fs/nine_p/mod.rs b/litebox/src/fs/nine_p/mod.rs index 54bfe67b2..c20b2c1f9 100644 --- a/litebox/src/fs/nine_p/mod.rs +++ b/litebox/src/fs/nine_p/mod.rs @@ -18,7 +18,7 @@ use alloc::string::String; use alloc::vec::Vec; use core::num::NonZeroUsize; -use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use thiserror::Error; @@ -29,6 +29,7 @@ use crate::fs::errors::{ }; use crate::fs::nine_p::fcall::Rlerror; use crate::path::Arg; +use crate::platform::DebugLogProvider; use crate::{LiteBox, sync}; mod client; @@ -446,17 +447,19 @@ impl - super::private::Sealed for FileSystem +impl< + Platform: sync::RawSyncPrimitivesProvider + DebugLogProvider, + T: transport::Read + transport::Write, +> super::private::Sealed for FileSystem { } -impl - super::FileSystem for FileSystem +impl< + Platform: sync::RawSyncPrimitivesProvider + DebugLogProvider, + T: transport::Read + transport::Write, +> super::FileSystem for FileSystem { + #[allow(clippy::similar_names)] fn open( &self, path: impl crate::path::Arg, @@ -577,7 +587,7 @@ impl, ) -> Result { - let descriptor_table = self.litebox.descriptor_table(); - let entry = descriptor_table - .get_entry(fd) + // Extract fid and current offset, releasing the descriptor table lock + // before performing potentially blocking I/O. + let (fid, current_offset) = self + .litebox + .descriptor_table() + .with_entry(fd, |desc| { + (desc.entry.fid, desc.entry.offset.load(Ordering::SeqCst)) + }) .ok_or(super::errors::ReadError::ClosedFd)?; - let desc = &entry.entry; - // Determine offset to use let read_offset = match offset { - Some(o) => o as u64, - None => desc.offset.load(Ordering::SeqCst), + Some(o) => o, + None => current_offset, }; - // TODO: read might be blocking while holding up the descriptor table lock. - // We should consider releasing the lock before doing the read. - let bytes_read = self.client.read(desc.fid, read_offset, buf)?; + let bytes_read = self.client.read(fid, read_offset as u64, buf)?; // Update offset if not using explicit offset if offset.is_none() { - desc.offset.fetch_add(bytes_read as u64, Ordering::SeqCst); + self.litebox.descriptor_table().with_entry(fd, |desc| { + desc.entry.offset.fetch_add(bytes_read, Ordering::SeqCst); + }); } Ok(bytes_read) @@ -629,29 +642,31 @@ impl, ) -> Result { - let descriptor_table = self.litebox.descriptor_table(); - let entry = descriptor_table - .get_entry(fd) + // Extract fid and current offset, releasing the descriptor table lock + // before performing potentially blocking I/O. + let (fid, current_offset) = self + .litebox + .descriptor_table() + .with_entry(fd, |desc| { + (desc.entry.fid, desc.entry.offset.load(Ordering::SeqCst)) + }) .ok_or(super::errors::WriteError::ClosedFd)?; - let desc = &entry.entry; - // Determine offset to use let write_offset = match offset { - Some(o) => o as u64, - None => desc.offset.load(Ordering::SeqCst), + Some(o) => o, + None => current_offset, }; - // TODO: write might be blocking while holding up the descriptor table lock. - // We should consider releasing the lock before doing the write. - let bytes_written = self.client.write(desc.fid, write_offset, buf)?; + let bytes_written = self.client.write(fid, write_offset as u64, buf)?; // Update offset if not using explicit offset if offset.is_none() { - desc.offset - .fetch_add(bytes_written as u64, Ordering::SeqCst); + self.litebox.descriptor_table().with_entry(fd, |desc| { + desc.entry.offset.fetch_add(bytes_written, Ordering::SeqCst); + }); } - Ok(bytes_written as usize) + Ok(bytes_written) } fn seek( @@ -660,27 +675,32 @@ impl Result { - let descriptor_table = self.litebox.descriptor_table(); - let entry = descriptor_table.get_entry(fd).ok_or(SeekError::ClosedFd)?; - let desc = &entry.entry; - - let current_offset = desc.offset.load(Ordering::SeqCst); + // Extract fid and current offset, releasing the descriptor table lock + // before performing potentially blocking I/O (getattr for SeekWhence::RelativeToEnd). + let (fid, current_offset) = self + .litebox + .descriptor_table() + .with_entry(fd, |desc| { + (desc.entry.fid, desc.entry.offset.load(Ordering::SeqCst)) + }) + .ok_or(SeekError::ClosedFd)?; let base = match whence { super::SeekWhence::RelativeToBeginning => 0, super::SeekWhence::RelativeToCurrentOffset => current_offset, super::SeekWhence::RelativeToEnd => { - // Need to get file size - let attr = self.client.getattr(desc.fid, fcall::GetattrMask::SIZE)?; - attr.stat.size + let attr = self.client.getattr(fid, fcall::GetattrMask::SIZE)?; + usize::try_from(attr.stat.size).expect("file size exceeds usize") } }; let new_offset = base - .checked_add_signed(offset as i64) + .checked_add_signed(offset) .ok_or(SeekError::InvalidOffset)?; - desc.offset.store(new_offset, Ordering::SeqCst); - Ok(new_offset as usize) + self.litebox.descriptor_table().with_entry(fd, |desc| { + desc.entry.offset.store(new_offset, Ordering::SeqCst); + }); + Ok(new_offset) } fn truncate( @@ -689,13 +709,15 @@ impl Result<(), super::errors::TruncateError> { - let descriptor_table = self.litebox.descriptor_table(); - let entry = descriptor_table - .get_entry(fd) + // Extract fid and qid, releasing the descriptor table lock + // before performing potentially blocking I/O. + let (fid, qid) = self + .litebox + .descriptor_table() + .with_entry(fd, |desc| (desc.entry.fid, desc.entry.qid)) .ok_or(super::errors::TruncateError::ClosedFd)?; - let desc = &entry.entry; - if desc.qid.typ.contains(fcall::QidType::DIR) { + if qid.typ.contains(fcall::QidType::DIR) { return Err(super::errors::TruncateError::IsDirectory); } @@ -708,11 +730,12 @@ impl { valid |= fcall::SetattrMask::UID; - u as u32 + u32::from(u) } None => 0, }; let gid = match group { Some(g) => { valid |= fcall::SetattrMask::GID; - g as u32 + u32::from(g) } None => 0, }; @@ -805,19 +828,20 @@ impl, ) -> Result, super::errors::ReadDirError> { - let descriptor_table = self.litebox.descriptor_table(); - let entry = descriptor_table - .get_entry(fd) + // Extract fid and qid, releasing the descriptor table lock + // before performing potentially blocking I/O. + let (fid, qid) = self + .litebox + .descriptor_table() + .with_entry(fd, |desc| (desc.entry.fid, desc.entry.qid)) .ok_or(super::errors::ReadDirError::ClosedFd)?; - let desc = &entry.entry; - if !desc.qid.typ.contains(fcall::QidType::DIR) { + if !qid.typ.contains(fcall::QidType::DIR) { return Err(super::errors::ReadDirError::NotADirectory); } - // TODO: read_dir might be blocking while holding up the descriptor table lock. - // We should consider releasing the lock before doing the read_dir. - let entries = self.client.readdir_all(desc.fid)?; + // Perform blocking I/O without holding any locks. + let entries = self.client.readdir_all(fid)?; let dir_entries: Vec = entries .into_iter() @@ -831,9 +855,16 @@ impl, ) -> Result { - let descriptor_table = self.litebox.descriptor_table(); - let entry = descriptor_table - .get_entry(fd) + // Extract fid, releasing the descriptor table lock + // before performing potentially blocking I/O. + let fid = self + .litebox + .descriptor_table() + .with_entry(fd, |desc| desc.entry.fid) .ok_or(super::errors::FileStatusError::ClosedFd)?; - let desc = &entry.entry; - let attr = self.client.getattr(desc.fid, fcall::GetattrMask::ALL)?; + // Perform blocking I/O without holding any locks. + let attr = self.client.getattr(fid, fcall::GetattrMask::ALL)?; Ok(Self::rgetattr_to_file_status(&attr)) } @@ -880,11 +914,7 @@ struct Descriptor { /// The 9P fid for this file fid: fcall::Fid, /// Current file offset (9P doesn't track this server-side) - offset: AtomicU64, - /// Whether this file is opened for reading - // read_allowed: bool, - /// Whether this file is opened for writing - // write_allowed: bool, + offset: AtomicUsize, /// The qid of the file (contains type and unique ID) qid: fcall::Qid, } diff --git a/litebox/src/fs/nine_p/transport.rs b/litebox/src/fs/nine_p/transport.rs index 7e40988f3..acef56bf1 100644 --- a/litebox/src/fs/nine_p/transport.rs +++ b/litebox/src/fs/nine_p/transport.rs @@ -57,7 +57,7 @@ pub trait Write { pub(super) fn write_message( w: &mut W, buf: &mut Vec, - fcall: &super::fcall::TaggedFcall<'_>, + fcall: super::fcall::TaggedFcall<'_>, ) -> Result<(), WriteError> { fcall.encode_to_buf(buf)?; w.write_all(&buf[..]) diff --git a/litebox_shim_linux/Cargo.toml b/litebox_shim_linux/Cargo.toml index 6cf93b20c..94d889a7f 100644 --- a/litebox_shim_linux/Cargo.toml +++ b/litebox_shim_linux/Cargo.toml @@ -26,6 +26,7 @@ platform_linux_snp = ["litebox_platform_multiplex/platform_linux_snp"] [dev-dependencies] spin = { version = "0.9.8", default-features = false, features = ["spin_mutex"] } libc = "0.2.177" +tempfile = "3" [lints] workspace = true diff --git a/litebox_shim_linux/src/lib.rs b/litebox_shim_linux/src/lib.rs index f4dccc729..56bee5424 100644 --- a/litebox_shim_linux/src/lib.rs +++ b/litebox_shim_linux/src/lib.rs @@ -43,6 +43,7 @@ macro_rules! log_unsupported { pub(crate) mod channel; pub mod loader; +pub mod nine_p; pub(crate) mod stdio; pub mod syscalls; mod wait; diff --git a/litebox_shim_linux/src/nine_p.rs b/litebox_shim_linux/src/nine_p.rs new file mode 100644 index 000000000..cc9420cc8 --- /dev/null +++ b/litebox_shim_linux/src/nine_p.rs @@ -0,0 +1,443 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +//! 9P transport implementation using litebox's syscall-level network APIs. +//! +//! This module provides a [`ShimTransport`] that implements the 9P transport traits +//! (`litebox::fs::nine_p::transport::{Read, Write}`) using the `Task::do_*` syscall +//! methods (e.g., `do_socket`, `do_connect`, `do_sendto`, `do_recvfrom`). + +use litebox::fs::nine_p::transport; +use litebox_common_linux::{ReceiveFlags, SendFlags}; + +use crate::Task; + +/// A 9P transport backed by litebox's syscall-level socket APIs. +/// +/// This transport wraps a connected TCP socket (identified by a file descriptor) +/// and implements blocking reads and writes using [`Task::do_recvfrom`] and +/// [`Task::do_sendto`], which handle wait/poll internally. +pub struct ShimTransport<'a> { + task: &'a Task, + sockfd: u32, +} + +impl Drop for ShimTransport<'_> { + fn drop(&mut self) { + let _ = self.task.sys_close(self.sockfd.cast_signed()); + } +} + +impl transport::Read for ShimTransport<'_> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.task + .do_recvfrom(self.sockfd, buf, ReceiveFlags::empty(), None) + .map_err(|_| transport::ReadError) + } +} + +impl transport::Write for ShimTransport<'_> { + fn write(&mut self, buf: &[u8]) -> Result { + self.task + .do_sendto(self.sockfd, buf, SendFlags::empty(), None) + .map_err(|_| transport::WriteError) + } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + extern crate std; + + use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + use std::net::TcpListener; + use std::path::Path; + + use litebox::fs::nine_p; + use litebox::fs::{FileSystem as _, Mode, OFlags}; + use litebox_common_linux::errno::Errno; + use litebox_common_linux::{AddressFamily, SockFlags, SockType}; + + use crate::syscalls::net::SocketAddress; + use crate::syscalls::tests::init_platform; + + use super::*; + + const TUN_DEVICE_NAME: &str = "tun99"; + + // ----------------------------------------------------------------------- + // diod server management (mirrors litebox/src/fs/nine_p/tests.rs) + // ----------------------------------------------------------------------- + + fn find_free_port() -> u16 { + let listener = TcpListener::bind("127.0.0.1:0").expect("failed to bind to port 0"); + listener.local_addr().unwrap().port() + } + + struct DiodServer { + child: std::process::Child, + port: u16, + _export_dir: tempfile::TempDir, + export_path: std::path::PathBuf, + } + + impl DiodServer { + fn start() -> Self { + let export_dir = tempfile::tempdir().expect("failed to create temp dir"); + let export_path = export_dir.path().to_path_buf(); + let port = find_free_port(); + + let child = std::process::Command::new("diod") + .args([ + "--foreground", + "--no-auth", + "--export", + export_dir.path().to_str().unwrap(), + "--listen", + &std::format!("0.0.0.0:{port}"), + "--nwthreads", + "1", + ]) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("failed to start diod – is it installed? (`apt install diod`)"); + + // Give the server time to start listening. + std::thread::sleep(std::time::Duration::from_millis(500)); + + Self { + child, + port, + _export_dir: export_dir, + export_path, + } + } + + fn export_path(&self) -> &Path { + &self.export_path + } + } + + impl Drop for DiodServer { + fn drop(&mut self) { + let _ = self.child.kill(); + let _ = self.child.wait(); + } + } + + // ----------------------------------------------------------------------- + // Test helpers + // ----------------------------------------------------------------------- + + /// Helper to create a `SocketAddr` for connection. + fn socket_addr(ip: [u8; 4], port: u16) -> SocketAddr { + SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3]), + port, + )) + } + + /// Connect to a 9P server and return a [`ShimTransport`]. + /// + /// This function creates a TCP socket via [`Task::do_socket`], connects it to the + /// given address via [`Task::do_connect`], and returns a transport suitable for use + /// with `litebox::fs::nine_p::FileSystem`. + /// + /// # Arguments + /// * `task` - The task whose syscall APIs will be used for socket operations + /// * `addr` - The socket address of the 9P server + fn connect(task: &Task, addr: core::net::SocketAddr) -> Result, Errno> { + let sockfd = + task.do_socket(AddressFamily::INET, SockType::Stream, SockFlags::empty(), 0)?; + + task.do_connect(sockfd, SocketAddress::Inet(addr))?; + + Ok(ShimTransport { task, sockfd }) + } + + fn connect_9p<'a>( + task: &'a crate::Task, + server: &DiodServer, + ) -> nine_p::FileSystem> { + // The diod server is reachable at the gateway address (10.0.0.1) from the shim's + // network perspective, since the TUN device bridges to the host. + let addr = socket_addr([10, 0, 0, 1], server.port); + let transport = + connect(task, addr).expect("failed to connect to 9P server via shim network"); + + let aname = server.export_path().to_str().unwrap(); + let username = std::env::var("USER") + .or_else(|_| std::env::var("LOGNAME")) + .unwrap_or_else(|_| std::string::String::from("nobody")); + + nine_p::FileSystem::new(&task.global.litebox, transport, 65536, &username, aname) + .expect("failed to create 9P filesystem") + } + + // ----------------------------------------------------------------------- + // Tests (require TUN device + diod) + // ----------------------------------------------------------------------- + + #[test] + fn test_tun_nine_p_create_and_read_file() { + let task = init_platform(Some(TUN_DEVICE_NAME)); + + let server = DiodServer::start(); + let fs = connect_9p(&task, &server); + + // Create a file and write to it. + let fd = fs + .open("/hello.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) + .expect("failed to create file via 9P"); + + let data = b"Hello from litebox shim 9P!"; + let written = fs.write(&fd, data, None).expect("failed to write via 9P"); + assert_eq!(written, data.len()); + fs.close(&fd).expect("failed to close file"); + + // Verify on host. + let host_path = server.export_path().join("hello.txt"); + assert!(host_path.exists(), "file should exist on host"); + let host_content = std::fs::read_to_string(&host_path).unwrap(); + assert_eq!(host_content, "Hello from litebox shim 9P!"); + + // Read back through 9P. + let fd = fs + .open("/hello.txt", OFlags::RDONLY, Mode::empty()) + .expect("failed to open file for reading"); + + let mut buf = alloc::vec![0u8; 256]; + let n = fs.read(&fd, &mut buf, None).expect("failed to read via 9P"); + assert_eq!(&buf[..n], data); + fs.close(&fd).expect("failed to close file"); + } + + #[test] + fn test_tun_nine_p_mkdir_and_readdir() { + let task = init_platform(Some(TUN_DEVICE_NAME)); + + let server = DiodServer::start(); + let fs = connect_9p(&task, &server); + + // Create directories. + fs.mkdir("/subdir", Mode::RWXU) + .expect("failed to mkdir via 9P"); + fs.mkdir("/subdir/nested", Mode::RWXU) + .expect("failed to mkdir nested via 9P"); + + // Create a file inside the subdirectory. + let fd = fs + .open( + "/subdir/file.txt", + OFlags::CREAT | OFlags::WRONLY, + Mode::RWXU, + ) + .expect("failed to create file in subdir"); + fs.write(&fd, b"nested content", None).unwrap(); + fs.close(&fd).unwrap(); + + // Read the root directory. + let fd = fs + .open("/", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()) + .expect("failed to open root dir"); + let entries = fs.read_dir(&fd).expect("failed to readdir root"); + fs.close(&fd).unwrap(); + + let names: alloc::vec::Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect(); + assert!( + names.contains(&"subdir"), + "root should contain 'subdir', got: {names:?}" + ); + + // Read the subdirectory. + let fd = fs + .open("/subdir", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()) + .expect("failed to open subdir"); + let entries = fs.read_dir(&fd).expect("failed to readdir subdir"); + fs.close(&fd).unwrap(); + + let names: alloc::vec::Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect(); + assert!( + names.contains(&"nested"), + "subdir should contain 'nested', got: {names:?}" + ); + assert!( + names.contains(&"file.txt"), + "subdir should contain 'file.txt', got: {names:?}" + ); + } + + #[test] + fn test_tun_nine_p_unlink_and_rmdir() { + let task = init_platform(Some(TUN_DEVICE_NAME)); + + let server = DiodServer::start(); + let fs = connect_9p(&task, &server); + + // Create a file, then delete it. + let fd = fs + .open("/to_delete.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) + .expect("failed to create file"); + fs.close(&fd).unwrap(); + + fs.unlink("/to_delete.txt") + .expect("failed to unlink file via 9P"); + + assert!( + fs.open("/to_delete.txt", OFlags::RDONLY, Mode::empty()) + .is_err(), + "file should no longer exist" + ); + + // Create a directory, then remove it. + fs.mkdir("/to_remove", Mode::RWXU).expect("failed to mkdir"); + fs.rmdir("/to_remove").expect("failed to rmdir via 9P"); + + assert!( + !server.export_path().join("to_remove").exists(), + "directory should no longer exist on host" + ); + } + + #[test] + fn test_tun_nine_p_file_status() { + let task = init_platform(Some(TUN_DEVICE_NAME)); + + let server = DiodServer::start(); + let fs = connect_9p(&task, &server); + + // Create a file with known content. + let fd = fs + .open( + "/status_test.txt", + OFlags::CREAT | OFlags::WRONLY, + Mode::RWXU, + ) + .expect("failed to create file"); + let data = b"1234567890"; + fs.write(&fd, data, None).unwrap(); + fs.close(&fd).unwrap(); + + // Check file_status via path. + let status = fs + .file_status("/status_test.txt") + .expect("failed to stat file"); + assert_eq!( + status.file_type, + litebox::fs::FileType::RegularFile, + "should be a regular file" + ); + assert_eq!(status.size, 10, "file size should be 10 bytes"); + + // Check directory status. + fs.mkdir("/stat_dir", Mode::RWXU).unwrap(); + let status = fs.file_status("/stat_dir").expect("failed to stat dir"); + assert_eq!( + status.file_type, + litebox::fs::FileType::Directory, + "should be a directory" + ); + } + + #[test] + fn test_tun_nine_p_seek_and_partial_read() { + let task = init_platform(Some(TUN_DEVICE_NAME)); + + let server = DiodServer::start(); + let fs = connect_9p(&task, &server); + + // Write a file with known content. + let fd = fs + .open("/seek_test.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) + .expect("failed to create file"); + fs.write(&fd, b"ABCDEFGHIJ", None).unwrap(); + fs.close(&fd).unwrap(); + + // Open for reading and seek. + let fd = fs + .open("/seek_test.txt", OFlags::RDONLY, Mode::empty()) + .expect("failed to open file for reading"); + + let pos = fs + .seek(&fd, 5, litebox::fs::SeekWhence::RelativeToBeginning) + .expect("failed to seek"); + assert_eq!(pos, 5); + + let mut buf = alloc::vec![0u8; 10]; + let n = fs.read(&fd, &mut buf, None).expect("failed to read"); + assert_eq!(&buf[..n], b"FGHIJ"); + + fs.close(&fd).unwrap(); + } + + #[test] + fn test_tun_nine_p_truncate() { + let task = init_platform(Some(TUN_DEVICE_NAME)); + + let server = DiodServer::start(); + let fs = connect_9p(&task, &server); + + // Write a file. + let fd = fs + .open("/trunc_test.txt", OFlags::CREAT | OFlags::RDWR, Mode::RWXU) + .expect("failed to create file"); + fs.write(&fd, b"Hello, World!", None).unwrap(); + + // Truncate to 5 bytes. + fs.truncate(&fd, 5, true) + .expect("failed to truncate via 9P"); + fs.close(&fd).unwrap(); + + // Verify on host. + let content = std::fs::read_to_string(server.export_path().join("trunc_test.txt")).unwrap(); + assert_eq!(content, "Hello"); + } + + #[test] + fn test_tun_nine_p_host_files_visible() { + let task = init_platform(Some(TUN_DEVICE_NAME)); + + let server = DiodServer::start(); + + // Pre-populate files on the host side. + std::fs::write(server.export_path().join("host_file.txt"), "from host").unwrap(); + std::fs::create_dir(server.export_path().join("host_dir")).unwrap(); + std::fs::write( + server.export_path().join("host_dir/inner.txt"), + "inner content", + ) + .unwrap(); + + let fs = connect_9p(&task, &server); + + // Read file created on the host through 9P. + let fd = fs + .open("/host_file.txt", OFlags::RDONLY, Mode::empty()) + .expect("failed to open host file via 9P"); + let mut buf = alloc::vec![0u8; 256]; + let n = fs.read(&fd, &mut buf, None).unwrap(); + assert_eq!(&buf[..n], b"from host"); + fs.close(&fd).unwrap(); + + // List host directory through 9P. + let fd = fs + .open( + "/host_dir", + OFlags::RDONLY | OFlags::DIRECTORY, + Mode::empty(), + ) + .expect("failed to open host dir via 9P"); + let entries = fs.read_dir(&fd).unwrap(); + fs.close(&fd).unwrap(); + + let names: alloc::vec::Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect(); + assert!( + names.contains(&"inner.txt"), + "host_dir should contain 'inner.txt', got: {names:?}" + ); + } +} diff --git a/litebox_shim_linux/src/syscalls/net.rs b/litebox_shim_linux/src/syscalls/net.rs index 2617afd5a..8450c6b98 100644 --- a/litebox_shim_linux/src/syscalls/net.rs +++ b/litebox_shim_linux/src/syscalls/net.rs @@ -915,7 +915,7 @@ impl Task { })?; self.do_socket(domain, ty, flags, protocol) } - fn do_socket( + pub(crate) fn do_socket( &self, domain: AddressFamily, ty: SockType, @@ -1267,7 +1267,7 @@ impl Task { let sockaddr = read_sockaddr_from_user(sockaddr, addrlen)?; self.do_connect(fd, sockaddr) } - fn do_connect(&self, sockfd: u32, sockaddr: SocketAddress) -> Result<(), Errno> { + pub(crate) fn do_connect(&self, sockfd: u32, sockaddr: SocketAddress) -> Result<(), Errno> { self.files.borrow().with_socket( sockfd, |fd| { @@ -1339,17 +1339,16 @@ impl Task { let sockaddr = addr .map(|addr| read_sockaddr_from_user(addr, addrlen as usize)) .transpose()?; - self.do_sendto(fd, buf, len, flags, sockaddr) + let buf = buf.to_owned_slice(len).ok_or(Errno::EFAULT)?; + self.do_sendto(fd, &buf, flags, sockaddr) } - fn do_sendto( + pub(crate) fn do_sendto( &self, sockfd: u32, - buf: ConstPtr, - len: usize, + buf: &[u8], flags: SendFlags, sockaddr: Option, ) -> Result { - let buf = buf.to_owned_slice(len).ok_or(Errno::EFAULT)?; self.files.borrow().with_socket( sockfd, |fd| { @@ -1358,14 +1357,14 @@ impl Task { .map(|addr| addr.inet().ok_or(Errno::EAFNOSUPPORT)) .transpose()?; self.global - .sendto(&self.wait_cx(), fd, &buf, flags, sockaddr) + .sendto(&self.wait_cx(), fd, buf, flags, sockaddr) }, |file| { let addr = sockaddr .clone() .map(|addr| addr.unix().ok_or(Errno::EAFNOSUPPORT)) .transpose()?; - file.sendto(self, &buf, flags, addr) + file.sendto(self, buf, flags, addr) }, ) } @@ -1444,14 +1443,16 @@ impl Task { addr: Option>, addrlen: MutPtr, ) -> Result { + const MAX_LEN: usize = 4096; let Ok(sockfd) = u32::try_from(fd) else { return Err(Errno::EBADF); }; let mut source_addr = None; + let mut buffer = [0u8; MAX_LEN]; + let recv_buf = &mut buffer[..MAX_LEN.min(len)]; let size = self.do_recvfrom( sockfd, - buf, - len, + recv_buf, flags, if addr.is_some() { Some(&mut source_addr) @@ -1459,6 +1460,8 @@ impl Task { None }, )?; + buf.copy_from_slice(0, &recv_buf[..size.min(recv_buf.len())]) + .ok_or(Errno::EFAULT)?; if let Some(src_addr) = source_addr && let Some(sock_ptr) = addr { @@ -1466,57 +1469,53 @@ impl Task { } Ok(size) } - fn do_recvfrom( + pub(crate) fn do_recvfrom( &self, sockfd: u32, - buf: MutPtr, - len: usize, + buf: &mut [u8], flags: ReceiveFlags, source_addr: Option<&mut Option>, ) -> Result { let want_source = source_addr.is_some(); - let (size, addr) = self.files.borrow().with_socket( - sockfd, - |fd| { - const MAX_LEN: usize = 4096; - let mut buffer: [u8; MAX_LEN] = [0; MAX_LEN]; - let buffer: &mut [u8] = &mut buffer[..MAX_LEN.min(len)]; - let mut addr = None; - let size = self.global.receive( - &self.wait_cx(), - fd, - buffer, - flags, - if want_source { Some(&mut addr) } else { None }, - )?; - let src_addr = addr.map(SocketAddress::Inet); - if !flags.contains(ReceiveFlags::TRUNC) { - assert!(size <= len, "{size} should be smaller than {len}"); - } - buf.copy_from_slice(0, &buffer[..size.min(buffer.len())]) - .ok_or(Errno::EFAULT)?; - Ok((size, src_addr)) - }, - |file| { - const MAX_LEN: usize = 4096; - let mut buffer: [u8; MAX_LEN] = [0; MAX_LEN]; - let buffer: &mut [u8] = &mut buffer[..MAX_LEN.min(len)]; + let files = self.files.borrow(); + let file_table = files.file_descriptors.read(); + let (size, addr) = match file_table.get_fd(sockfd).ok_or(Errno::EBADF)? { + Descriptor::LiteBoxRawFd(raw_fd) => { + let raw_fd = *raw_fd; + drop(file_table); + files.with_socket_fd(raw_fd, |fd| { + let mut addr = None; + let size = self.global.receive( + &self.wait_cx(), + fd, + buf, + flags, + if want_source { Some(&mut addr) } else { None }, + )?; + let src_addr = addr.map(SocketAddress::Inet); + Ok((size, src_addr)) + })? + } + Descriptor::Unix { file, .. } => { + let file = file.clone(); + drop(file_table); let mut addr = None; let size = file.recvfrom( &self.wait_cx(), - buffer, + buf, flags, if want_source { Some(&mut addr) } else { None }, )?; let src_addr = addr.map(SocketAddress::Unix); - if !flags.contains(ReceiveFlags::TRUNC) { - assert!(size <= len, "{size} should be smaller than {len}"); - } - buf.copy_from_slice(0, &buffer[..size.min(buffer.len())]) - .ok_or(Errno::EFAULT)?; - Ok((size, src_addr)) - }, - )?; + (size, src_addr) + } + _ => return Err(Errno::ENOTSOCK), + }; + + if !flags.contains(ReceiveFlags::TRUNC) { + let len = buf.len(); + assert!(size <= len, "{size} should be smaller than {len}"); + } if let (Some(source_addr), Some(addr)) = (source_addr, addr) { *source_addr = Some(addr); @@ -1846,37 +1845,17 @@ mod tests { }; use super::SocketAddress; - use crate::{ConstPtr, MutPtr}; + use crate::{ConstPtr, MutPtr, syscalls::tests::init_platform}; extern crate alloc; extern crate std; const TUN_IP_ADDR: [u8; 4] = [10, 0, 0, 2]; const TUN_IP_ADDR_STR: &str = "10.0.0.2"; + const TUN_DEVICE_NAME: &str = "tun99"; const SERVER_PORT: u16 = 8080; const CLIENT_PORT: u16 = 8081; - fn init_platform(tun_device_name: Option<&str>) -> crate::Task { - let task = crate::syscalls::tests::init_platform(tun_device_name); - let global = task.global.clone(); - if tun_device_name.is_some() { - // Start a background thread to perform network interaction - // Naive implementation for testing purpose only - std::thread::spawn(move || { - loop { - while global - .net - .lock() - .perform_platform_interaction() - .call_again_immediately() - {} - core::hint::spin_loop(); - } - }); - } - task - } - fn close_socket(task: &crate::Task, fd: u32) { task.sys_close(i32::try_from(fd).unwrap()) .expect("close socket failed"); @@ -2002,9 +1981,8 @@ mod tests { match option { "sendto" => { - let ptr = ConstPtr::from_usize(buf.as_ptr().expose_provenance()); let n = task - .do_sendto(client_fd, ptr, buf.len(), SendFlags::empty(), None) + .do_sendto(client_fd, buf.as_bytes(), SendFlags::empty(), None) .expect("Failed to send data"); assert_eq!(n, buf.len()); let output = child_handle @@ -2060,12 +2038,10 @@ mod tests { } } let mut recv_buf = [0u8; 48]; - let recv_ptr = crate::MutPtr::from_usize(recv_buf.as_mut_ptr() as usize); let n = task .do_recvfrom( client_fd, - recv_ptr, - recv_buf.len(), + &mut recv_buf, if test_trunc { ReceiveFlags::TRUNC } else { @@ -2095,12 +2071,12 @@ mod tests { test_trunc: bool, option: &'static str, ) { - let task = init_platform(Some("tun99")); + let task = init_platform(Some(TUN_DEVICE_NAME)); test_tcp_socket_as_server(&task, TUN_IP_ADDR, port, is_nonblocking, test_trunc, option); } fn test_tcp_socket_send(is_nonblocking: bool, test_trunc: bool) { - let task = init_platform(Some("tun99")); + let task = init_platform(Some(TUN_DEVICE_NAME)); test_tcp_socket_as_server( &task, TUN_IP_ADDR, @@ -2146,7 +2122,7 @@ mod tests { #[test] fn test_tun_tcp_connection_refused() { - let task = init_platform(Some("tun99")); + let task = init_platform(Some(TUN_DEVICE_NAME)); let socket_fd = task .do_socket(AddressFamily::INET, SockType::Stream, SockFlags::empty(), 0) .expect("failed to create socket"); @@ -2169,7 +2145,7 @@ mod tests { #[test] fn test_tun_tcp_socket_as_client() { - let task = init_platform(Some("tun99")); + let task = init_platform(Some(TUN_DEVICE_NAME)); let child_handle = std::thread::spawn(|| { std::process::Command::new("nc") @@ -2197,12 +2173,10 @@ mod tests { .expect("failed to connect to server"); let buf = "Hello, world!"; - let ptr = ConstPtr::from_usize(buf.as_ptr().expose_provenance()); - let len = buf.len(); let n = task - .do_sendto(client_fd, ptr, len, SendFlags::empty(), None) + .do_sendto(client_fd, buf.as_bytes(), SendFlags::empty(), None) .unwrap(); - assert_eq!(n, len); + assert_eq!(n, buf.len()); let linger = litebox_common_linux::Linger { onoff: 1, // enable linger @@ -2228,7 +2202,7 @@ mod tests { } fn blocking_udp_server_socket(test_trunc: bool, is_nonblocking: bool) { - let task = init_platform(Some("tun99")); + let task = init_platform(Some(TUN_DEVICE_NAME)); // Server socket and bind let server_fd = task @@ -2288,7 +2262,6 @@ mod tests { // Server receives and inspects sender addr let mut recv_buf = [0u8; 48]; - let recv_ptr = crate::MutPtr::from_usize(recv_buf.as_mut_ptr() as usize); let mut sender_addr = None; let mut recv_flags = ReceiveFlags::empty(); if test_trunc { @@ -2304,15 +2277,15 @@ mod tests { assert_eq!(fd, server_fd); } } + let recv_len = if test_trunc { + 8 // intentionally small size to test truncation + } else { + recv_buf.len() + }; let n = task .do_recvfrom( server_fd, - recv_ptr, - if test_trunc { - 8 // intentionally small size to test truncation - } else { - recv_buf.len() - }, + &mut recv_buf[..recv_len], recv_flags, Some(&mut sender_addr), ) @@ -2353,7 +2326,7 @@ mod tests { fn test_tun_udp_client_socket_without_server() { // We do not support loopback yet, so this test only checks that // the client can send packets without a server. - let task = init_platform(Some("tun99")); + let task = init_platform(Some(TUN_DEVICE_NAME)); // Client socket and explicit bind let client_fd = task @@ -2372,11 +2345,9 @@ mod tests { // Send from client to server let msg = "Hello without connect()"; - let msg_ptr = ConstPtr::from_usize(msg.as_ptr().expose_provenance()); task.do_sendto( client_fd, - msg_ptr, - msg.len(), + msg.as_bytes(), SendFlags::empty(), Some(server_addr.clone()), ) @@ -2396,8 +2367,7 @@ mod tests { // Now client can send without specifying addr let msg = "Hello with connect()"; - let msg_ptr = ConstPtr::from_usize(msg.as_ptr().expose_provenance()); - task.do_sendto(client_fd, msg_ptr, msg.len(), SendFlags::empty(), None) + task.do_sendto(client_fd, msg.as_bytes(), SendFlags::empty(), None) .expect("failed to sendto"); close_socket(&task, client_fd); @@ -2405,7 +2375,7 @@ mod tests { #[test] fn test_tun_tcp_sockopt() { - let task = init_platform(Some("tun99")); + let task = init_platform(Some(TUN_DEVICE_NAME)); let sockfd = task .do_socket(AddressFamily::INET, SockType::Stream, SockFlags::empty(), 0) .expect("failed to create socket"); @@ -2543,8 +2513,7 @@ mod unix_tests { let n = task .do_sendto( server_fd, - ConstPtr::from_usize(msg1.as_ptr() as usize), - msg1.len(), + msg1.as_bytes(), SendFlags::empty(), Some(client_addr.clone()), ) @@ -2556,8 +2525,7 @@ mod unix_tests { let n = task .do_recvfrom( client_fd, - MutPtr::from_usize(buf.as_mut_ptr() as usize), - buf.len(), + &mut buf, ReceiveFlags::empty(), Some(&mut source), ) @@ -2571,8 +2539,7 @@ mod unix_tests { let n = task .do_sendto( client_fd, - ConstPtr::from_usize(msg2.as_ptr() as usize), - msg2.len(), + msg2.as_bytes(), SendFlags::empty(), Some(server_addr), ) @@ -2584,8 +2551,7 @@ mod unix_tests { let n = task .do_recvfrom( server_fd, - MutPtr::from_usize(buf.as_mut_ptr() as usize), - buf.len(), + &mut buf, ReceiveFlags::empty(), Some(&mut source), ) @@ -2627,36 +2593,18 @@ mod unix_tests { )); let msg1 = "Hello, "; let n = task - .do_sendto( - server_conn, - ConstPtr::from_usize(msg1.as_ptr() as usize), - msg1.len(), - SendFlags::empty(), - None, - ) + .do_sendto(server_conn, msg1.as_bytes(), SendFlags::empty(), None) .expect("sendto failed"); assert_eq!(n, msg1.len()); let msg2 = "world!"; let n = task - .do_sendto( - server_conn, - ConstPtr::from_usize(msg2.as_ptr() as usize), - msg2.len(), - SendFlags::empty(), - None, - ) + .do_sendto(server_conn, msg2.as_bytes(), SendFlags::empty(), None) .expect("sendto failed"); assert_eq!(n, msg2.len()); let mut buf = [0u8; 64]; let n = task - .do_recvfrom( - client_fd, - MutPtr::from_usize(buf.as_mut_ptr() as usize), - buf.len(), - ReceiveFlags::empty(), - None, - ) + .do_recvfrom(client_fd, &mut buf, ReceiveFlags::empty(), None) .expect("recvfrom failed"); assert_eq!(n, msg1.len() + msg2.len()); assert_eq!(&buf[..n], b"Hello, world!"); @@ -2756,13 +2704,7 @@ mod unix_tests { for (i, client_fd) in client_fds.iter().enumerate() { let msg = alloc::format!("message from connection {i}"); let n = task - .do_sendto( - *client_fd, - ConstPtr::from_usize(msg.as_ptr() as usize), - msg.len(), - SendFlags::empty(), - None, - ) + .do_sendto(*client_fd, msg.as_bytes(), SendFlags::empty(), None) .expect("sendto failed"); assert_eq!(n, msg.len()); } @@ -2798,13 +2740,7 @@ mod unix_tests { ppoll(&task, *server_conn_fd, Events::IN); } let n = task - .do_recvfrom( - *server_conn_fd, - MutPtr::from_usize(buf.as_mut_ptr() as usize), - buf.len(), - ReceiveFlags::empty(), - None, - ) + .do_recvfrom(*server_conn_fd, &mut buf, ReceiveFlags::empty(), None) .expect("recvfrom failed"); assert_eq!(n, msg.len()); assert_eq!(&buf[..n], msg.as_bytes()); @@ -2935,13 +2871,7 @@ mod unix_tests { ppoll(&task, sock2, Events::IN); } let n = task - .do_recvfrom( - sock2, - MutPtr::from_usize(buf.as_mut_ptr() as usize), - buf.len(), - ReceiveFlags::empty(), - None, - ) + .do_recvfrom(sock2, &mut buf, ReceiveFlags::empty(), None) .expect("recvfrom failed"); assert_eq!(&buf[..n], b"Message from sock1"); }); @@ -2949,14 +2879,8 @@ mod unix_tests { std::thread::sleep(core::time::Duration::from_millis(100)); // Send from sock1 to sock2 let msg1 = "Message from sock1"; - task.do_sendto( - sock1, - ConstPtr::from_usize(msg1.as_ptr().expose_provenance()), - msg1.len(), - SendFlags::empty(), - None, - ) - .expect("sendto failed"); + task.do_sendto(sock1, msg1.as_bytes(), SendFlags::empty(), None) + .expect("sendto failed"); task.spawn_clone_for_test(move |task| { // Receive on sock1 (from sock2) @@ -2965,13 +2889,7 @@ mod unix_tests { ppoll(&task, sock1, Events::IN); } let n = task - .do_recvfrom( - sock1, - MutPtr::from_usize(buf.as_mut_ptr() as usize), - buf.len(), - ReceiveFlags::empty(), - None, - ) + .do_recvfrom(sock1, &mut buf, ReceiveFlags::empty(), None) .expect("recvfrom failed"); assert_eq!(&buf[..n], b"Message from sock2"); }); @@ -2979,14 +2897,8 @@ mod unix_tests { std::thread::sleep(core::time::Duration::from_millis(100)); // Send from sock2 to sock1 let msg2 = "Message from sock2"; - task.do_sendto( - sock2, - ConstPtr::from_usize(msg2.as_ptr().expose_provenance()), - msg2.len(), - SendFlags::empty(), - None, - ) - .expect("sendto failed"); + task.do_sendto(sock2, msg2.as_bytes(), SendFlags::empty(), None) + .expect("sendto failed"); std::thread::sleep(core::time::Duration::from_millis(500)); close_socket(&task, sock1); @@ -3020,13 +2932,7 @@ mod unix_tests { let mut buf = [0u8; 16]; let start = std::time::Instant::now(); let err = task - .do_recvfrom( - sock1, - MutPtr::from_usize(buf.as_mut_ptr() as usize), - buf.len(), - ReceiveFlags::empty(), - None, - ) + .do_recvfrom(sock1, &mut buf, ReceiveFlags::empty(), None) .unwrap_err(); let elapsed = start.elapsed(); assert_eq!(err, Errno::ETIMEDOUT); diff --git a/litebox_shim_linux/src/syscalls/tests.rs b/litebox_shim_linux/src/syscalls/tests.rs index e6c7e95c5..e900112b8 100644 --- a/litebox_shim_linux/src/syscalls/tests.rs +++ b/litebox_shim_linux/src/syscalls/tests.rs @@ -38,7 +38,25 @@ pub(crate) fn init_platform(tun_device_name: Option<&str>) -> crate::Task { }); let tar_ro_fs = litebox::fs::tar_ro::FileSystem::new(litebox, TEST_TAR_FILE.into()); shim_builder.set_fs(shim_builder.default_fs(in_mem_fs, tar_ro_fs)); - shim_builder.build().0.new_test_task() + let task = shim_builder.build().0.new_test_task(); + + let global = task.global.clone(); + if tun_device_name.is_some() { + // Start a background thread to perform network interaction + // Naive implementation for testing purpose only + std::thread::spawn(move || { + loop { + while global + .net + .lock() + .perform_platform_interaction() + .call_again_immediately() + {} + core::hint::spin_loop(); + } + }); + } + task } #[test] From cc36869a66fdd25b6786e539e6ba784aa5a7d077 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Mon, 9 Feb 2026 18:25:09 -0800 Subject: [PATCH 06/16] fix fcall --- litebox/src/fs/nine_p/client.rs | 301 ++++++++++++++++------------- litebox/src/fs/nine_p/fcall.rs | 328 ++------------------------------ 2 files changed, 184 insertions(+), 445 deletions(-) diff --git a/litebox/src/fs/nine_p/client.rs b/litebox/src/fs/nine_p/client.rs index d7966d3d0..7ebcd4318 100644 --- a/litebox/src/fs/nine_p/client.rs +++ b/litebox/src/fs/nine_p/client.rs @@ -159,7 +159,10 @@ impl Client { /// Send a request and wait for the response /// /// TODO: support async operations - fn fcall(&self, fcall: Fcall<'_>) -> Result, Error> { + fn fcall(&self, fcall: Fcall<'_>, f: F) -> Result + where + F: FnOnce(Fcall<'_>) -> Result, + { let tag = self.next_tag.fetch_add(1, Ordering::Relaxed); if tag == fcall::NOTAG { todo!("tag wraparound"); @@ -176,7 +179,7 @@ impl Client { loop { let response = transport::read_message(transport, &mut rbuf)?; if response.tag == tag { - return Ok(response.fcall.clone_static()); + return f(response.fcall); } } } @@ -188,23 +191,24 @@ impl Client { aname: &str, ) -> Result<(fcall::Qid, fcall::Fid), Error> { let fid = self.fids.next(); - match self.fcall(Fcall::Tattach(fcall::Tattach { - afid: fcall::NOFID, - fid, - n_uname: fcall::NONUNAME, - uname: uname.into(), - aname: aname.into(), - }))? { - Fcall::Rattach(fcall::Rattach { qid }) => Ok((qid, fid)), - Fcall::Rlerror(e) => { - self.fids.free(fid); - Err(Error::from(e)) - } - _ => { - self.fids.free(fid); - Err(Error::InvalidResponse) - } + let res = self.fcall( + Fcall::Tattach(fcall::Tattach { + afid: fcall::NOFID, + fid, + n_uname: fcall::NONUNAME, + uname: uname.into(), + aname: aname.into(), + }), + |response| match response { + Fcall::Rattach(fcall::Rattach { qid }) => Ok((qid, fid)), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ); + if res.is_err() { + self.fids.free(fid); } + res } /// Walks the path from the given fid. @@ -221,15 +225,18 @@ impl Client { return Err(Error::NameTooLong); } let new_fid = self.fids.next(); - let ret = match self.fcall(Fcall::Twalk(fcall::Twalk { - fid, - new_fid, - wnames: wnames.to_vec(), - }))? { - Fcall::Rwalk(fcall::Rwalk { wqids }) => Ok((wqids, new_fid)), - Fcall::Rlerror(err) => Err(Error::from(err)), - _ => Err(Error::InvalidResponse), - }; + let ret = self.fcall( + Fcall::Twalk(fcall::Twalk { + fid, + new_fid, + wnames: wnames.to_vec(), + }), + |response| match response { + Fcall::Rwalk(fcall::Rwalk { wqids }) => Ok((wqids, new_fid)), + Fcall::Rlerror(err) => Err(Error::from(err)), + _ => Err(Error::InvalidResponse), + }, + ); if ret.is_err() { self.fids.free(new_fid); } @@ -291,11 +298,14 @@ impl Client { fid: fcall::Fid, flags: fcall::LOpenFlags, ) -> Result { - match self.fcall(Fcall::Tlopen(fcall::Tlopen { fid, flags }))? { - Fcall::Rlopen(fcall::Rlopen { qid, .. }) => Ok(qid), - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Tlopen(fcall::Tlopen { fid, flags }), + |response| match response { + Fcall::Rlopen(fcall::Rlopen { qid, .. }) => Ok(qid), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Create a file with the given name and flags. @@ -310,17 +320,20 @@ impl Client { mode: u32, gid: u32, ) -> Result<(fcall::Qid, fcall::Fid), Error> { - match self.fcall(Fcall::Tlcreate(fcall::Tlcreate { - fid: dfid, - name: name.into(), - flags, - mode, - gid, - }))? { - Fcall::Rlcreate(fcall::Rlcreate { qid, iounit: _ }) => Ok((qid, dfid)), - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Tlcreate(fcall::Tlcreate { + fid: dfid, + name: name.into(), + flags, + mode, + gid, + }), + |response| match response { + Fcall::Rlcreate(fcall::Rlcreate { qid, iounit: _ }) => Ok((qid, dfid)), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Read from a file @@ -331,32 +344,38 @@ impl Client { buf: &mut [u8], ) -> Result { let count = buf.len().min((self.msize - fcall::IOHDRSZ) as usize); - match self.fcall(Fcall::Tread(fcall::Tread { - fid, - offset, - count: u32::try_from(count).expect("count exceeds u32"), - }))? { - Fcall::Rread(fcall::Rread { data }) => { - buf[..data.len()].copy_from_slice(&data); - Ok(data.len()) - } - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Tread(fcall::Tread { + fid, + offset, + count: u32::try_from(count).expect("count exceeds u32"), + }), + |response| match response { + Fcall::Rread(fcall::Rread { data }) => { + buf[..data.len()].copy_from_slice(&data); + Ok(data.len()) + } + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Write to a file pub(super) fn write(&self, fid: fcall::Fid, offset: u64, data: &[u8]) -> Result { let count = data.len().min((self.msize - fcall::IOHDRSZ) as usize); - match self.fcall(Fcall::Twrite(fcall::Twrite { - fid, - offset, - data: alloc::borrow::Cow::Borrowed(&data[..count]), - }))? { - Fcall::Rwrite(fcall::Rwrite { count }) => Ok(count as usize), - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Twrite(fcall::Twrite { + fid, + offset, + data: alloc::borrow::Cow::Borrowed(&data[..count]), + }), + |response| match response { + Fcall::Rwrite(fcall::Rwrite { count }) => Ok(count as usize), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Get file attributes @@ -365,11 +384,14 @@ impl Client { fid: fcall::Fid, req_mask: GetattrMask, ) -> Result { - match self.fcall(Fcall::Tgetattr(fcall::Tgetattr { fid, req_mask }))? { - Fcall::Rgetattr(r) => Ok(r), - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Tgetattr(fcall::Tgetattr { fid, req_mask }), + |response| match response { + Fcall::Rgetattr(r) => Ok(r), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Set file attributes @@ -379,11 +401,14 @@ impl Client { valid: fcall::SetattrMask, stat: fcall::SetAttr, ) -> Result<(), Error> { - match self.fcall(Fcall::Tsetattr(fcall::Tsetattr { fid, valid, stat }))? { - Fcall::Rsetattr(_) => Ok(()), - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Tsetattr(fcall::Tsetattr { fid, valid, stat }), + |response| match response { + Fcall::Rsetattr(_) => Ok(()), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Read directory entries @@ -393,14 +418,16 @@ impl Client { offset: u64, ) -> Result>, Error> { let count = self.msize - fcall::READDIRHDRSZ; - match self.fcall(Fcall::Treaddir(fcall::Treaddir { fid, offset, count }))? { - Fcall::Rreaddir(fcall::Rreaddir { data }) => { - // Clone the directory entries to owned versions - Ok(data.data) - } - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Treaddir(fcall::Treaddir { fid, offset, count }), + |response| match response { + Fcall::Rreaddir(fcall::Rreaddir { data }) => { + Ok(data.data.iter().map(|each| each.clone_static()).collect()) + } + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Read all directory entries @@ -429,38 +456,47 @@ impl Client { mode: u32, gid: u32, ) -> Result { - match self.fcall(Fcall::Tmkdir(fcall::Tmkdir { - dfid, - name: name.into(), - mode, - gid, - }))? { - Fcall::Rmkdir(fcall::Rmkdir { qid }) => Ok(qid), - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Tmkdir(fcall::Tmkdir { + dfid, + name: name.into(), + mode, + gid, + }), + |response| match response { + Fcall::Rmkdir(fcall::Rmkdir { qid }) => Ok(qid), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Remove the file represented by fid and clunk the fid, even if the remove fails pub(super) fn remove(&self, fid: fcall::Fid) -> Result<(), Error> { - match self.fcall(Fcall::Tremove(fcall::Tremove { fid }))? { - Fcall::Rremove(_) => Ok(()), - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Tremove(fcall::Tremove { fid }), + |response| match response { + Fcall::Rremove(_) => Ok(()), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Remove (unlink) a file or directory pub(super) fn unlinkat(&self, dfid: fcall::Fid, name: &str, flags: u32) -> Result<(), Error> { - match self.fcall(Fcall::Tunlinkat(fcall::Tunlinkat { - dfid, - name: name.into(), - flags, - }))? { - Fcall::Runlinkat(_) => Ok(()), - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Tunlinkat(fcall::Tunlinkat { + dfid, + name: name.into(), + flags, + }), + |response| match response { + Fcall::Runlinkat(_) => Ok(()), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Rename a file @@ -471,37 +507,46 @@ impl Client { dfid: fcall::Fid, name: &str, ) -> Result<(), Error> { - match self.fcall(Fcall::Trename(fcall::Trename { - fid, - dfid, - name: name.into(), - }))? { - Fcall::Rrename(_) => Ok(()), - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Trename(fcall::Trename { + fid, + dfid, + name: name.into(), + }), + |response| match response { + Fcall::Rrename(_) => Ok(()), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Fsync a file #[expect(dead_code)] pub(super) fn fsync(&self, fid: fcall::Fid, datasync: bool) -> Result<(), Error> { - match self.fcall(Fcall::Tfsync(fcall::Tfsync { - fid, - datasync: u32::from(datasync), - }))? { - Fcall::Rfsync(_) => Ok(()), - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - } + self.fcall( + Fcall::Tfsync(fcall::Tfsync { + fid, + datasync: u32::from(datasync), + }), + |response| match response { + Fcall::Rfsync(_) => Ok(()), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ) } /// Clunk (close) a fid pub(super) fn clunk(&self, fid: fcall::Fid) -> Result<(), Error> { - let result = match self.fcall(Fcall::Tclunk(fcall::Tclunk { fid }))? { - Fcall::Rclunk(_) => Ok(()), - Fcall::Rlerror(e) => Err(Error::from(e)), - _ => Err(Error::InvalidResponse), - }; + let result = self.fcall( + Fcall::Tclunk(fcall::Tclunk { fid }), + |response| match response { + Fcall::Rclunk(_) => Ok(()), + Fcall::Rlerror(e) => Err(Error::from(e)), + _ => Err(Error::InvalidResponse), + }, + ); self.fids.free(fid); result } diff --git a/litebox/src/fs/nine_p/fcall.rs b/litebox/src/fs/nine_p/fcall.rs index e3a13c2c6..d72b96b91 100644 --- a/litebox/src/fs/nine_p/fcall.rs +++ b/litebox/src/fs/nine_p/fcall.rs @@ -398,7 +398,7 @@ pub(super) struct Time { /// File attributes #[derive(Clone, Debug, Copy)] pub(super) struct Stat { - pub(super)mode: u32, + pub(super) mode: u32, pub(super) uid: u32, pub(super) gid: u32, pub(super) nlink: u64, @@ -435,6 +435,16 @@ pub(super) struct DirEntry<'a> { } impl DirEntry<'_> { + /// Create a static (owned) copy of this directory entry + pub(super) fn clone_static(&self) -> DirEntry<'static> { + DirEntry { + qid: self.qid, + offset: self.offset, + typ: self.typ, + name: self.name.clone_static(), + } + } + /// Calculate the size of this entry when encoded fn size(&self) -> u64 { (13 + 8 + 1 + 2 + self.name.len()) as u64 @@ -488,18 +498,6 @@ pub(super) struct Tattach<'a> { pub(super) n_uname: u32, } -impl Tattach<'_> { - fn clone_static(&self) -> Tattach<'static> { - Tattach { - afid: self.afid, - fid: self.fid, - n_uname: self.n_uname, - aname: self.aname.clone_static(), - uname: self.uname.clone_static(), - } - } -} - /// Attach response #[derive(Clone, Debug)] pub(super) struct Rattach { @@ -542,18 +540,6 @@ pub(super) struct Tlcreate<'a> { pub(super) gid: u32, } -impl<'a> Tlcreate<'a> { - fn clone_static(&self) -> Tlcreate<'static> { - Tlcreate { - fid: self.fid, - flags: self.flags, - gid: self.gid, - mode: self.mode, - name: self.name.clone_static(), - } - } -} - /// Create response #[derive(Clone, Debug)] pub(super) struct Rlcreate { @@ -570,17 +556,6 @@ pub(super) struct Tsymlink<'a> { gid: u32, } -impl<'a> Tsymlink<'a> { - fn clone_static(&'a self) -> Tsymlink<'static> { - Tsymlink { - fid: self.fid, - name: self.name.clone_static(), - symtgt: self.symtgt.clone_static(), - gid: self.gid, - } - } -} - /// Symlink response #[derive(Clone, Debug)] pub(super) struct Rsymlink { @@ -598,19 +573,6 @@ pub(super) struct Tmknod<'a> { gid: u32, } -impl<'a> Tmknod<'a> { - fn clone_static(&'a self) -> Tmknod<'static> { - Tmknod { - dfid: self.dfid, - gid: self.gid, - major: self.major, - minor: self.minor, - mode: self.mode, - name: self.name.clone_static(), - } - } -} - /// Mknod response #[derive(Clone, Debug)] pub(super) struct Rmknod { @@ -625,16 +587,6 @@ pub(super) struct Trename<'a> { pub(super) name: FcallStr<'a>, } -impl<'a> Trename<'a> { - fn clone_static(&self) -> Trename<'static> { - Trename { - fid: self.fid, - dfid: self.dfid, - name: self.name.clone_static(), - } - } -} - /// Rename response #[derive(Clone, Debug)] pub(super) struct Rrename {} @@ -651,14 +603,6 @@ pub(super) struct Rreadlink<'a> { target: FcallStr<'a>, } -impl<'a> Rreadlink<'a> { - fn clone_static(&'a self) -> Rreadlink<'static> { - Rreadlink { - target: self.target.clone_static(), - } - } -} - /// Getattr request #[derive(Clone, Debug)] pub(super) struct Tgetattr { @@ -694,16 +638,6 @@ pub(super) struct Txattrwalk<'a> { name: FcallStr<'a>, } -impl<'a> Txattrwalk<'a> { - fn clone_static(&'a self) -> Txattrwalk<'static> { - Txattrwalk { - fid: self.fid, - new_fid: self.new_fid, - name: self.name.clone_static(), - } - } -} - /// Xattr walk response #[derive(Clone, Debug)] pub(super) struct Rxattrwalk { @@ -719,17 +653,6 @@ pub(super) struct Txattrcreate<'a> { flags: u32, } -impl<'a> Txattrcreate<'a> { - fn clone_static(&'a self) -> Txattrcreate<'static> { - Txattrcreate { - fid: self.fid, - name: self.name.clone_static(), - attr_size: self.attr_size, - flags: self.flags, - } - } -} - /// Xattr create response #[derive(Clone, Debug)] pub(super) struct Rxattrcreate {} @@ -748,26 +671,6 @@ pub(super) struct Rreaddir<'a> { pub(super) data: DirEntryData<'a>, } -impl<'a> Rreaddir<'a> { - fn clone_static(&'a self) -> Rreaddir<'static> { - Rreaddir { - data: DirEntryData { - data: self - .data - .data - .iter() - .map(|de| DirEntry { - qid: de.qid, - offset: de.offset, - typ: de.typ, - name: de.name.clone_static(), - }) - .collect(), - }, - } - } -} - /// Fsync request #[derive(Clone, Debug)] pub(super) struct Tfsync { @@ -786,22 +689,6 @@ pub(super) struct Tlock<'a> { flock: Flock<'a>, } -impl<'a> Tlock<'a> { - fn clone_static(&'a self) -> Tlock<'static> { - Tlock { - fid: self.fid, - flock: Flock { - typ: self.flock.typ, - flags: self.flock.flags, - start: self.flock.start, - length: self.flock.length, - proc_id: self.flock.proc_id, - client_id: self.flock.client_id.clone_static(), - }, - } - } -} - /// Lock response #[derive(Clone, Debug)] pub(super) struct Rlock { @@ -815,41 +702,12 @@ pub(super) struct Tgetlock<'a> { flock: Getlock<'a>, } -impl<'a> Tgetlock<'a> { - fn clone_static(&'a self) -> Tgetlock<'static> { - Tgetlock { - fid: self.fid, - flock: Getlock { - typ: self.flock.typ, - start: self.flock.start, - length: self.flock.length, - proc_id: self.flock.proc_id, - client_id: self.flock.client_id.clone_static(), - }, - } - } -} - /// Getlock response #[derive(Clone, Debug)] pub(super) struct Rgetlock<'a> { flock: Getlock<'a>, } -impl<'a> Rgetlock<'a> { - fn clone_static(&'a self) -> Rgetlock<'static> { - Rgetlock { - flock: Getlock { - typ: self.flock.typ, - start: self.flock.start, - length: self.flock.length, - proc_id: self.flock.proc_id, - client_id: self.flock.client_id.clone_static(), - }, - } - } -} - /// Link request #[derive(Clone, Debug)] pub(super) struct Tlink<'a> { @@ -858,16 +716,6 @@ pub(super) struct Tlink<'a> { name: FcallStr<'a>, } -impl<'a> Tlink<'a> { - fn clone_static(&'a self) -> Tlink<'static> { - Tlink { - fid: self.fid, - dfid: self.dfid, - name: self.name.clone_static(), - } - } -} - /// Link response #[derive(Clone, Debug)] pub(super) struct Rlink {} @@ -881,17 +729,6 @@ pub(super) struct Tmkdir<'a> { pub(super) gid: u32, } -impl<'a> Tmkdir<'a> { - fn clone_static(&'a self) -> Tmkdir<'static> { - Tmkdir { - dfid: self.dfid, - gid: self.gid, - mode: self.mode, - name: self.name.clone_static(), - } - } -} - /// Mkdir response #[derive(Clone, Debug)] pub(super) struct Rmkdir { @@ -907,17 +744,6 @@ pub(super) struct Trenameat<'a> { newname: FcallStr<'a>, } -impl<'a> Trenameat<'a> { - fn clone_static(&'a self) -> Trenameat<'static> { - Trenameat { - newdfid: self.newdfid, - olddfid: self.olddfid, - newname: self.newname.clone_static(), - oldname: self.oldname.clone_static(), - } - } -} - /// Renameat response #[derive(Clone, Debug)] pub(super) struct Rrenameat {} @@ -930,16 +756,6 @@ pub(super) struct Tunlinkat<'a> { pub(super) flags: u32, } -impl<'a> Tunlinkat<'a> { - fn clone_static(&'a self) -> Tunlinkat<'static> { - Tunlinkat { - dfid: self.dfid, - flags: self.flags, - name: self.name.clone_static(), - } - } -} - /// Unlinkat response #[derive(Clone, Debug)] pub(super) struct Runlinkat {} @@ -953,17 +769,6 @@ pub(super) struct Tauth<'a> { n_uname: u32, } -impl<'a> Tauth<'a> { - fn clone_static(&'a self) -> Tauth<'static> { - Tauth { - afid: self.afid, - n_uname: self.n_uname, - aname: self.aname.clone_static(), - uname: self.uname.clone_static(), - } - } -} - /// Auth response #[derive(Clone, Debug)] pub(super) struct Rauth { @@ -977,15 +782,6 @@ pub(super) struct Tversion<'a> { pub(super) version: FcallStr<'a>, } -impl<'a> Tversion<'a> { - fn clone_static(&'a self) -> Tversion<'static> { - Tversion { - msize: self.msize, - version: self.version.clone_static(), - } - } -} - /// Version response #[derive(Clone, Debug)] pub(super) struct Rversion<'a> { @@ -993,15 +789,6 @@ pub(super) struct Rversion<'a> { pub(super) version: FcallStr<'a>, } -impl<'a> Rversion<'a> { - fn clone_static(&'a self) -> Rversion<'static> { - Rversion { - msize: self.msize, - version: self.version.clone_static(), - } - } -} - /// Flush request #[derive(Clone, Debug)] pub(super) struct Tflush { @@ -1020,16 +807,6 @@ pub(super) struct Twalk<'a> { pub(super) wnames: Vec>, } -impl<'a> Twalk<'a> { - fn clone_static(&'a self) -> Twalk<'static> { - Twalk { - fid: self.fid, - new_fid: self.new_fid, - wnames: self.wnames.iter().map(FcallStr::clone_static).collect(), - } - } -} - /// Walk response #[derive(Clone, Debug)] pub(super) struct Rwalk { @@ -1050,14 +827,6 @@ pub(super) struct Rread<'a> { pub(super) data: Cow<'a, [u8]>, } -impl<'a> Rread<'a> { - fn clone_static(&'a self) -> Rread<'static> { - Rread { - data: Cow::from(self.data.clone().into_owned()), - } - } -} - /// Write request #[derive(Clone, Debug)] pub(super) struct Twrite<'a> { @@ -1066,16 +835,6 @@ pub(super) struct Twrite<'a> { pub(super) data: Cow<'a, [u8]>, } -impl<'a> Twrite<'a> { - fn clone_static(&'a self) -> Twrite<'static> { - Twrite { - fid: self.fid, - offset: self.offset, - data: Cow::from(self.data.clone().into_owned()), - } - } -} - /// Write response #[derive(Clone, Debug)] pub(super) struct Rwrite { @@ -1168,71 +927,6 @@ pub(super) enum Fcall<'a> { Rremove(Rremove), } -impl Fcall<'_> { - /// Create a static (owned) copy of this Fcall - pub(super) fn clone_static(&self) -> Fcall<'static> { - match self { - Fcall::Rlerror(v) => Fcall::Rlerror(v.clone()), - Fcall::Tattach(v) => Fcall::Tattach(v.clone_static()), - Fcall::Rattach(v) => Fcall::Rattach(v.clone()), - Fcall::Tstatfs(v) => Fcall::Tstatfs(v.clone()), - Fcall::Rstatfs(v) => Fcall::Rstatfs(v.clone()), - Fcall::Tlopen(v) => Fcall::Tlopen(v.clone()), - Fcall::Rlopen(v) => Fcall::Rlopen(v.clone()), - Fcall::Tlcreate(v) => Fcall::Tlcreate(v.clone_static()), - Fcall::Rlcreate(v) => Fcall::Rlcreate(v.clone()), - Fcall::Tsymlink(v) => Fcall::Tsymlink(v.clone_static()), - Fcall::Rsymlink(v) => Fcall::Rsymlink(v.clone()), - Fcall::Tmknod(v) => Fcall::Tmknod(v.clone_static()), - Fcall::Rmknod(v) => Fcall::Rmknod(v.clone()), - Fcall::Trename(v) => Fcall::Trename(v.clone_static()), - Fcall::Rrename(v) => Fcall::Rrename(v.clone()), - Fcall::Treadlink(v) => Fcall::Treadlink(v.clone()), - Fcall::Rreadlink(v) => Fcall::Rreadlink(v.clone_static()), - Fcall::Tgetattr(v) => Fcall::Tgetattr(v.clone()), - Fcall::Rgetattr(v) => Fcall::Rgetattr(v.clone()), - Fcall::Tsetattr(v) => Fcall::Tsetattr(v.clone()), - Fcall::Rsetattr(v) => Fcall::Rsetattr(v.clone()), - Fcall::Txattrwalk(v) => Fcall::Txattrwalk(v.clone_static()), - Fcall::Rxattrwalk(v) => Fcall::Rxattrwalk(v.clone()), - Fcall::Txattrcreate(v) => Fcall::Txattrcreate(v.clone_static()), - Fcall::Rxattrcreate(v) => Fcall::Rxattrcreate(v.clone()), - Fcall::Treaddir(v) => Fcall::Treaddir(v.clone()), - Fcall::Rreaddir(v) => Fcall::Rreaddir(v.clone_static()), - Fcall::Tfsync(v) => Fcall::Tfsync(v.clone()), - Fcall::Rfsync(v) => Fcall::Rfsync(v.clone()), - Fcall::Tlock(v) => Fcall::Tlock(v.clone_static()), - Fcall::Rlock(v) => Fcall::Rlock(v.clone()), - Fcall::Tgetlock(v) => Fcall::Tgetlock(v.clone_static()), - Fcall::Rgetlock(v) => Fcall::Rgetlock(v.clone_static()), - Fcall::Tlink(v) => Fcall::Tlink(v.clone_static()), - Fcall::Rlink(v) => Fcall::Rlink(v.clone()), - Fcall::Tmkdir(v) => Fcall::Tmkdir(v.clone_static()), - Fcall::Rmkdir(v) => Fcall::Rmkdir(v.clone()), - Fcall::Trenameat(v) => Fcall::Trenameat(v.clone_static()), - Fcall::Rrenameat(v) => Fcall::Rrenameat(v.clone()), - Fcall::Tunlinkat(v) => Fcall::Tunlinkat(v.clone_static()), - Fcall::Runlinkat(v) => Fcall::Runlinkat(v.clone()), - Fcall::Tauth(v) => Fcall::Tauth(v.clone_static()), - Fcall::Rauth(v) => Fcall::Rauth(v.clone()), - Fcall::Tversion(v) => Fcall::Tversion(v.clone_static()), - Fcall::Rversion(v) => Fcall::Rversion(v.clone_static()), - Fcall::Tflush(v) => Fcall::Tflush(v.clone()), - Fcall::Rflush(v) => Fcall::Rflush(v.clone()), - Fcall::Twalk(v) => Fcall::Twalk(v.clone_static()), - Fcall::Rwalk(v) => Fcall::Rwalk(v.clone()), - Fcall::Tread(v) => Fcall::Tread(v.clone()), - Fcall::Rread(v) => Fcall::Rread(v.clone_static()), - Fcall::Twrite(v) => Fcall::Twrite(v.clone_static()), - Fcall::Rwrite(v) => Fcall::Rwrite(v.clone()), - Fcall::Tclunk(v) => Fcall::Tclunk(v.clone()), - Fcall::Rclunk(v) => Fcall::Rclunk(v.clone()), - Fcall::Tremove(v) => Fcall::Tremove(v.clone()), - Fcall::Rremove(v) => Fcall::Rremove(v.clone()), - } - } -} - // Implement From for all message types macro_rules! impl_from_for_fcall { ($($variant:ident($ty:ty)),* $(,)?) => { From 17d79df34ed6b1f99f9efcc9745d4b46dbed7e39 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Mon, 9 Feb 2026 23:48:51 -0800 Subject: [PATCH 07/16] remove clone_static --- litebox/src/fs/nine_p/client.rs | 31 +++++++++++++++------------ litebox/src/fs/nine_p/fcall.rs | 36 +++----------------------------- litebox/src/fs/nine_p/mod.rs | 24 ++++++--------------- litebox_shim_linux/src/nine_p.rs | 4 ---- 4 files changed, 27 insertions(+), 68 deletions(-) diff --git a/litebox/src/fs/nine_p/client.rs b/litebox/src/fs/nine_p/client.rs index 7ebcd4318..143a5f88b 100644 --- a/litebox/src/fs/nine_p/client.rs +++ b/litebox/src/fs/nine_p/client.rs @@ -119,7 +119,7 @@ impl Client { tag: fcall::NOTAG, fcall: Fcall::Tversion(fcall::Tversion { msize: bufsize, - version: "9P2000.L".into(), + version: fcall::FcallStr::Borrowed(b"9P2000.L"), }), }, ) @@ -132,7 +132,7 @@ impl Client { tag: fcall::NOTAG, fcall: Fcall::Rversion(fcall::Rversion { msize, version }), } => { - if version.as_bytes() != b"9P2000.L" { + if &*version != b"9P2000.L" { return Err(Error::InvalidResponse); } msize.min(bufsize) @@ -196,8 +196,8 @@ impl Client { afid: fcall::NOFID, fid, n_uname: fcall::NONUNAME, - uname: uname.into(), - aname: aname.into(), + uname: fcall::FcallStr::Borrowed(uname.as_bytes()), + aname: fcall::FcallStr::Borrowed(aname.as_bytes()), }), |response| match response { Fcall::Rattach(fcall::Rattach { qid }) => Ok((qid, fid)), @@ -283,12 +283,15 @@ impl Client { /// Walk to a path from a given fid /// /// Returns the qids for each path component and a new fid for the final location - pub(super) fn walk<'a, S: Into> + AsRef<[u8]>>( + pub(super) fn walk>( &self, fid: fcall::Fid, wnames: &[S], ) -> Result<(Vec, fcall::Fid), Error> { - let wnames: Vec = wnames.iter().map(Into::into).collect(); + let wnames: Vec> = wnames + .iter() + .map(|s| fcall::FcallStr::Borrowed(s.as_ref())) + .collect(); self.walk_chunked(fid, &wnames) } @@ -323,7 +326,7 @@ impl Client { self.fcall( Fcall::Tlcreate(fcall::Tlcreate { fid: dfid, - name: name.into(), + name: fcall::FcallStr::Borrowed(name.as_bytes()), flags, mode, gid, @@ -421,9 +424,11 @@ impl Client { self.fcall( Fcall::Treaddir(fcall::Treaddir { fid, offset, count }), |response| match response { - Fcall::Rreaddir(fcall::Rreaddir { data }) => { - Ok(data.data.iter().map(|each| each.clone_static()).collect()) - } + Fcall::Rreaddir(fcall::Rreaddir { data }) => Ok(data + .data + .iter() + .map(super::fcall::DirEntry::clone_static) + .collect()), Fcall::Rlerror(e) => Err(Error::from(e)), _ => Err(Error::InvalidResponse), }, @@ -459,7 +464,7 @@ impl Client { self.fcall( Fcall::Tmkdir(fcall::Tmkdir { dfid, - name: name.into(), + name: fcall::FcallStr::Borrowed(name.as_bytes()), mode, gid, }), @@ -488,7 +493,7 @@ impl Client { self.fcall( Fcall::Tunlinkat(fcall::Tunlinkat { dfid, - name: name.into(), + name: fcall::FcallStr::Borrowed(name.as_bytes()), flags, }), |response| match response { @@ -511,7 +516,7 @@ impl Client { Fcall::Trename(fcall::Trename { fid, dfid, - name: name.into(), + name: fcall::FcallStr::Borrowed(name.as_bytes()), }), |response| match response { Fcall::Rrename(_) => Ok(()), diff --git a/litebox/src/fs/nine_p/fcall.rs b/litebox/src/fs/nine_p/fcall.rs index d72b96b91..f8019a80a 100644 --- a/litebox/src/fs/nine_p/fcall.rs +++ b/litebox/src/fs/nine_p/fcall.rs @@ -182,37 +182,7 @@ bitflags! { } /// String type used in 9P protocol messages -#[derive(Clone, Debug)] -pub(super) enum FcallStr<'a> { - Owned(Vec), - Borrowed(&'a [u8]), -} - -impl<'a> FcallStr<'a> { - /// Get the bytes of the string - pub(super) fn as_bytes(&'a self) -> &'a [u8] { - match self { - FcallStr::Owned(b) => b, - FcallStr::Borrowed(b) => b, - } - } - - /// Create a static (owned) copy of this string - fn clone_static(&self) -> FcallStr<'static> { - FcallStr::Owned(self.as_bytes().to_vec()) - } - - /// Get the length of the string - fn len(&self) -> usize { - self.as_bytes().len() - } -} - -impl<'a, T: ?Sized + AsRef<[u8]>> From<&'a T> for FcallStr<'a> { - fn from(b: &'a T) -> FcallStr<'a> { - FcallStr::Borrowed(b.as_ref()) - } -} +pub(super) type FcallStr<'a> = Cow<'a, [u8]>; /// Directory entry data container #[derive(Clone, Debug)] @@ -441,7 +411,7 @@ impl DirEntry<'_> { qid: self.qid, offset: self.offset, typ: self.typ, - name: self.name.clone_static(), + name: FcallStr::Owned(self.name.clone().into_owned()), } } @@ -1168,7 +1138,7 @@ fn encode_u64(w: &mut W, v: u64) -> Result<(), transport::WriteError> fn encode_str(w: &mut W, v: &FcallStr<'_>) -> Result<(), transport::WriteError> { encode_u16(w, u16::try_from(v.len()).expect("str length exceeds u16"))?; - w.write_all(v.as_bytes()) + w.write_all(v) } fn encode_data_buf(w: &mut W, v: &[u8]) -> Result<(), transport::WriteError> { diff --git a/litebox/src/fs/nine_p/mod.rs b/litebox/src/fs/nine_p/mod.rs index c20b2c1f9..f76aa3f6f 100644 --- a/litebox/src/fs/nine_p/mod.rs +++ b/litebox/src/fs/nine_p/mod.rs @@ -29,7 +29,6 @@ use crate::fs::errors::{ }; use crate::fs::nine_p::fcall::Rlerror; use crate::path::Arg; -use crate::platform::DebugLogProvider; use crate::{LiteBox, sync}; mod client; @@ -534,17 +533,13 @@ impl super::private::Sealed for FileSystem +impl + super::private::Sealed for FileSystem { } -impl< - Platform: sync::RawSyncPrimitivesProvider + DebugLogProvider, - T: transport::Read + transport::Write, -> super::FileSystem for FileSystem +impl + super::FileSystem for FileSystem { #[allow(clippy::similar_names)] fn open( @@ -853,18 +848,11 @@ impl< }; super::DirEntry { - name: String::from_utf8_lossy(e.name.as_bytes()).into_owned(), + name: String::from_utf8_lossy(&e.name).into_owned(), file_type, - #[allow(unused_imports)] ino_info: Some(super::NodeInfo { dev: DEVICE_ID, - ino: usize::try_from(e.qid.path).unwrap_or_else(|_| { - crate::log_println!( - self.litebox.x.platform, - "qid.path doesn't fit in usize, using 0 instead" - ); - 0 - }), + ino: usize::try_from(e.qid.path).expect("inode number exceeds usize"), rdev: None, }), } diff --git a/litebox_shim_linux/src/nine_p.rs b/litebox_shim_linux/src/nine_p.rs index cc9420cc8..2c385efcc 100644 --- a/litebox_shim_linux/src/nine_p.rs +++ b/litebox_shim_linux/src/nine_p.rs @@ -13,10 +13,6 @@ use litebox_common_linux::{ReceiveFlags, SendFlags}; use crate::Task; /// A 9P transport backed by litebox's syscall-level socket APIs. -/// -/// This transport wraps a connected TCP socket (identified by a file descriptor) -/// and implements blocking reads and writes using [`Task::do_recvfrom`] and -/// [`Task::do_sendto`], which handle wait/poll internally. pub struct ShimTransport<'a> { task: &'a Task, sockfd: u32, From 5a9cede85ca5d08ccb3c9cbd47198199d2c00a0c Mon Sep 17 00:00:00 2001 From: weitengchen Date: Tue, 10 Feb 2026 00:10:15 -0800 Subject: [PATCH 08/16] remove some tests --- Cargo.lock | 2 + litebox_shim_linux/src/nine_p.rs | 138 ------------------------------- 2 files changed, 2 insertions(+), 138 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43a59d912..c5f1544c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -853,6 +853,7 @@ dependencies = [ "smoltcp", "spin 0.9.8", "tar-no-std", + "tempfile", "thiserror", "windows-sys 0.60.2", "zerocopy", @@ -1080,6 +1081,7 @@ dependencies = [ "seq-macro", "spin 0.9.8", "syscalls", + "tempfile", "thiserror", "zerocopy", ] diff --git a/litebox_shim_linux/src/nine_p.rs b/litebox_shim_linux/src/nine_p.rs index 2c385efcc..f14e2c723 100644 --- a/litebox_shim_linux/src/nine_p.rs +++ b/litebox_shim_linux/src/nine_p.rs @@ -298,142 +298,4 @@ mod tests { "directory should no longer exist on host" ); } - - #[test] - fn test_tun_nine_p_file_status() { - let task = init_platform(Some(TUN_DEVICE_NAME)); - - let server = DiodServer::start(); - let fs = connect_9p(&task, &server); - - // Create a file with known content. - let fd = fs - .open( - "/status_test.txt", - OFlags::CREAT | OFlags::WRONLY, - Mode::RWXU, - ) - .expect("failed to create file"); - let data = b"1234567890"; - fs.write(&fd, data, None).unwrap(); - fs.close(&fd).unwrap(); - - // Check file_status via path. - let status = fs - .file_status("/status_test.txt") - .expect("failed to stat file"); - assert_eq!( - status.file_type, - litebox::fs::FileType::RegularFile, - "should be a regular file" - ); - assert_eq!(status.size, 10, "file size should be 10 bytes"); - - // Check directory status. - fs.mkdir("/stat_dir", Mode::RWXU).unwrap(); - let status = fs.file_status("/stat_dir").expect("failed to stat dir"); - assert_eq!( - status.file_type, - litebox::fs::FileType::Directory, - "should be a directory" - ); - } - - #[test] - fn test_tun_nine_p_seek_and_partial_read() { - let task = init_platform(Some(TUN_DEVICE_NAME)); - - let server = DiodServer::start(); - let fs = connect_9p(&task, &server); - - // Write a file with known content. - let fd = fs - .open("/seek_test.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) - .expect("failed to create file"); - fs.write(&fd, b"ABCDEFGHIJ", None).unwrap(); - fs.close(&fd).unwrap(); - - // Open for reading and seek. - let fd = fs - .open("/seek_test.txt", OFlags::RDONLY, Mode::empty()) - .expect("failed to open file for reading"); - - let pos = fs - .seek(&fd, 5, litebox::fs::SeekWhence::RelativeToBeginning) - .expect("failed to seek"); - assert_eq!(pos, 5); - - let mut buf = alloc::vec![0u8; 10]; - let n = fs.read(&fd, &mut buf, None).expect("failed to read"); - assert_eq!(&buf[..n], b"FGHIJ"); - - fs.close(&fd).unwrap(); - } - - #[test] - fn test_tun_nine_p_truncate() { - let task = init_platform(Some(TUN_DEVICE_NAME)); - - let server = DiodServer::start(); - let fs = connect_9p(&task, &server); - - // Write a file. - let fd = fs - .open("/trunc_test.txt", OFlags::CREAT | OFlags::RDWR, Mode::RWXU) - .expect("failed to create file"); - fs.write(&fd, b"Hello, World!", None).unwrap(); - - // Truncate to 5 bytes. - fs.truncate(&fd, 5, true) - .expect("failed to truncate via 9P"); - fs.close(&fd).unwrap(); - - // Verify on host. - let content = std::fs::read_to_string(server.export_path().join("trunc_test.txt")).unwrap(); - assert_eq!(content, "Hello"); - } - - #[test] - fn test_tun_nine_p_host_files_visible() { - let task = init_platform(Some(TUN_DEVICE_NAME)); - - let server = DiodServer::start(); - - // Pre-populate files on the host side. - std::fs::write(server.export_path().join("host_file.txt"), "from host").unwrap(); - std::fs::create_dir(server.export_path().join("host_dir")).unwrap(); - std::fs::write( - server.export_path().join("host_dir/inner.txt"), - "inner content", - ) - .unwrap(); - - let fs = connect_9p(&task, &server); - - // Read file created on the host through 9P. - let fd = fs - .open("/host_file.txt", OFlags::RDONLY, Mode::empty()) - .expect("failed to open host file via 9P"); - let mut buf = alloc::vec![0u8; 256]; - let n = fs.read(&fd, &mut buf, None).unwrap(); - assert_eq!(&buf[..n], b"from host"); - fs.close(&fd).unwrap(); - - // List host directory through 9P. - let fd = fs - .open( - "/host_dir", - OFlags::RDONLY | OFlags::DIRECTORY, - Mode::empty(), - ) - .expect("failed to open host dir via 9P"); - let entries = fs.read_dir(&fd).unwrap(); - fs.close(&fd).unwrap(); - - let names: alloc::vec::Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect(); - assert!( - names.contains(&"inner.txt"), - "host_dir should contain 'inner.txt', got: {names:?}" - ); - } } From 8c4d64301b9c0290615da950ae6354182cbe54f4 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Wed, 11 Feb 2026 10:57:10 -0800 Subject: [PATCH 09/16] install diod --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60cea36aa..892071d19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,9 @@ jobs: - name: Install iperf3 run: | sudo apt install -y iperf3 + - name: Install diod + run: | + sudo apt install -y diod - uses: Swatinem/rust-cache@v2 - name: Cache custom out directories uses: actions/cache@v5 @@ -90,6 +93,9 @@ jobs: uses: taiki-e/install-action@v2 with: tool: nextest@${{ env.NEXTEST_VERSION }} + - name: Install diod + run: | + sudo apt install -y diod - name: Set up tun run: | sudo ./litebox_platform_linux_userland/scripts/tun-setup.sh From a078095343784b990c1eac5554381fb5efc74595 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Wed, 11 Feb 2026 10:57:49 -0800 Subject: [PATCH 10/16] revert --- Cargo.lock | 1 - litebox_shim_linux/Cargo.toml | 1 - litebox_shim_linux/src/lib.rs | 1 - litebox_shim_linux/src/nine_p.rs | 301 ------------------------------- 4 files changed, 304 deletions(-) delete mode 100644 litebox_shim_linux/src/nine_p.rs diff --git a/Cargo.lock b/Cargo.lock index c5f1544c3..c1476e34d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1081,7 +1081,6 @@ dependencies = [ "seq-macro", "spin 0.9.8", "syscalls", - "tempfile", "thiserror", "zerocopy", ] diff --git a/litebox_shim_linux/Cargo.toml b/litebox_shim_linux/Cargo.toml index 94d889a7f..6cf93b20c 100644 --- a/litebox_shim_linux/Cargo.toml +++ b/litebox_shim_linux/Cargo.toml @@ -26,7 +26,6 @@ platform_linux_snp = ["litebox_platform_multiplex/platform_linux_snp"] [dev-dependencies] spin = { version = "0.9.8", default-features = false, features = ["spin_mutex"] } libc = "0.2.177" -tempfile = "3" [lints] workspace = true diff --git a/litebox_shim_linux/src/lib.rs b/litebox_shim_linux/src/lib.rs index 56bee5424..f4dccc729 100644 --- a/litebox_shim_linux/src/lib.rs +++ b/litebox_shim_linux/src/lib.rs @@ -43,7 +43,6 @@ macro_rules! log_unsupported { pub(crate) mod channel; pub mod loader; -pub mod nine_p; pub(crate) mod stdio; pub mod syscalls; mod wait; diff --git a/litebox_shim_linux/src/nine_p.rs b/litebox_shim_linux/src/nine_p.rs deleted file mode 100644 index f14e2c723..000000000 --- a/litebox_shim_linux/src/nine_p.rs +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -//! 9P transport implementation using litebox's syscall-level network APIs. -//! -//! This module provides a [`ShimTransport`] that implements the 9P transport traits -//! (`litebox::fs::nine_p::transport::{Read, Write}`) using the `Task::do_*` syscall -//! methods (e.g., `do_socket`, `do_connect`, `do_sendto`, `do_recvfrom`). - -use litebox::fs::nine_p::transport; -use litebox_common_linux::{ReceiveFlags, SendFlags}; - -use crate::Task; - -/// A 9P transport backed by litebox's syscall-level socket APIs. -pub struct ShimTransport<'a> { - task: &'a Task, - sockfd: u32, -} - -impl Drop for ShimTransport<'_> { - fn drop(&mut self) { - let _ = self.task.sys_close(self.sockfd.cast_signed()); - } -} - -impl transport::Read for ShimTransport<'_> { - fn read(&mut self, buf: &mut [u8]) -> Result { - self.task - .do_recvfrom(self.sockfd, buf, ReceiveFlags::empty(), None) - .map_err(|_| transport::ReadError) - } -} - -impl transport::Write for ShimTransport<'_> { - fn write(&mut self, buf: &[u8]) -> Result { - self.task - .do_sendto(self.sockfd, buf, SendFlags::empty(), None) - .map_err(|_| transport::WriteError) - } -} - -// --------------------------------------------------------------------------- -// Tests -// --------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - extern crate std; - - use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; - use std::net::TcpListener; - use std::path::Path; - - use litebox::fs::nine_p; - use litebox::fs::{FileSystem as _, Mode, OFlags}; - use litebox_common_linux::errno::Errno; - use litebox_common_linux::{AddressFamily, SockFlags, SockType}; - - use crate::syscalls::net::SocketAddress; - use crate::syscalls::tests::init_platform; - - use super::*; - - const TUN_DEVICE_NAME: &str = "tun99"; - - // ----------------------------------------------------------------------- - // diod server management (mirrors litebox/src/fs/nine_p/tests.rs) - // ----------------------------------------------------------------------- - - fn find_free_port() -> u16 { - let listener = TcpListener::bind("127.0.0.1:0").expect("failed to bind to port 0"); - listener.local_addr().unwrap().port() - } - - struct DiodServer { - child: std::process::Child, - port: u16, - _export_dir: tempfile::TempDir, - export_path: std::path::PathBuf, - } - - impl DiodServer { - fn start() -> Self { - let export_dir = tempfile::tempdir().expect("failed to create temp dir"); - let export_path = export_dir.path().to_path_buf(); - let port = find_free_port(); - - let child = std::process::Command::new("diod") - .args([ - "--foreground", - "--no-auth", - "--export", - export_dir.path().to_str().unwrap(), - "--listen", - &std::format!("0.0.0.0:{port}"), - "--nwthreads", - "1", - ]) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::piped()) - .spawn() - .expect("failed to start diod – is it installed? (`apt install diod`)"); - - // Give the server time to start listening. - std::thread::sleep(std::time::Duration::from_millis(500)); - - Self { - child, - port, - _export_dir: export_dir, - export_path, - } - } - - fn export_path(&self) -> &Path { - &self.export_path - } - } - - impl Drop for DiodServer { - fn drop(&mut self) { - let _ = self.child.kill(); - let _ = self.child.wait(); - } - } - - // ----------------------------------------------------------------------- - // Test helpers - // ----------------------------------------------------------------------- - - /// Helper to create a `SocketAddr` for connection. - fn socket_addr(ip: [u8; 4], port: u16) -> SocketAddr { - SocketAddr::V4(SocketAddrV4::new( - Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3]), - port, - )) - } - - /// Connect to a 9P server and return a [`ShimTransport`]. - /// - /// This function creates a TCP socket via [`Task::do_socket`], connects it to the - /// given address via [`Task::do_connect`], and returns a transport suitable for use - /// with `litebox::fs::nine_p::FileSystem`. - /// - /// # Arguments - /// * `task` - The task whose syscall APIs will be used for socket operations - /// * `addr` - The socket address of the 9P server - fn connect(task: &Task, addr: core::net::SocketAddr) -> Result, Errno> { - let sockfd = - task.do_socket(AddressFamily::INET, SockType::Stream, SockFlags::empty(), 0)?; - - task.do_connect(sockfd, SocketAddress::Inet(addr))?; - - Ok(ShimTransport { task, sockfd }) - } - - fn connect_9p<'a>( - task: &'a crate::Task, - server: &DiodServer, - ) -> nine_p::FileSystem> { - // The diod server is reachable at the gateway address (10.0.0.1) from the shim's - // network perspective, since the TUN device bridges to the host. - let addr = socket_addr([10, 0, 0, 1], server.port); - let transport = - connect(task, addr).expect("failed to connect to 9P server via shim network"); - - let aname = server.export_path().to_str().unwrap(); - let username = std::env::var("USER") - .or_else(|_| std::env::var("LOGNAME")) - .unwrap_or_else(|_| std::string::String::from("nobody")); - - nine_p::FileSystem::new(&task.global.litebox, transport, 65536, &username, aname) - .expect("failed to create 9P filesystem") - } - - // ----------------------------------------------------------------------- - // Tests (require TUN device + diod) - // ----------------------------------------------------------------------- - - #[test] - fn test_tun_nine_p_create_and_read_file() { - let task = init_platform(Some(TUN_DEVICE_NAME)); - - let server = DiodServer::start(); - let fs = connect_9p(&task, &server); - - // Create a file and write to it. - let fd = fs - .open("/hello.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) - .expect("failed to create file via 9P"); - - let data = b"Hello from litebox shim 9P!"; - let written = fs.write(&fd, data, None).expect("failed to write via 9P"); - assert_eq!(written, data.len()); - fs.close(&fd).expect("failed to close file"); - - // Verify on host. - let host_path = server.export_path().join("hello.txt"); - assert!(host_path.exists(), "file should exist on host"); - let host_content = std::fs::read_to_string(&host_path).unwrap(); - assert_eq!(host_content, "Hello from litebox shim 9P!"); - - // Read back through 9P. - let fd = fs - .open("/hello.txt", OFlags::RDONLY, Mode::empty()) - .expect("failed to open file for reading"); - - let mut buf = alloc::vec![0u8; 256]; - let n = fs.read(&fd, &mut buf, None).expect("failed to read via 9P"); - assert_eq!(&buf[..n], data); - fs.close(&fd).expect("failed to close file"); - } - - #[test] - fn test_tun_nine_p_mkdir_and_readdir() { - let task = init_platform(Some(TUN_DEVICE_NAME)); - - let server = DiodServer::start(); - let fs = connect_9p(&task, &server); - - // Create directories. - fs.mkdir("/subdir", Mode::RWXU) - .expect("failed to mkdir via 9P"); - fs.mkdir("/subdir/nested", Mode::RWXU) - .expect("failed to mkdir nested via 9P"); - - // Create a file inside the subdirectory. - let fd = fs - .open( - "/subdir/file.txt", - OFlags::CREAT | OFlags::WRONLY, - Mode::RWXU, - ) - .expect("failed to create file in subdir"); - fs.write(&fd, b"nested content", None).unwrap(); - fs.close(&fd).unwrap(); - - // Read the root directory. - let fd = fs - .open("/", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()) - .expect("failed to open root dir"); - let entries = fs.read_dir(&fd).expect("failed to readdir root"); - fs.close(&fd).unwrap(); - - let names: alloc::vec::Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect(); - assert!( - names.contains(&"subdir"), - "root should contain 'subdir', got: {names:?}" - ); - - // Read the subdirectory. - let fd = fs - .open("/subdir", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()) - .expect("failed to open subdir"); - let entries = fs.read_dir(&fd).expect("failed to readdir subdir"); - fs.close(&fd).unwrap(); - - let names: alloc::vec::Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect(); - assert!( - names.contains(&"nested"), - "subdir should contain 'nested', got: {names:?}" - ); - assert!( - names.contains(&"file.txt"), - "subdir should contain 'file.txt', got: {names:?}" - ); - } - - #[test] - fn test_tun_nine_p_unlink_and_rmdir() { - let task = init_platform(Some(TUN_DEVICE_NAME)); - - let server = DiodServer::start(); - let fs = connect_9p(&task, &server); - - // Create a file, then delete it. - let fd = fs - .open("/to_delete.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) - .expect("failed to create file"); - fs.close(&fd).unwrap(); - - fs.unlink("/to_delete.txt") - .expect("failed to unlink file via 9P"); - - assert!( - fs.open("/to_delete.txt", OFlags::RDONLY, Mode::empty()) - .is_err(), - "file should no longer exist" - ); - - // Create a directory, then remove it. - fs.mkdir("/to_remove", Mode::RWXU).expect("failed to mkdir"); - fs.rmdir("/to_remove").expect("failed to rmdir via 9P"); - - assert!( - !server.export_path().join("to_remove").exists(), - "directory should no longer exist on host" - ); - } -} From b44c3825aee7a878c432634825cc5e7fedad101a Mon Sep 17 00:00:00 2001 From: weitengchen Date: Wed, 11 Feb 2026 11:33:43 -0800 Subject: [PATCH 11/16] fix fmt --- litebox/src/fs/nine_p/fcall.rs | 2 +- litebox_shim_linux/src/syscalls/tests.rs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/litebox/src/fs/nine_p/fcall.rs b/litebox/src/fs/nine_p/fcall.rs index f8019a80a..1994a0ede 100644 --- a/litebox/src/fs/nine_p/fcall.rs +++ b/litebox/src/fs/nine_p/fcall.rs @@ -29,7 +29,7 @@ pub(super) const NONUNAME: u32 = !0; /// Room for `Twrite`/`Rread` header /// -/// size[4] Tread/Twrite[2] tag[2] fid[4] offset[8] count[4] +/// size\[4\] Tread/Twrite\[2\] tag\[2\] fid\[4\] offset\[8\] count\[4\] pub(super) const IOHDRSZ: u32 = 24; /// Room for readdir header diff --git a/litebox_shim_linux/src/syscalls/tests.rs b/litebox_shim_linux/src/syscalls/tests.rs index e900112b8..695d3f2aa 100644 --- a/litebox_shim_linux/src/syscalls/tests.rs +++ b/litebox_shim_linux/src/syscalls/tests.rs @@ -13,10 +13,6 @@ extern crate std; const TEST_TAR_FILE: &[u8] = include_bytes!("../../../litebox/src/fs/test.tar"); #[must_use] -#[cfg_attr( - not(target_os = "linux"), - expect(unused_variables, reason = "ignored parameter on non-linux platforms") -)] pub(crate) fn init_platform(tun_device_name: Option<&str>) -> crate::Task { static PLATFORM_INIT: std::sync::Once = std::sync::Once::new(); PLATFORM_INIT.call_once(|| { From 6bbb2a4db49c7d3122a28316b66e286213c49ceb Mon Sep 17 00:00:00 2001 From: weitengchen Date: Wed, 11 Feb 2026 13:36:07 -0800 Subject: [PATCH 12/16] refactor code --- litebox/src/fs/nine_p/client.rs | 10 +- litebox/src/fs/nine_p/fcall.rs | 396 +++++++++++--------------------- litebox/src/fs/nine_p/mod.rs | 232 ++++++++++--------- 3 files changed, 258 insertions(+), 380 deletions(-) diff --git a/litebox/src/fs/nine_p/client.rs b/litebox/src/fs/nine_p/client.rs index 143a5f88b..553339b76 100644 --- a/litebox/src/fs/nine_p/client.rs +++ b/litebox/src/fs/nine_p/client.rs @@ -158,7 +158,7 @@ impl Client { /// Send a request and wait for the response /// - /// TODO: support async operations + /// TODO: this is synchronous, will support async operations fn fcall(&self, fcall: Fcall<'_>, f: F) -> Result where F: FnOnce(Fcall<'_>) -> Result, @@ -222,7 +222,7 @@ impl Client { wnames: &[FcallStr], ) -> Result<(Vec, fcall::Fid), Error> { if wnames.len() > fcall::MAXWELEM { - return Err(Error::NameTooLong); + return Err(Error::InvalidPathname); } let new_fid = self.fids.next(); let ret = self.fcall( @@ -274,7 +274,7 @@ impl Client { todo!("symlink"); } let _ = self.clunk(f); - return Err(Error::NotFound); + return Err(Error::Remote(super::ENOENT)); } } Ok((wqids, f)) @@ -426,8 +426,8 @@ impl Client { |response| match response { Fcall::Rreaddir(fcall::Rreaddir { data }) => Ok(data .data - .iter() - .map(super::fcall::DirEntry::clone_static) + .into_iter() + .map(fcall::DirEntry::into_owned) .collect()), Fcall::Rlerror(e) => Err(Error::from(e)), _ => Err(Error::InvalidResponse), diff --git a/litebox/src/fs/nine_p/fcall.rs b/litebox/src/fs/nine_p/fcall.rs index 1994a0ede..8e4fdc68e 100644 --- a/litebox/src/fs/nine_p/fcall.rs +++ b/litebox/src/fs/nine_p/fcall.rs @@ -202,137 +202,115 @@ impl<'a> DirEntryData<'a> { } } -/// 9P message types -#[derive(Copy, Clone, Debug)] -enum FcallType { - // 9P2000.L - Rlerror = 7, - Tstatfs = 8, - Rstatfs = 9, - Tlopen = 12, - Rlopen = 13, - Tlcreate = 14, - Rlcreate = 15, - Tsymlink = 16, - Rsymlink = 17, - Tmknod = 18, - Rmknod = 19, - Trename = 20, - Rrename = 21, - Treadlink = 22, - Rreadlink = 23, - Tgetattr = 24, - Rgetattr = 25, - Tsetattr = 26, - Rsetattr = 27, - Txattrwalk = 30, - Rxattrwalk = 31, - Txattrcreate = 32, - Rxattrcreate = 33, - Treaddir = 40, - Rreaddir = 41, - Tfsync = 50, - Rfsync = 51, - Tlock = 52, - Rlock = 53, - Tgetlock = 54, - Rgetlock = 55, - Tlink = 70, - Rlink = 71, - Tmkdir = 72, - Rmkdir = 73, - Trenameat = 74, - Rrenameat = 75, - Tunlinkat = 76, - Runlinkat = 77, - - // 9P2000 - Tversion = 100, - Rversion = 101, - Tauth = 102, - Rauth = 103, - Tattach = 104, - Rattach = 105, - Tflush = 108, - Rflush = 109, - Twalk = 110, - Rwalk = 111, - Tread = 116, - Rread = 117, - Twrite = 118, - Rwrite = 119, - Tclunk = 120, - Rclunk = 121, - Tremove = 122, - Rremove = 123, -} - -impl FcallType { - /// Convert a u8 to FcallType - fn from_u8(v: u8) -> Option { - match v { - // 9P2000.L - 7 => Some(FcallType::Rlerror), - 8 => Some(FcallType::Tstatfs), - 9 => Some(FcallType::Rstatfs), - 12 => Some(FcallType::Tlopen), - 13 => Some(FcallType::Rlopen), - 14 => Some(FcallType::Tlcreate), - 15 => Some(FcallType::Rlcreate), - 16 => Some(FcallType::Tsymlink), - 17 => Some(FcallType::Rsymlink), - 18 => Some(FcallType::Tmknod), - 19 => Some(FcallType::Rmknod), - 20 => Some(FcallType::Trename), - 21 => Some(FcallType::Rrename), - 22 => Some(FcallType::Treadlink), - 23 => Some(FcallType::Rreadlink), - 24 => Some(FcallType::Tgetattr), - 25 => Some(FcallType::Rgetattr), - 26 => Some(FcallType::Tsetattr), - 27 => Some(FcallType::Rsetattr), - 30 => Some(FcallType::Txattrwalk), - 31 => Some(FcallType::Rxattrwalk), - 32 => Some(FcallType::Txattrcreate), - 33 => Some(FcallType::Rxattrcreate), - 40 => Some(FcallType::Treaddir), - 41 => Some(FcallType::Rreaddir), - 50 => Some(FcallType::Tfsync), - 51 => Some(FcallType::Rfsync), - 52 => Some(FcallType::Tlock), - 53 => Some(FcallType::Rlock), - 54 => Some(FcallType::Tgetlock), - 55 => Some(FcallType::Rgetlock), - 70 => Some(FcallType::Tlink), - 71 => Some(FcallType::Rlink), - 72 => Some(FcallType::Tmkdir), - 73 => Some(FcallType::Rmkdir), - 74 => Some(FcallType::Trenameat), - 75 => Some(FcallType::Rrenameat), - 76 => Some(FcallType::Tunlinkat), - 77 => Some(FcallType::Runlinkat), - - // 9P2000 - 100 => Some(FcallType::Tversion), - 101 => Some(FcallType::Rversion), - 102 => Some(FcallType::Tauth), - 103 => Some(FcallType::Rauth), - 104 => Some(FcallType::Tattach), - 105 => Some(FcallType::Rattach), - 108 => Some(FcallType::Tflush), - 109 => Some(FcallType::Rflush), - 110 => Some(FcallType::Twalk), - 111 => Some(FcallType::Rwalk), - 116 => Some(FcallType::Tread), - 117 => Some(FcallType::Rread), - 118 => Some(FcallType::Twrite), - 119 => Some(FcallType::Rwrite), - 120 => Some(FcallType::Tclunk), - 121 => Some(FcallType::Rclunk), - 122 => Some(FcallType::Tremove), - 123 => Some(FcallType::Rremove), - _ => None, +/// Define a `#[repr($int_ty)]` enum and auto-generate a typed conversion method. +/// +/// # Example +/// ```ignore +/// repr_enum! { +/// #[derive(Copy, Clone, Debug)] +/// enum Color: u8, from_u8 { +/// Red = 1, +/// Green = 2, +/// Blue = 3, +/// } +/// } +/// assert_eq!(Color::from_u8(2), Some(Color::Green)); +/// ``` +macro_rules! repr_enum { + ( + $(#[$meta:meta])* + $vis:vis enum $name:ident : $int_ty:ty, $from_fn:ident { + $( + $(#[$vmeta:meta])* + $variant:ident = $value:expr + ),* $(,)? + } + ) => { + $(#[$meta])* + #[repr($int_ty)] + $vis enum $name { + $( + $(#[$vmeta])* + $variant = $value, + )* + } + + impl $name { + /// Convert a raw integer to the enum, returning `None` for unknown values. + fn $from_fn(v: $int_ty) -> Option { + match v { + $( $value => Some(Self::$variant), )* + _ => None, + } + } } + }; +} + +repr_enum! { + /// 9P message types + #[derive(Copy, Clone, Debug)] + enum FcallType: u8, from_u8 { + // 9P2000.L + Rlerror = 7, + Tstatfs = 8, + Rstatfs = 9, + Tlopen = 12, + Rlopen = 13, + Tlcreate = 14, + Rlcreate = 15, + Tsymlink = 16, + Rsymlink = 17, + Tmknod = 18, + Rmknod = 19, + Trename = 20, + Rrename = 21, + Treadlink = 22, + Rreadlink = 23, + Tgetattr = 24, + Rgetattr = 25, + Tsetattr = 26, + Rsetattr = 27, + Txattrwalk = 30, + Rxattrwalk = 31, + Txattrcreate = 32, + Rxattrcreate = 33, + Treaddir = 40, + Rreaddir = 41, + Tfsync = 50, + Rfsync = 51, + Tlock = 52, + Rlock = 53, + Tgetlock = 54, + Rgetlock = 55, + Tlink = 70, + Rlink = 71, + Tmkdir = 72, + Rmkdir = 73, + Trenameat = 74, + Rrenameat = 75, + Tunlinkat = 76, + Runlinkat = 77, + + // 9P2000 + Tversion = 100, + Rversion = 101, + Tauth = 102, + Rauth = 103, + Tattach = 104, + Rattach = 105, + Tflush = 108, + Rflush = 109, + Twalk = 110, + Rwalk = 111, + Tread = 116, + Rread = 117, + Twrite = 118, + Rwrite = 119, + Tclunk = 120, + Rclunk = 121, + Tremove = 122, + Rremove = 123, } } @@ -405,8 +383,8 @@ pub(super) struct DirEntry<'a> { } impl DirEntry<'_> { - /// Create a static (owned) copy of this directory entry - pub(super) fn clone_static(&self) -> DirEntry<'static> { + /// Create an owned copy of this directory entry. + pub(super) fn into_owned(self) -> DirEntry<'static> { DirEntry { qid: self.qid, offset: self.offset, @@ -946,138 +924,28 @@ impl_from_for_fcall! { Rclunk(Rclunk), Tremove(Tremove), Rremove(Rremove), -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Tattach<'a>) -> Fcall<'a> { - Fcall::Tattach(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Tlcreate<'a>) -> Fcall<'a> { - Fcall::Tlcreate(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Tsymlink<'a>) -> Fcall<'a> { - Fcall::Tsymlink(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Tmknod<'a>) -> Fcall<'a> { - Fcall::Tmknod(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Trename<'a>) -> Fcall<'a> { - Fcall::Trename(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Rreadlink<'a>) -> Fcall<'a> { - Fcall::Rreadlink(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Txattrwalk<'a>) -> Fcall<'a> { - Fcall::Txattrwalk(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Txattrcreate<'a>) -> Fcall<'a> { - Fcall::Txattrcreate(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Rreaddir<'a>) -> Fcall<'a> { - Fcall::Rreaddir(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Tlock<'a>) -> Fcall<'a> { - Fcall::Tlock(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Tgetlock<'a>) -> Fcall<'a> { - Fcall::Tgetlock(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Rgetlock<'a>) -> Fcall<'a> { - Fcall::Rgetlock(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Tlink<'a>) -> Fcall<'a> { - Fcall::Tlink(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Tmkdir<'a>) -> Fcall<'a> { - Fcall::Tmkdir(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Trenameat<'a>) -> Fcall<'a> { - Fcall::Trenameat(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Tunlinkat<'a>) -> Fcall<'a> { - Fcall::Tunlinkat(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Tauth<'a>) -> Fcall<'a> { - Fcall::Tauth(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Tversion<'a>) -> Fcall<'a> { - Fcall::Tversion(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Rversion<'a>) -> Fcall<'a> { - Fcall::Rversion(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Twalk<'a>) -> Fcall<'a> { - Fcall::Twalk(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Rread<'a>) -> Fcall<'a> { - Fcall::Rread(v) - } -} - -impl<'a> From> for Fcall<'a> { - fn from(v: Twrite<'a>) -> Fcall<'a> { - Fcall::Twrite(v) - } + Tattach(Tattach<'a>), + Tlcreate(Tlcreate<'a>), + Tsymlink(Tsymlink<'a>), + Tmknod(Tmknod<'a>), + Trename(Trename<'a>), + Rreadlink(Rreadlink<'a>), + Txattrwalk(Txattrwalk<'a>), + Txattrcreate(Txattrcreate<'a>), + Rreaddir(Rreaddir<'a>), + Tlock(Tlock<'a>), + Tgetlock(Tgetlock<'a>), + Rgetlock(Rgetlock<'a>), + Tlink(Tlink<'a>), + Tmkdir(Tmkdir<'a>), + Trenameat(Trenameat<'a>), + Tunlinkat(Tunlinkat<'a>), + Tauth(Tauth<'a>), + Tversion(Tversion<'a>), + Rversion(Rversion<'a>), + Twalk(Twalk<'a>), + Rread(Rread<'a>), + Twrite(Twrite<'a>), } /// Tagged 9P message diff --git a/litebox/src/fs/nine_p/mod.rs b/litebox/src/fs/nine_p/mod.rs index f76aa3f6f..d727658fb 100644 --- a/litebox/src/fs/nine_p/mod.rs +++ b/litebox/src/fs/nine_p/mod.rs @@ -41,6 +41,20 @@ mod tests; const DEVICE_ID: usize = u32::from_le_bytes(*b"NINE") as usize; +// Common POSIX error codes used when converting remote errors to specific FS error types. +const EPERM: u32 = 1; +const ENOENT: u32 = 2; +const EACCES: u32 = 13; +const EEXIST: u32 = 17; +const ENOTDIR: u32 = 20; +const EISDIR: u32 = 21; +const EINVAL: u32 = 22; +const ESPIPE: u32 = 29; +const ENAMETOOLONG: u32 = 36; +const ENOSYS: u32 = 38; +const ENOTEMPTY: u32 = 39; +const EOPNOTSUPP: u32 = 95; + /// Error type for 9P operations #[derive(Debug, Error)] pub enum Error { @@ -53,45 +67,24 @@ pub enum Error { #[error("Invalid pathname")] InvalidPathname, - #[error("Path not found")] - NotFound, - - #[error("File already exists")] - AlreadyExists, - - #[error("Permission denied")] - PermissionDenied, - - #[error("Not a directory")] - NotADirectory, - - #[error("Is a directory")] - IsADirectory, - - #[error("Directory not empty")] - NotEmpty, - - #[error("Name too long")] - NameTooLong, - - #[error("Operation not supported")] - NotSupported, - - /// Unrecognized remote error code not mapped to a specific variant + /// Error reported by the 9P server, carrying the raw errno #[error("Remote error (errno={0})")] - RemoteError(u32), + Remote(u32), } impl From for OpenError { fn from(e: Error) -> Self { match e { - Error::NotFound => OpenError::PathError(PathError::NoSuchFileOrDirectory), - Error::AlreadyExists => OpenError::AlreadyExists, - Error::PermissionDenied => OpenError::AccessNotAllowed, - Error::NotADirectory => OpenError::PathError(PathError::ComponentNotADirectory), Error::InvalidPathname => OpenError::PathError(PathError::InvalidPathname), - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => OpenError::Io, - _ => unimplemented!("convert {e:?} to OpenError"), + Error::Remote(errno) => match errno { + ENOENT => OpenError::PathError(PathError::NoSuchFileOrDirectory), + EEXIST => OpenError::AlreadyExists, + EPERM | EACCES => OpenError::AccessNotAllowed, + ENOTDIR => OpenError::PathError(PathError::ComponentNotADirectory), + ENAMETOOLONG => OpenError::PathError(PathError::InvalidPathname), + _ => OpenError::Io, + }, + Error::Io | Error::InvalidResponse => OpenError::Io, } } } @@ -99,10 +92,12 @@ impl From for OpenError { impl From for ReadError { fn from(e: Error) -> Self { match e { - Error::NotFound | Error::IsADirectory => ReadError::NotAFile, - Error::PermissionDenied => ReadError::NotForReading, - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => ReadError::Io, - _ => unimplemented!("convert {e:?} to ReadError"), + Error::Remote(errno) => match errno { + ENOENT | EISDIR => ReadError::NotAFile, + EPERM | EACCES => ReadError::NotForReading, + _ => ReadError::Io, + }, + Error::Io | Error::InvalidResponse | Error::InvalidPathname => ReadError::Io, } } } @@ -110,10 +105,12 @@ impl From for ReadError { impl From for WriteError { fn from(e: Error) -> Self { match e { - Error::NotFound | Error::IsADirectory => WriteError::NotAFile, - Error::PermissionDenied => WriteError::NotForWriting, - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => WriteError::Io, - _ => unimplemented!("convert {e:?} to WriteError"), + Error::Remote(errno) => match errno { + ENOENT | EISDIR => WriteError::NotAFile, + EPERM | EACCES => WriteError::NotForWriting, + _ => WriteError::Io, + }, + Error::Io | Error::InvalidResponse | Error::InvalidPathname => WriteError::Io, } } } @@ -121,13 +118,16 @@ impl From for WriteError { impl From for MkdirError { fn from(e: Error) -> Self { match e { - Error::NotFound => MkdirError::PathError(PathError::NoSuchFileOrDirectory), - Error::AlreadyExists => MkdirError::AlreadyExists, - Error::PermissionDenied => MkdirError::NoWritePerms, - Error::NotADirectory => MkdirError::PathError(PathError::ComponentNotADirectory), Error::InvalidPathname => MkdirError::PathError(PathError::InvalidPathname), - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => MkdirError::Io, - _ => unimplemented!("convert {e:?} to MkdirError"), + Error::Remote(errno) => match errno { + ENOENT => MkdirError::PathError(PathError::NoSuchFileOrDirectory), + EEXIST => MkdirError::AlreadyExists, + EPERM | EACCES => MkdirError::NoWritePerms, + ENOTDIR => MkdirError::PathError(PathError::ComponentNotADirectory), + ENAMETOOLONG => MkdirError::PathError(PathError::InvalidPathname), + _ => MkdirError::Io, + }, + Error::Io | Error::InvalidResponse => MkdirError::Io, } } } @@ -135,9 +135,11 @@ impl From for MkdirError { impl From for ReadDirError { fn from(e: Error) -> Self { match e { - Error::NotFound | Error::NotADirectory => ReadDirError::NotADirectory, - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => ReadDirError::Io, - _ => unimplemented!("convert {e:?} to ReadDirError"), + Error::Remote(errno) => match errno { + ENOENT | ENOTDIR => ReadDirError::NotADirectory, + _ => ReadDirError::Io, + }, + Error::Io | Error::InvalidResponse | Error::InvalidPathname => ReadDirError::Io, } } } @@ -145,13 +147,16 @@ impl From for ReadDirError { impl From for UnlinkError { fn from(e: Error) -> Self { match e { - Error::NotFound => UnlinkError::PathError(PathError::NoSuchFileOrDirectory), - Error::IsADirectory => UnlinkError::IsADirectory, - Error::PermissionDenied => UnlinkError::NoWritePerms, - Error::NotADirectory => UnlinkError::PathError(PathError::ComponentNotADirectory), Error::InvalidPathname => UnlinkError::PathError(PathError::InvalidPathname), - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => UnlinkError::Io, - _ => unimplemented!("convert {e:?} to UnlinkError"), + Error::Remote(errno) => match errno { + ENOENT => UnlinkError::PathError(PathError::NoSuchFileOrDirectory), + EISDIR => UnlinkError::IsADirectory, + EPERM | EACCES => UnlinkError::NoWritePerms, + ENOTDIR => UnlinkError::PathError(PathError::ComponentNotADirectory), + ENAMETOOLONG => UnlinkError::PathError(PathError::InvalidPathname), + _ => UnlinkError::Io, + }, + Error::Io | Error::InvalidResponse => UnlinkError::Io, } } } @@ -159,13 +164,16 @@ impl From for UnlinkError { impl From for RmdirError { fn from(e: Error) -> Self { match e { - Error::NotFound => RmdirError::PathError(PathError::NoSuchFileOrDirectory), - Error::NotADirectory => RmdirError::NotADirectory, - Error::PermissionDenied => RmdirError::NoWritePerms, Error::InvalidPathname => RmdirError::PathError(PathError::InvalidPathname), - Error::NotEmpty => RmdirError::NotEmpty, - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => RmdirError::Io, - _ => unimplemented!("convert {e:?} to RmdirError"), + Error::Remote(errno) => match errno { + ENOENT => RmdirError::PathError(PathError::NoSuchFileOrDirectory), + ENOTDIR => RmdirError::NotADirectory, + EPERM | EACCES => RmdirError::NoWritePerms, + ENAMETOOLONG => RmdirError::PathError(PathError::InvalidPathname), + ENOTEMPTY => RmdirError::NotEmpty, + _ => RmdirError::Io, + }, + Error::Io | Error::InvalidResponse => RmdirError::Io, } } } @@ -173,11 +181,20 @@ impl From for RmdirError { impl From for FileStatusError { fn from(e: Error) -> Self { match e { - Error::NotFound => FileStatusError::PathError(PathError::NoSuchFileOrDirectory), Error::InvalidPathname => FileStatusError::PathError(PathError::InvalidPathname), - Error::NotADirectory => FileStatusError::PathError(PathError::ComponentNotADirectory), - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => FileStatusError::Io, - _ => unimplemented!("convert {e:?} to FileStatusError"), + Error::Remote(errno) => match errno { + ENOENT => FileStatusError::PathError(PathError::NoSuchFileOrDirectory), + ENAMETOOLONG => FileStatusError::PathError(PathError::InvalidPathname), + ENOTDIR => FileStatusError::PathError(PathError::ComponentNotADirectory), + EPERM | EACCES => FileStatusError::PathError(PathError::NoSearchPerms { + #[cfg(debug_assertions)] + dir: String::new(), + #[cfg(debug_assertions)] + perms: super::Mode::empty(), + }), + _ => FileStatusError::Io, + }, + Error::Io | Error::InvalidResponse => FileStatusError::Io, } } } @@ -185,9 +202,13 @@ impl From for FileStatusError { impl From for SeekError { fn from(e: Error) -> Self { match e { - Error::NotFound => SeekError::ClosedFd, - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => SeekError::Io, - _ => unimplemented!("convert {e:?} to SeekError"), + Error::Remote(e) => match e { + ENOENT => SeekError::ClosedFd, + EINVAL => SeekError::InvalidOffset, + ESPIPE => SeekError::NonSeekable, + _ => SeekError::Io, + }, + _ => SeekError::Io, } } } @@ -195,11 +216,13 @@ impl From for SeekError { impl From for TruncateError { fn from(e: Error) -> Self { match e { - Error::NotFound => TruncateError::ClosedFd, - Error::IsADirectory => TruncateError::IsDirectory, - Error::PermissionDenied => TruncateError::NotForWriting, - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => TruncateError::Io, - _ => unimplemented!("convert {e:?} to TruncateError"), + Error::Remote(errno) => match errno { + ENOENT => TruncateError::ClosedFd, + EISDIR => TruncateError::IsDirectory, + EPERM | EACCES => TruncateError::NotForWriting, + _ => TruncateError::Io, + }, + Error::Io | Error::InvalidResponse | Error::InvalidPathname => TruncateError::Io, } } } @@ -207,12 +230,14 @@ impl From for TruncateError { impl From for ChmodError { fn from(e: Error) -> Self { match e { - Error::NotFound => ChmodError::PathError(PathError::NoSuchFileOrDirectory), Error::InvalidPathname => ChmodError::PathError(PathError::InvalidPathname), - Error::NotADirectory => ChmodError::PathError(PathError::ComponentNotADirectory), - Error::PermissionDenied => ChmodError::NotTheOwner, - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => ChmodError::Io, - _ => unimplemented!("convert {e:?} to ChmodError"), + Error::Remote(errno) => match errno { + ENOENT => ChmodError::PathError(PathError::NoSuchFileOrDirectory), + ENOTDIR => ChmodError::PathError(PathError::ComponentNotADirectory), + EPERM | EACCES => ChmodError::NotTheOwner, + _ => ChmodError::Io, + }, + Error::Io | Error::InvalidResponse => ChmodError::Io, } } } @@ -220,44 +245,21 @@ impl From for ChmodError { impl From for ChownError { fn from(e: Error) -> Self { match e { - Error::NotFound => ChownError::PathError(PathError::NoSuchFileOrDirectory), Error::InvalidPathname => ChownError::PathError(PathError::InvalidPathname), - Error::NotADirectory => ChownError::PathError(PathError::ComponentNotADirectory), - Error::PermissionDenied => ChownError::NotTheOwner, - Error::Io | Error::InvalidResponse | Error::RemoteError(_) => ChownError::Io, - _ => unimplemented!("convert {e:?} to ChownError"), + Error::Remote(errno) => match errno { + ENOENT => ChownError::PathError(PathError::NoSuchFileOrDirectory), + ENOTDIR => ChownError::PathError(PathError::ComponentNotADirectory), + EPERM | EACCES => ChownError::NotTheOwner, + _ => ChownError::Io, + }, + Error::Io | Error::InvalidResponse => ChownError::Io, } } } -/// Convert remote error code to our Error type impl From for Error { fn from(err: Rlerror) -> Self { - // Common POSIX error codes - const EPERM: u32 = 1; - const ENOENT: u32 = 2; - const EIO: u32 = 5; - const EACCES: u32 = 13; - const EEXIST: u32 = 17; - const ENOTDIR: u32 = 20; - const EISDIR: u32 = 21; - const ENAMETOOLONG: u32 = 36; - const ENOSYS: u32 = 38; - const ENOTEMPTY: u32 = 39; - const EOPNOTSUPP: u32 = 95; - - match err.ecode { - EPERM | EACCES => Error::PermissionDenied, - ENOENT => Error::NotFound, - EIO => Error::Io, - EEXIST => Error::AlreadyExists, - ENOTDIR => Error::NotADirectory, - EISDIR => Error::IsADirectory, - ENAMETOOLONG => Error::NameTooLong, - ENOSYS | EOPNOTSUPP => Error::NotSupported, - ENOTEMPTY => Error::NotEmpty, - _ => Error::RemoteError(err.ecode), - } + Error::Remote(err.ecode) } } @@ -518,9 +520,9 @@ impl Drop + for FileSystem +{ + fn drop(&mut self) { + let _ = self.client.clunk(self.root.1); + } +} + impl super::private::Sealed for FileSystem { From 333c449e3c49c9e209e1a05ff4eb63010da08d13 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Wed, 11 Feb 2026 13:43:14 -0800 Subject: [PATCH 13/16] add debug --- litebox/src/fs/nine_p/tests.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/litebox/src/fs/nine_p/tests.rs b/litebox/src/fs/nine_p/tests.rs index 2d32382ff..138443c53 100644 --- a/litebox/src/fs/nine_p/tests.rs +++ b/litebox/src/fs/nine_p/tests.rs @@ -75,6 +75,8 @@ impl DiodServer { &std::format!("127.0.0.1:{port}"), "--nwthreads", "1", + "-d", + "100000", ]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::piped()) @@ -107,6 +109,13 @@ impl Drop for DiodServer { fn drop(&mut self) { let _ = self.child.kill(); let _ = self.child.wait(); + if let Some(mut stderr) = self.child.stderr.take() { + let mut output = std::string::String::new(); + let _ = stderr.read_to_string(&mut output); + if !output.is_empty() { + std::eprintln!("--- diod stderr ---\n{output}\n--- end diod stderr ---"); + } + } } } From 5cd4fc973fca91ee576c0c2db028cefe16184092 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Wed, 11 Feb 2026 13:48:16 -0800 Subject: [PATCH 14/16] update comment --- litebox/src/fs/nine_p/client.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/litebox/src/fs/nine_p/client.rs b/litebox/src/fs/nine_p/client.rs index 553339b76..759e5d555 100644 --- a/litebox/src/fs/nine_p/client.rs +++ b/litebox/src/fs/nine_p/client.rs @@ -157,8 +157,6 @@ impl Client { } /// Send a request and wait for the response - /// - /// TODO: this is synchronous, will support async operations fn fcall(&self, fcall: Fcall<'_>, f: F) -> Result where F: FnOnce(Fcall<'_>) -> Result, @@ -176,6 +174,7 @@ impl Client { let mut rbuf = self.rbuf.lock(); // Loop until we get a response with matching tag (in case of stale responses) + // TODO: support concurrent requests by allowing out-of-order responses and matching tags accordingly loop { let response = transport::read_message(transport, &mut rbuf)?; if response.tag == tag { From d5e5adc45259608bf22304285a1af8c1e73e8995 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Wed, 11 Feb 2026 14:04:22 -0800 Subject: [PATCH 15/16] revert change --- litebox_shim_linux/src/syscalls/net.rs | 264 +++++++++++++++-------- litebox_shim_linux/src/syscalls/tests.rs | 24 +-- 2 files changed, 184 insertions(+), 104 deletions(-) diff --git a/litebox_shim_linux/src/syscalls/net.rs b/litebox_shim_linux/src/syscalls/net.rs index 8450c6b98..2617afd5a 100644 --- a/litebox_shim_linux/src/syscalls/net.rs +++ b/litebox_shim_linux/src/syscalls/net.rs @@ -915,7 +915,7 @@ impl Task { })?; self.do_socket(domain, ty, flags, protocol) } - pub(crate) fn do_socket( + fn do_socket( &self, domain: AddressFamily, ty: SockType, @@ -1267,7 +1267,7 @@ impl Task { let sockaddr = read_sockaddr_from_user(sockaddr, addrlen)?; self.do_connect(fd, sockaddr) } - pub(crate) fn do_connect(&self, sockfd: u32, sockaddr: SocketAddress) -> Result<(), Errno> { + fn do_connect(&self, sockfd: u32, sockaddr: SocketAddress) -> Result<(), Errno> { self.files.borrow().with_socket( sockfd, |fd| { @@ -1339,16 +1339,17 @@ impl Task { let sockaddr = addr .map(|addr| read_sockaddr_from_user(addr, addrlen as usize)) .transpose()?; - let buf = buf.to_owned_slice(len).ok_or(Errno::EFAULT)?; - self.do_sendto(fd, &buf, flags, sockaddr) + self.do_sendto(fd, buf, len, flags, sockaddr) } - pub(crate) fn do_sendto( + fn do_sendto( &self, sockfd: u32, - buf: &[u8], + buf: ConstPtr, + len: usize, flags: SendFlags, sockaddr: Option, ) -> Result { + let buf = buf.to_owned_slice(len).ok_or(Errno::EFAULT)?; self.files.borrow().with_socket( sockfd, |fd| { @@ -1357,14 +1358,14 @@ impl Task { .map(|addr| addr.inet().ok_or(Errno::EAFNOSUPPORT)) .transpose()?; self.global - .sendto(&self.wait_cx(), fd, buf, flags, sockaddr) + .sendto(&self.wait_cx(), fd, &buf, flags, sockaddr) }, |file| { let addr = sockaddr .clone() .map(|addr| addr.unix().ok_or(Errno::EAFNOSUPPORT)) .transpose()?; - file.sendto(self, buf, flags, addr) + file.sendto(self, &buf, flags, addr) }, ) } @@ -1443,16 +1444,14 @@ impl Task { addr: Option>, addrlen: MutPtr, ) -> Result { - const MAX_LEN: usize = 4096; let Ok(sockfd) = u32::try_from(fd) else { return Err(Errno::EBADF); }; let mut source_addr = None; - let mut buffer = [0u8; MAX_LEN]; - let recv_buf = &mut buffer[..MAX_LEN.min(len)]; let size = self.do_recvfrom( sockfd, - recv_buf, + buf, + len, flags, if addr.is_some() { Some(&mut source_addr) @@ -1460,8 +1459,6 @@ impl Task { None }, )?; - buf.copy_from_slice(0, &recv_buf[..size.min(recv_buf.len())]) - .ok_or(Errno::EFAULT)?; if let Some(src_addr) = source_addr && let Some(sock_ptr) = addr { @@ -1469,53 +1466,57 @@ impl Task { } Ok(size) } - pub(crate) fn do_recvfrom( + fn do_recvfrom( &self, sockfd: u32, - buf: &mut [u8], + buf: MutPtr, + len: usize, flags: ReceiveFlags, source_addr: Option<&mut Option>, ) -> Result { let want_source = source_addr.is_some(); - let files = self.files.borrow(); - let file_table = files.file_descriptors.read(); - let (size, addr) = match file_table.get_fd(sockfd).ok_or(Errno::EBADF)? { - Descriptor::LiteBoxRawFd(raw_fd) => { - let raw_fd = *raw_fd; - drop(file_table); - files.with_socket_fd(raw_fd, |fd| { - let mut addr = None; - let size = self.global.receive( - &self.wait_cx(), - fd, - buf, - flags, - if want_source { Some(&mut addr) } else { None }, - )?; - let src_addr = addr.map(SocketAddress::Inet); - Ok((size, src_addr)) - })? - } - Descriptor::Unix { file, .. } => { - let file = file.clone(); - drop(file_table); + let (size, addr) = self.files.borrow().with_socket( + sockfd, + |fd| { + const MAX_LEN: usize = 4096; + let mut buffer: [u8; MAX_LEN] = [0; MAX_LEN]; + let buffer: &mut [u8] = &mut buffer[..MAX_LEN.min(len)]; + let mut addr = None; + let size = self.global.receive( + &self.wait_cx(), + fd, + buffer, + flags, + if want_source { Some(&mut addr) } else { None }, + )?; + let src_addr = addr.map(SocketAddress::Inet); + if !flags.contains(ReceiveFlags::TRUNC) { + assert!(size <= len, "{size} should be smaller than {len}"); + } + buf.copy_from_slice(0, &buffer[..size.min(buffer.len())]) + .ok_or(Errno::EFAULT)?; + Ok((size, src_addr)) + }, + |file| { + const MAX_LEN: usize = 4096; + let mut buffer: [u8; MAX_LEN] = [0; MAX_LEN]; + let buffer: &mut [u8] = &mut buffer[..MAX_LEN.min(len)]; let mut addr = None; let size = file.recvfrom( &self.wait_cx(), - buf, + buffer, flags, if want_source { Some(&mut addr) } else { None }, )?; let src_addr = addr.map(SocketAddress::Unix); - (size, src_addr) - } - _ => return Err(Errno::ENOTSOCK), - }; - - if !flags.contains(ReceiveFlags::TRUNC) { - let len = buf.len(); - assert!(size <= len, "{size} should be smaller than {len}"); - } + if !flags.contains(ReceiveFlags::TRUNC) { + assert!(size <= len, "{size} should be smaller than {len}"); + } + buf.copy_from_slice(0, &buffer[..size.min(buffer.len())]) + .ok_or(Errno::EFAULT)?; + Ok((size, src_addr)) + }, + )?; if let (Some(source_addr), Some(addr)) = (source_addr, addr) { *source_addr = Some(addr); @@ -1845,17 +1846,37 @@ mod tests { }; use super::SocketAddress; - use crate::{ConstPtr, MutPtr, syscalls::tests::init_platform}; + use crate::{ConstPtr, MutPtr}; extern crate alloc; extern crate std; const TUN_IP_ADDR: [u8; 4] = [10, 0, 0, 2]; const TUN_IP_ADDR_STR: &str = "10.0.0.2"; - const TUN_DEVICE_NAME: &str = "tun99"; const SERVER_PORT: u16 = 8080; const CLIENT_PORT: u16 = 8081; + fn init_platform(tun_device_name: Option<&str>) -> crate::Task { + let task = crate::syscalls::tests::init_platform(tun_device_name); + let global = task.global.clone(); + if tun_device_name.is_some() { + // Start a background thread to perform network interaction + // Naive implementation for testing purpose only + std::thread::spawn(move || { + loop { + while global + .net + .lock() + .perform_platform_interaction() + .call_again_immediately() + {} + core::hint::spin_loop(); + } + }); + } + task + } + fn close_socket(task: &crate::Task, fd: u32) { task.sys_close(i32::try_from(fd).unwrap()) .expect("close socket failed"); @@ -1981,8 +2002,9 @@ mod tests { match option { "sendto" => { + let ptr = ConstPtr::from_usize(buf.as_ptr().expose_provenance()); let n = task - .do_sendto(client_fd, buf.as_bytes(), SendFlags::empty(), None) + .do_sendto(client_fd, ptr, buf.len(), SendFlags::empty(), None) .expect("Failed to send data"); assert_eq!(n, buf.len()); let output = child_handle @@ -2038,10 +2060,12 @@ mod tests { } } let mut recv_buf = [0u8; 48]; + let recv_ptr = crate::MutPtr::from_usize(recv_buf.as_mut_ptr() as usize); let n = task .do_recvfrom( client_fd, - &mut recv_buf, + recv_ptr, + recv_buf.len(), if test_trunc { ReceiveFlags::TRUNC } else { @@ -2071,12 +2095,12 @@ mod tests { test_trunc: bool, option: &'static str, ) { - let task = init_platform(Some(TUN_DEVICE_NAME)); + let task = init_platform(Some("tun99")); test_tcp_socket_as_server(&task, TUN_IP_ADDR, port, is_nonblocking, test_trunc, option); } fn test_tcp_socket_send(is_nonblocking: bool, test_trunc: bool) { - let task = init_platform(Some(TUN_DEVICE_NAME)); + let task = init_platform(Some("tun99")); test_tcp_socket_as_server( &task, TUN_IP_ADDR, @@ -2122,7 +2146,7 @@ mod tests { #[test] fn test_tun_tcp_connection_refused() { - let task = init_platform(Some(TUN_DEVICE_NAME)); + let task = init_platform(Some("tun99")); let socket_fd = task .do_socket(AddressFamily::INET, SockType::Stream, SockFlags::empty(), 0) .expect("failed to create socket"); @@ -2145,7 +2169,7 @@ mod tests { #[test] fn test_tun_tcp_socket_as_client() { - let task = init_platform(Some(TUN_DEVICE_NAME)); + let task = init_platform(Some("tun99")); let child_handle = std::thread::spawn(|| { std::process::Command::new("nc") @@ -2173,10 +2197,12 @@ mod tests { .expect("failed to connect to server"); let buf = "Hello, world!"; + let ptr = ConstPtr::from_usize(buf.as_ptr().expose_provenance()); + let len = buf.len(); let n = task - .do_sendto(client_fd, buf.as_bytes(), SendFlags::empty(), None) + .do_sendto(client_fd, ptr, len, SendFlags::empty(), None) .unwrap(); - assert_eq!(n, buf.len()); + assert_eq!(n, len); let linger = litebox_common_linux::Linger { onoff: 1, // enable linger @@ -2202,7 +2228,7 @@ mod tests { } fn blocking_udp_server_socket(test_trunc: bool, is_nonblocking: bool) { - let task = init_platform(Some(TUN_DEVICE_NAME)); + let task = init_platform(Some("tun99")); // Server socket and bind let server_fd = task @@ -2262,6 +2288,7 @@ mod tests { // Server receives and inspects sender addr let mut recv_buf = [0u8; 48]; + let recv_ptr = crate::MutPtr::from_usize(recv_buf.as_mut_ptr() as usize); let mut sender_addr = None; let mut recv_flags = ReceiveFlags::empty(); if test_trunc { @@ -2277,15 +2304,15 @@ mod tests { assert_eq!(fd, server_fd); } } - let recv_len = if test_trunc { - 8 // intentionally small size to test truncation - } else { - recv_buf.len() - }; let n = task .do_recvfrom( server_fd, - &mut recv_buf[..recv_len], + recv_ptr, + if test_trunc { + 8 // intentionally small size to test truncation + } else { + recv_buf.len() + }, recv_flags, Some(&mut sender_addr), ) @@ -2326,7 +2353,7 @@ mod tests { fn test_tun_udp_client_socket_without_server() { // We do not support loopback yet, so this test only checks that // the client can send packets without a server. - let task = init_platform(Some(TUN_DEVICE_NAME)); + let task = init_platform(Some("tun99")); // Client socket and explicit bind let client_fd = task @@ -2345,9 +2372,11 @@ mod tests { // Send from client to server let msg = "Hello without connect()"; + let msg_ptr = ConstPtr::from_usize(msg.as_ptr().expose_provenance()); task.do_sendto( client_fd, - msg.as_bytes(), + msg_ptr, + msg.len(), SendFlags::empty(), Some(server_addr.clone()), ) @@ -2367,7 +2396,8 @@ mod tests { // Now client can send without specifying addr let msg = "Hello with connect()"; - task.do_sendto(client_fd, msg.as_bytes(), SendFlags::empty(), None) + let msg_ptr = ConstPtr::from_usize(msg.as_ptr().expose_provenance()); + task.do_sendto(client_fd, msg_ptr, msg.len(), SendFlags::empty(), None) .expect("failed to sendto"); close_socket(&task, client_fd); @@ -2375,7 +2405,7 @@ mod tests { #[test] fn test_tun_tcp_sockopt() { - let task = init_platform(Some(TUN_DEVICE_NAME)); + let task = init_platform(Some("tun99")); let sockfd = task .do_socket(AddressFamily::INET, SockType::Stream, SockFlags::empty(), 0) .expect("failed to create socket"); @@ -2513,7 +2543,8 @@ mod unix_tests { let n = task .do_sendto( server_fd, - msg1.as_bytes(), + ConstPtr::from_usize(msg1.as_ptr() as usize), + msg1.len(), SendFlags::empty(), Some(client_addr.clone()), ) @@ -2525,7 +2556,8 @@ mod unix_tests { let n = task .do_recvfrom( client_fd, - &mut buf, + MutPtr::from_usize(buf.as_mut_ptr() as usize), + buf.len(), ReceiveFlags::empty(), Some(&mut source), ) @@ -2539,7 +2571,8 @@ mod unix_tests { let n = task .do_sendto( client_fd, - msg2.as_bytes(), + ConstPtr::from_usize(msg2.as_ptr() as usize), + msg2.len(), SendFlags::empty(), Some(server_addr), ) @@ -2551,7 +2584,8 @@ mod unix_tests { let n = task .do_recvfrom( server_fd, - &mut buf, + MutPtr::from_usize(buf.as_mut_ptr() as usize), + buf.len(), ReceiveFlags::empty(), Some(&mut source), ) @@ -2593,18 +2627,36 @@ mod unix_tests { )); let msg1 = "Hello, "; let n = task - .do_sendto(server_conn, msg1.as_bytes(), SendFlags::empty(), None) + .do_sendto( + server_conn, + ConstPtr::from_usize(msg1.as_ptr() as usize), + msg1.len(), + SendFlags::empty(), + None, + ) .expect("sendto failed"); assert_eq!(n, msg1.len()); let msg2 = "world!"; let n = task - .do_sendto(server_conn, msg2.as_bytes(), SendFlags::empty(), None) + .do_sendto( + server_conn, + ConstPtr::from_usize(msg2.as_ptr() as usize), + msg2.len(), + SendFlags::empty(), + None, + ) .expect("sendto failed"); assert_eq!(n, msg2.len()); let mut buf = [0u8; 64]; let n = task - .do_recvfrom(client_fd, &mut buf, ReceiveFlags::empty(), None) + .do_recvfrom( + client_fd, + MutPtr::from_usize(buf.as_mut_ptr() as usize), + buf.len(), + ReceiveFlags::empty(), + None, + ) .expect("recvfrom failed"); assert_eq!(n, msg1.len() + msg2.len()); assert_eq!(&buf[..n], b"Hello, world!"); @@ -2704,7 +2756,13 @@ mod unix_tests { for (i, client_fd) in client_fds.iter().enumerate() { let msg = alloc::format!("message from connection {i}"); let n = task - .do_sendto(*client_fd, msg.as_bytes(), SendFlags::empty(), None) + .do_sendto( + *client_fd, + ConstPtr::from_usize(msg.as_ptr() as usize), + msg.len(), + SendFlags::empty(), + None, + ) .expect("sendto failed"); assert_eq!(n, msg.len()); } @@ -2740,7 +2798,13 @@ mod unix_tests { ppoll(&task, *server_conn_fd, Events::IN); } let n = task - .do_recvfrom(*server_conn_fd, &mut buf, ReceiveFlags::empty(), None) + .do_recvfrom( + *server_conn_fd, + MutPtr::from_usize(buf.as_mut_ptr() as usize), + buf.len(), + ReceiveFlags::empty(), + None, + ) .expect("recvfrom failed"); assert_eq!(n, msg.len()); assert_eq!(&buf[..n], msg.as_bytes()); @@ -2871,7 +2935,13 @@ mod unix_tests { ppoll(&task, sock2, Events::IN); } let n = task - .do_recvfrom(sock2, &mut buf, ReceiveFlags::empty(), None) + .do_recvfrom( + sock2, + MutPtr::from_usize(buf.as_mut_ptr() as usize), + buf.len(), + ReceiveFlags::empty(), + None, + ) .expect("recvfrom failed"); assert_eq!(&buf[..n], b"Message from sock1"); }); @@ -2879,8 +2949,14 @@ mod unix_tests { std::thread::sleep(core::time::Duration::from_millis(100)); // Send from sock1 to sock2 let msg1 = "Message from sock1"; - task.do_sendto(sock1, msg1.as_bytes(), SendFlags::empty(), None) - .expect("sendto failed"); + task.do_sendto( + sock1, + ConstPtr::from_usize(msg1.as_ptr().expose_provenance()), + msg1.len(), + SendFlags::empty(), + None, + ) + .expect("sendto failed"); task.spawn_clone_for_test(move |task| { // Receive on sock1 (from sock2) @@ -2889,7 +2965,13 @@ mod unix_tests { ppoll(&task, sock1, Events::IN); } let n = task - .do_recvfrom(sock1, &mut buf, ReceiveFlags::empty(), None) + .do_recvfrom( + sock1, + MutPtr::from_usize(buf.as_mut_ptr() as usize), + buf.len(), + ReceiveFlags::empty(), + None, + ) .expect("recvfrom failed"); assert_eq!(&buf[..n], b"Message from sock2"); }); @@ -2897,8 +2979,14 @@ mod unix_tests { std::thread::sleep(core::time::Duration::from_millis(100)); // Send from sock2 to sock1 let msg2 = "Message from sock2"; - task.do_sendto(sock2, msg2.as_bytes(), SendFlags::empty(), None) - .expect("sendto failed"); + task.do_sendto( + sock2, + ConstPtr::from_usize(msg2.as_ptr().expose_provenance()), + msg2.len(), + SendFlags::empty(), + None, + ) + .expect("sendto failed"); std::thread::sleep(core::time::Duration::from_millis(500)); close_socket(&task, sock1); @@ -2932,7 +3020,13 @@ mod unix_tests { let mut buf = [0u8; 16]; let start = std::time::Instant::now(); let err = task - .do_recvfrom(sock1, &mut buf, ReceiveFlags::empty(), None) + .do_recvfrom( + sock1, + MutPtr::from_usize(buf.as_mut_ptr() as usize), + buf.len(), + ReceiveFlags::empty(), + None, + ) .unwrap_err(); let elapsed = start.elapsed(); assert_eq!(err, Errno::ETIMEDOUT); diff --git a/litebox_shim_linux/src/syscalls/tests.rs b/litebox_shim_linux/src/syscalls/tests.rs index 695d3f2aa..e6c7e95c5 100644 --- a/litebox_shim_linux/src/syscalls/tests.rs +++ b/litebox_shim_linux/src/syscalls/tests.rs @@ -13,6 +13,10 @@ extern crate std; const TEST_TAR_FILE: &[u8] = include_bytes!("../../../litebox/src/fs/test.tar"); #[must_use] +#[cfg_attr( + not(target_os = "linux"), + expect(unused_variables, reason = "ignored parameter on non-linux platforms") +)] pub(crate) fn init_platform(tun_device_name: Option<&str>) -> crate::Task { static PLATFORM_INIT: std::sync::Once = std::sync::Once::new(); PLATFORM_INIT.call_once(|| { @@ -34,25 +38,7 @@ pub(crate) fn init_platform(tun_device_name: Option<&str>) -> crate::Task { }); let tar_ro_fs = litebox::fs::tar_ro::FileSystem::new(litebox, TEST_TAR_FILE.into()); shim_builder.set_fs(shim_builder.default_fs(in_mem_fs, tar_ro_fs)); - let task = shim_builder.build().0.new_test_task(); - - let global = task.global.clone(); - if tun_device_name.is_some() { - // Start a background thread to perform network interaction - // Naive implementation for testing purpose only - std::thread::spawn(move || { - loop { - while global - .net - .lock() - .perform_platform_interaction() - .call_again_immediately() - {} - core::hint::spin_loop(); - } - }); - } - task + shim_builder.build().0.new_test_task() } #[test] From 28d252acd5fc514e11ad870dcbd68df525f09e24 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Wed, 11 Feb 2026 14:15:16 -0800 Subject: [PATCH 16/16] add error handling --- litebox/src/fs/nine_p/tests.rs | 276 ++++++++++++++++++++++++++ litebox_common_linux/src/errno/mod.rs | 7 + 2 files changed, 283 insertions(+) diff --git a/litebox/src/fs/nine_p/tests.rs b/litebox/src/fs/nine_p/tests.rs index 138443c53..197a0840f 100644 --- a/litebox/src/fs/nine_p/tests.rs +++ b/litebox/src/fs/nine_p/tests.rs @@ -7,6 +7,10 @@ use std::io::{Read as _, Write as _}; use std::net::{TcpListener, TcpStream}; use std::path::Path; +use crate::fs::errors::{ + FileStatusError, MkdirError, OpenError, ReadDirError, ReadError, RmdirError, SeekError, + TruncateError, UnlinkError, WriteError, +}; use crate::fs::{FileSystem as _, Mode, OFlags}; use crate::platform::mock::MockPlatform; @@ -397,3 +401,275 @@ fn test_nine_p_host_files_visible() { "host_dir should contain 'inner.txt', got: {names:?}" ); } + +// --------------------------------------------------------------------------- +// Broken-connection transport: wraps TcpTransport and breaks after N writes +// --------------------------------------------------------------------------- + +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + +/// A transport wrapper that allows a fixed number of write-message calls to +/// succeed, then fails all subsequent I/O. This simulates a connection that +/// breaks in the middle of a session. +/// +/// Reads are only failed once a write has actually been rejected, so the +/// response to the last successful write is still received. +struct BrokenTransport { + inner: TcpTransport, + /// Number of `write` calls remaining before the connection "breaks". + remaining_writes: AtomicUsize, + /// Set to `true` once a write has been rejected. + broken: AtomicBool, +} + +impl BrokenTransport { + /// Create a new `BrokenTransport` that allows `allowed_writes` successful + /// `write` calls before all I/O starts failing. + fn new(inner: TcpTransport, allowed_writes: usize) -> Self { + Self { + inner, + remaining_writes: AtomicUsize::new(allowed_writes), + broken: AtomicBool::new(false), + } + } +} + +impl transport::Read for BrokenTransport { + fn read(&mut self, buf: &mut [u8]) -> Result { + if self.broken.load(Ordering::SeqCst) { + return Err(transport::ReadError); + } + self.inner.read(buf) + } +} + +impl transport::Write for BrokenTransport { + fn write(&mut self, buf: &[u8]) -> Result { + if self.remaining_writes.load(Ordering::SeqCst) == 0 { + self.broken.store(true, Ordering::SeqCst); + return Err(transport::WriteError); + } + self.remaining_writes.fetch_sub(1, Ordering::SeqCst); + self.inner.write(buf) + } +} + +/// Helper: connect to a diod server and build a `FileSystem` backed by +/// `BrokenTransport` that will break after `allowed_writes` write calls. +/// +/// The version handshake and attach each consume one write, so +/// `allowed_writes` must be >= 2 for the filesystem to be constructed +/// successfully. Any FS operation after construction will consume one +/// additional write. +fn connect_9p_broken( + litebox: &crate::LiteBox, + server: &DiodServer, + allowed_writes: usize, +) -> super::FileSystem { + let tcp = TcpTransport::connect(&server.addr()); + let transport = BrokenTransport::new(tcp, allowed_writes); + let aname = server.export_path().to_str().unwrap(); + let username = std::env::var("USER") + .or_else(|_| std::env::var("LOGNAME")) + .unwrap_or_else(|_| std::string::String::from("nobody")); + super::FileSystem::new(litebox, transport, 65536, &username, aname) + .expect("failed to create 9P filesystem (broken transport)") +} + +// --------------------------------------------------------------------------- +// Broken-connection failure tests +// --------------------------------------------------------------------------- + +/// Opening a file should fail with an I/O-class error when the connection +/// breaks after the filesystem has been attached. +#[test] +fn test_nine_p_broken_open() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + // 2 writes: version + attach. The next write (open's walk) will fail. + let fs = connect_9p_broken(&litebox, &server, 2); + + let result = fs.open("/anything.txt", OFlags::RDONLY, Mode::empty()); + assert!(matches!(result, Err(OpenError::Io))); +} + +/// Creating a file should fail when the connection is broken. +#[test] +fn test_nine_p_broken_create() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + let fs = connect_9p_broken(&litebox, &server, 2); + + let result = fs.open("/new.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU); + assert!(matches!(result, Err(OpenError::Io))); +} + +/// Reading from an fd obtained before the break should fail. +#[test] +fn test_nine_p_broken_read() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + + // Pre-create a file via normal connection + { + let fs = connect_9p(&litebox, &server); + let fd = fs + .open("/read_me.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) + .unwrap(); + fs.write(&fd, b"data", None).unwrap(); + fs.close(&fd).unwrap(); + } + + // 4 writes: version + attach + walk + lopen. Then read will fail. + let fs = connect_9p_broken(&litebox, &server, 4); + let fd = fs + .open("/read_me.txt", OFlags::RDONLY, Mode::empty()) + .expect("open should succeed before break"); + + let mut buf = alloc::vec![0u8; 64]; + let result = fs.read(&fd, &mut buf, None); + assert!(matches!(result, Err(ReadError::Io))); +} + +/// Writing to an fd obtained before the break should fail. +#[test] +fn test_nine_p_broken_write() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + + // 4 writes: version + attach + walk + lopen. Then write will fail. + let fs = connect_9p_broken(&litebox, &server, 4); + let fd = fs + .open("/write_me.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) + .expect("create should succeed before break"); + + let result = fs.write(&fd, b"data", None); + assert!(matches!(result, Err(WriteError::Io))); +} + +/// mkdir should fail when the connection is broken. +#[test] +fn test_nine_p_broken_mkdir() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + let fs = connect_9p_broken(&litebox, &server, 2); + + let result = fs.mkdir("/broken_dir", Mode::RWXU); + assert!(matches!(result, Err(MkdirError::Io))); +} + +/// readdir should fail when the connection breaks during the directory read. +#[test] +fn test_nine_p_broken_readdir() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + + // 4 writes: version + attach + walk + lopen for the directory. + let fs = connect_9p_broken(&litebox, &server, 4); + let fd = fs + .open("/", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()) + .expect("open dir should succeed before break"); + + let result = fs.read_dir(&fd); + assert!(matches!(result, Err(ReadDirError::Io))); +} + +/// unlink should fail when the connection is broken. +#[test] +fn test_nine_p_broken_unlink() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + + // Pre-create a file + { + let fs = connect_9p(&litebox, &server); + let fd = fs + .open("/to_unlink.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) + .unwrap(); + fs.close(&fd).unwrap(); + } + + let fs = connect_9p_broken(&litebox, &server, 2); + let result = fs.unlink("/to_unlink.txt"); + assert!(matches!(result, Err(UnlinkError::Io))); +} + +/// rmdir should fail when the connection is broken. +#[test] +fn test_nine_p_broken_rmdir() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + + // Pre-create a directory + { + let fs = connect_9p(&litebox, &server); + fs.mkdir("/to_rmdir", Mode::RWXU).unwrap(); + } + + let fs = connect_9p_broken(&litebox, &server, 2); + let result = fs.rmdir("/to_rmdir"); + assert!(matches!(result, Err(RmdirError::Io))); +} + +/// file_status should fail when the connection is broken. +#[test] +fn test_nine_p_broken_file_status() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + let fs = connect_9p_broken(&litebox, &server, 2); + + let result = fs.file_status("/"); + assert!(matches!(result, Err(FileStatusError::Io))); +} + +/// truncate should fail when the connection breaks after open. +#[test] +fn test_nine_p_broken_truncate() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + + // Pre-create a file + { + let fs = connect_9p(&litebox, &server); + let fd = fs + .open("/to_trunc.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) + .unwrap(); + fs.write(&fd, b"some data", None).unwrap(); + fs.close(&fd).unwrap(); + } + + // 4 writes: version + attach + walk + lopen. Then truncate will fail. + let fs = connect_9p_broken(&litebox, &server, 4); + let fd = fs + .open("/to_trunc.txt", OFlags::RDWR, Mode::empty()) + .expect("open should succeed before break"); + + let result = fs.truncate(&fd, 0, true); + assert!(matches!(result, Err(TruncateError::Io))); +} + +/// seek (RelativeToEnd, which requires a getattr) should fail when broken. +#[test] +fn test_nine_p_broken_seek() { + let litebox = crate::LiteBox::new(MockPlatform::new()); + let server = DiodServer::start(); + + // Pre-create a file + { + let fs = connect_9p(&litebox, &server); + let fd = fs + .open("/to_seek.txt", OFlags::CREAT | OFlags::WRONLY, Mode::RWXU) + .unwrap(); + fs.write(&fd, b"data", None).unwrap(); + fs.close(&fd).unwrap(); + } + + // 4 writes: version + attach + walk + lopen. Then the getattr for seek will fail. + let fs = connect_9p_broken(&litebox, &server, 4); + let fd = fs + .open("/to_seek.txt", OFlags::RDONLY, Mode::empty()) + .expect("open should succeed before break"); + + let result = fs.seek(&fd, -1, crate::fs::SeekWhence::RelativeToEnd); + assert!(matches!(result, Err(SeekError::Io))); +} diff --git a/litebox_common_linux/src/errno/mod.rs b/litebox_common_linux/src/errno/mod.rs index 06290f2a5..15109c374 100644 --- a/litebox_common_linux/src/errno/mod.rs +++ b/litebox_common_linux/src/errno/mod.rs @@ -131,6 +131,7 @@ impl From for Errno { litebox::fs::errors::OpenError::PathError(path_error) => path_error.into(), litebox::fs::errors::OpenError::ReadOnlyFileSystem => Errno::EROFS, litebox::fs::errors::OpenError::AlreadyExists => Errno::EEXIST, + litebox::fs::errors::OpenError::Io => Errno::EIO, _ => unimplemented!(), } } @@ -142,6 +143,7 @@ impl From for Errno { litebox::fs::errors::UnlinkError::NoWritePerms => Errno::EACCES, litebox::fs::errors::UnlinkError::IsADirectory => Errno::EISDIR, litebox::fs::errors::UnlinkError::ReadOnlyFileSystem => Errno::EROFS, + litebox::fs::errors::UnlinkError::Io => Errno::EIO, litebox::fs::errors::UnlinkError::PathError(path_error) => path_error.into(), _ => unimplemented!(), } @@ -156,6 +158,7 @@ impl From for Errno { litebox::fs::errors::RmdirError::NotEmpty => Errno::ENOTEMPTY, litebox::fs::errors::RmdirError::NotADirectory => Errno::ENOTDIR, litebox::fs::errors::RmdirError::ReadOnlyFileSystem => Errno::EROFS, + litebox::fs::errors::RmdirError::Io => Errno::EIO, litebox::fs::errors::RmdirError::PathError(path_error) => path_error.into(), _ => unimplemented!(), } @@ -185,6 +188,7 @@ impl From for Errno { match value { litebox::fs::errors::ReadError::NotAFile => Errno::EISDIR, litebox::fs::errors::ReadError::NotForReading => Errno::EACCES, + litebox::fs::errors::ReadError::Io => Errno::EIO, _ => unimplemented!(), } } @@ -195,6 +199,7 @@ impl From for Errno { match value { litebox::fs::errors::WriteError::NotAFile => Errno::EISDIR, litebox::fs::errors::WriteError::NotForWriting => Errno::EACCES, + litebox::fs::errors::WriteError::Io => Errno::EIO, _ => unimplemented!(), } } @@ -208,6 +213,7 @@ impl From for Errno { } litebox::fs::errors::SeekError::InvalidOffset => Errno::EINVAL, litebox::fs::errors::SeekError::NonSeekable => Errno::ESPIPE, + litebox::fs::errors::SeekError::Io => Errno::EIO, _ => unimplemented!(), } } @@ -220,6 +226,7 @@ impl From for Errno { litebox::fs::errors::MkdirError::AlreadyExists => Errno::EEXIST, litebox::fs::errors::MkdirError::ReadOnlyFileSystem => Errno::EROFS, litebox::fs::errors::MkdirError::NoWritePerms => Errno::EACCES, + litebox::fs::errors::MkdirError::Io => Errno::EIO, _ => unimplemented!(), } }