681 lines
23 KiB
Rust
681 lines
23 KiB
Rust
//! 9P2000 server implementation.
|
|
//!
|
|
//! Handles the 9P protocol and maps operations to the virtual filesystem.
|
|
|
|
use crate::virtfs::FsNode;
|
|
use nine::p2000::*;
|
|
use std::collections::HashMap;
|
|
use std::io::{self, Read, Write};
|
|
use std::sync::RwLock;
|
|
use std::hash::{Hash, Hasher};
|
|
use std::collections::hash_map::DefaultHasher;
|
|
|
|
/// Maximum message size.
|
|
const MSIZE: u32 = 8192;
|
|
|
|
/// 9P protocol version.
|
|
const VERSION: &str = "9P2000";
|
|
|
|
/// Fid state: tracks open files.
|
|
struct Fid {
|
|
path: String,
|
|
node: FsNode,
|
|
open_mode: Option<OpenMode>,
|
|
dir_offset: usize, // For directory reads
|
|
read_cache: Vec<u8>, // Cached content for reads
|
|
write_buf: Vec<u8>, // Buffer for writes
|
|
}
|
|
|
|
/// 9P server state.
|
|
pub struct Server {
|
|
root: FsNode,
|
|
fids: RwLock<HashMap<u32, Fid>>,
|
|
msize: u32,
|
|
}
|
|
|
|
impl Server {
|
|
/// Create a new 9P server with the given root filesystem.
|
|
pub fn new(root: FsNode) -> Self {
|
|
Self {
|
|
root,
|
|
fids: RwLock::new(HashMap::new()),
|
|
msize: MSIZE,
|
|
}
|
|
}
|
|
|
|
/// Serve a single connection.
|
|
pub fn serve<R: Read, W: Write>(&self, mut reader: R, mut writer: W) -> io::Result<()> {
|
|
let mut buf = vec![0u8; MSIZE as usize];
|
|
|
|
loop {
|
|
// Read message size (4 bytes)
|
|
let mut size_buf = [0u8; 4];
|
|
match reader.read_exact(&mut size_buf) {
|
|
Ok(_) => {}
|
|
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(()),
|
|
Err(e) => return Err(e),
|
|
}
|
|
|
|
let size = u32::from_le_bytes(size_buf) as usize;
|
|
if size < 7 || size > self.msize as usize {
|
|
return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid message size"));
|
|
}
|
|
|
|
// Read rest of message
|
|
buf.resize(size, 0);
|
|
buf[0..4].copy_from_slice(&size_buf);
|
|
reader.read_exact(&mut buf[4..size])?;
|
|
|
|
// Parse and handle message
|
|
let response = self.handle_message(&buf[..size])?;
|
|
|
|
// Write response
|
|
writer.write_all(&response)?;
|
|
writer.flush()?;
|
|
}
|
|
}
|
|
|
|
/// Handle a single 9P message.
|
|
fn handle_message(&self, data: &[u8]) -> io::Result<Vec<u8>> {
|
|
if data.len() < 7 {
|
|
return self.error_response(0, "message too short");
|
|
}
|
|
|
|
let msg_type = data[4];
|
|
let tag = u16::from_le_bytes([data[5], data[6]]);
|
|
|
|
let result = match msg_type {
|
|
100 => self.handle_tversion(data, tag),
|
|
102 => self.handle_tauth(tag),
|
|
104 => self.handle_tattach(data, tag),
|
|
110 => self.handle_twalk(data, tag),
|
|
112 => self.handle_topen(data, tag),
|
|
114 => self.handle_tcreate(data, tag),
|
|
116 => self.handle_tread(data, tag),
|
|
118 => self.handle_twrite(data, tag),
|
|
120 => self.handle_tclunk(data, tag),
|
|
122 => self.handle_tremove(data, tag),
|
|
124 => self.handle_tstat(data, tag),
|
|
126 => self.handle_twstat(data, tag),
|
|
108 => self.handle_tflush(tag),
|
|
_ => self.error_response(tag, &format!("unknown message type: {}", msg_type)),
|
|
};
|
|
|
|
result
|
|
}
|
|
|
|
/// Create an error response.
|
|
fn error_response(&self, tag: u16, msg: &str) -> io::Result<Vec<u8>> {
|
|
let mut resp = Vec::new();
|
|
resp.extend_from_slice(&[0u8; 4]); // size placeholder
|
|
resp.push(107); // Rerror
|
|
resp.extend_from_slice(&tag.to_le_bytes());
|
|
|
|
// String: 2-byte length + data
|
|
let msg_bytes = msg.as_bytes();
|
|
resp.extend_from_slice(&(msg_bytes.len() as u16).to_le_bytes());
|
|
resp.extend_from_slice(msg_bytes);
|
|
|
|
// Fill in size
|
|
let size = resp.len() as u32;
|
|
resp[0..4].copy_from_slice(&size.to_le_bytes());
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
/// Tversion handler.
|
|
fn handle_tversion(&self, data: &[u8], tag: u16) -> io::Result<Vec<u8>> {
|
|
// Parse client msize and version
|
|
if data.len() < 13 {
|
|
return self.error_response(tag, "tversion too short");
|
|
}
|
|
|
|
let client_msize = u32::from_le_bytes([data[7], data[8], data[9], data[10]]);
|
|
let msize = client_msize.min(self.msize);
|
|
|
|
// Build Rversion
|
|
let mut resp = Vec::new();
|
|
resp.extend_from_slice(&[0u8; 4]); // size placeholder
|
|
resp.push(101); // Rversion
|
|
resp.extend_from_slice(&tag.to_le_bytes());
|
|
resp.extend_from_slice(&msize.to_le_bytes());
|
|
|
|
let version = VERSION.as_bytes();
|
|
resp.extend_from_slice(&(version.len() as u16).to_le_bytes());
|
|
resp.extend_from_slice(version);
|
|
|
|
let size = resp.len() as u32;
|
|
resp[0..4].copy_from_slice(&size.to_le_bytes());
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
/// Tauth handler (no auth required).
|
|
fn handle_tauth(&self, tag: u16) -> io::Result<Vec<u8>> {
|
|
self.error_response(tag, "no authentication required")
|
|
}
|
|
|
|
/// Tflush handler.
|
|
fn handle_tflush(&self, tag: u16) -> io::Result<Vec<u8>> {
|
|
// Just acknowledge the flush
|
|
let mut resp = Vec::new();
|
|
resp.extend_from_slice(&[0u8; 4]);
|
|
resp.push(109); // Rflush
|
|
resp.extend_from_slice(&tag.to_le_bytes());
|
|
|
|
let size = resp.len() as u32;
|
|
resp[0..4].copy_from_slice(&size.to_le_bytes());
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
/// Tattach handler.
|
|
fn handle_tattach(&self, data: &[u8], tag: u16) -> io::Result<Vec<u8>> {
|
|
if data.len() < 19 {
|
|
return self.error_response(tag, "tattach too short");
|
|
}
|
|
|
|
let fid = u32::from_le_bytes([data[7], data[8], data[9], data[10]]);
|
|
// afid at 11-14, uname and aname follow
|
|
|
|
// Create root fid
|
|
let mut fids = self.fids.write().unwrap();
|
|
fids.insert(fid, Fid {
|
|
path: "/".to_string(),
|
|
node: self.root.clone(),
|
|
open_mode: None,
|
|
dir_offset: 0,
|
|
read_cache: Vec::new(),
|
|
write_buf: Vec::new(),
|
|
});
|
|
|
|
// Build Rattach
|
|
let qid = self.node_qid(&self.root, "/");
|
|
self.qid_response(105, tag, &qid)
|
|
}
|
|
|
|
/// Twalk handler.
|
|
fn handle_twalk(&self, data: &[u8], tag: u16) -> io::Result<Vec<u8>> {
|
|
if data.len() < 17 {
|
|
return self.error_response(tag, "twalk too short");
|
|
}
|
|
|
|
let fid = u32::from_le_bytes([data[7], data[8], data[9], data[10]]);
|
|
let newfid = u32::from_le_bytes([data[11], data[12], data[13], data[14]]);
|
|
let nwname = u16::from_le_bytes([data[15], data[16]]) as usize;
|
|
|
|
// Parse path elements
|
|
let mut offset = 17;
|
|
let mut wnames = Vec::new();
|
|
for _ in 0..nwname {
|
|
if offset + 2 > data.len() {
|
|
return self.error_response(tag, "twalk truncated");
|
|
}
|
|
let len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
|
|
offset += 2;
|
|
if offset + len > data.len() {
|
|
return self.error_response(tag, "twalk truncated");
|
|
}
|
|
let name = String::from_utf8_lossy(&data[offset..offset + len]).to_string();
|
|
offset += len;
|
|
wnames.push(name);
|
|
}
|
|
|
|
// Get source fid
|
|
let fids = self.fids.read().unwrap();
|
|
let source = match fids.get(&fid) {
|
|
Some(f) => f,
|
|
None => return self.error_response(tag, "unknown fid"),
|
|
};
|
|
|
|
// Walk the path
|
|
let mut current_path = source.path.clone();
|
|
let mut current_node = source.node.clone();
|
|
let mut qids = Vec::new();
|
|
|
|
for name in &wnames {
|
|
if name == ".." {
|
|
// Go up one level
|
|
if let Some(pos) = current_path.rfind('/') {
|
|
if pos > 0 {
|
|
current_path = current_path[..pos].to_string();
|
|
} else {
|
|
current_path = "/".to_string();
|
|
}
|
|
}
|
|
// Re-walk from root
|
|
current_node = match self.root.walk(¤t_path[1..]) {
|
|
Some(n) => n,
|
|
None => return self.error_response(tag, "walk failed"),
|
|
};
|
|
} else {
|
|
// Walk to child
|
|
let new_node = match current_node.walk(name) {
|
|
Some(n) => n,
|
|
None => {
|
|
// Partial walk - return qids so far
|
|
break;
|
|
}
|
|
};
|
|
if current_path == "/" {
|
|
current_path = format!("/{}", name);
|
|
} else {
|
|
current_path = format!("{}/{}", current_path, name);
|
|
}
|
|
current_node = new_node;
|
|
}
|
|
qids.push(self.node_qid(¤t_node, ¤t_path));
|
|
}
|
|
|
|
drop(fids);
|
|
|
|
// If we walked all names, create/update newfid
|
|
if qids.len() == wnames.len() {
|
|
let mut fids = self.fids.write().unwrap();
|
|
fids.insert(newfid, Fid {
|
|
path: current_path,
|
|
node: current_node,
|
|
open_mode: None,
|
|
dir_offset: 0,
|
|
read_cache: Vec::new(),
|
|
write_buf: Vec::new(),
|
|
});
|
|
}
|
|
|
|
// Build Rwalk
|
|
let mut resp = Vec::new();
|
|
resp.extend_from_slice(&[0u8; 4]);
|
|
resp.push(111); // Rwalk
|
|
resp.extend_from_slice(&tag.to_le_bytes());
|
|
resp.extend_from_slice(&(qids.len() as u16).to_le_bytes());
|
|
for qid in &qids {
|
|
self.encode_qid(&mut resp, qid);
|
|
}
|
|
|
|
let size = resp.len() as u32;
|
|
resp[0..4].copy_from_slice(&size.to_le_bytes());
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
/// Topen handler.
|
|
fn handle_topen(&self, data: &[u8], tag: u16) -> io::Result<Vec<u8>> {
|
|
if data.len() < 12 {
|
|
return self.error_response(tag, "topen too short");
|
|
}
|
|
|
|
let fid = u32::from_le_bytes([data[7], data[8], data[9], data[10]]);
|
|
let mode = OpenMode::from_bits_truncate(data[11]);
|
|
|
|
let mut fids = self.fids.write().unwrap();
|
|
let f = match fids.get_mut(&fid) {
|
|
Some(f) => f,
|
|
None => return self.error_response(tag, "unknown fid"),
|
|
};
|
|
|
|
// Check permissions
|
|
let node_mode = f.node.mode;
|
|
if mode.is_readable() && (node_mode & 0o444) == 0 && !f.node.is_dir {
|
|
return self.error_response(tag, "permission denied");
|
|
}
|
|
if mode.is_writable() && (node_mode & 0o222) == 0 {
|
|
return self.error_response(tag, "permission denied");
|
|
}
|
|
|
|
f.open_mode = Some(mode);
|
|
f.dir_offset = 0;
|
|
f.read_cache.clear();
|
|
f.write_buf.clear();
|
|
|
|
let qid = self.node_qid(&f.node, &f.path);
|
|
let iounit = self.msize - 24; // Leave room for headers
|
|
|
|
// Build Ropen
|
|
let mut resp = Vec::new();
|
|
resp.extend_from_slice(&[0u8; 4]);
|
|
resp.push(113); // Ropen
|
|
resp.extend_from_slice(&tag.to_le_bytes());
|
|
self.encode_qid(&mut resp, &qid);
|
|
resp.extend_from_slice(&iounit.to_le_bytes());
|
|
|
|
let size = resp.len() as u32;
|
|
resp[0..4].copy_from_slice(&size.to_le_bytes());
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
/// Tcreate handler.
|
|
fn handle_tcreate(&self, _data: &[u8], tag: u16) -> io::Result<Vec<u8>> {
|
|
self.error_response(tag, "create not supported")
|
|
}
|
|
|
|
/// Tread handler.
|
|
fn handle_tread(&self, data: &[u8], tag: u16) -> io::Result<Vec<u8>> {
|
|
if data.len() < 23 {
|
|
return self.error_response(tag, "tread too short");
|
|
}
|
|
|
|
let fid = u32::from_le_bytes([data[7], data[8], data[9], data[10]]);
|
|
let offset = u64::from_le_bytes([
|
|
data[11], data[12], data[13], data[14],
|
|
data[15], data[16], data[17], data[18],
|
|
]) as usize;
|
|
let count = u32::from_le_bytes([data[19], data[20], data[21], data[22]]) as usize;
|
|
|
|
let mut fids = self.fids.write().unwrap();
|
|
let f = match fids.get_mut(&fid) {
|
|
Some(f) => f,
|
|
None => return self.error_response(tag, "unknown fid"),
|
|
};
|
|
|
|
if f.open_mode.is_none() {
|
|
return self.error_response(tag, "not open");
|
|
}
|
|
|
|
let read_data = if f.node.is_dir {
|
|
// Directory read
|
|
self.read_dir(f, offset, count)?
|
|
} else {
|
|
// File read
|
|
if f.read_cache.is_empty() {
|
|
f.read_cache = f.node.read().unwrap_or_default();
|
|
}
|
|
|
|
if offset >= f.read_cache.len() {
|
|
Vec::new()
|
|
} else {
|
|
let end = (offset + count).min(f.read_cache.len());
|
|
f.read_cache[offset..end].to_vec()
|
|
}
|
|
};
|
|
|
|
// Build Rread
|
|
let mut resp = Vec::new();
|
|
resp.extend_from_slice(&[0u8; 4]);
|
|
resp.push(117); // Rread
|
|
resp.extend_from_slice(&tag.to_le_bytes());
|
|
resp.extend_from_slice(&(read_data.len() as u32).to_le_bytes());
|
|
resp.extend_from_slice(&read_data);
|
|
|
|
let size = resp.len() as u32;
|
|
resp[0..4].copy_from_slice(&size.to_le_bytes());
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
/// Read directory entries.
|
|
fn read_dir(&self, fid: &mut Fid, offset: usize, count: usize) -> io::Result<Vec<u8>> {
|
|
// Get all entries and encode them
|
|
if fid.read_cache.is_empty() || offset == 0 {
|
|
fid.read_cache.clear();
|
|
let entries = fid.node.list().unwrap_or_default();
|
|
|
|
for name in entries {
|
|
if let Some(child) = fid.node.walk(&name) {
|
|
let child_path = if fid.path == "/" {
|
|
format!("/{}", name)
|
|
} else {
|
|
format!("{}/{}", fid.path, name)
|
|
};
|
|
let stat = self.node_stat(&child, &child_path, &name);
|
|
self.encode_stat(&mut fid.read_cache, &stat);
|
|
}
|
|
}
|
|
}
|
|
|
|
if offset >= fid.read_cache.len() {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
let end = (offset + count).min(fid.read_cache.len());
|
|
Ok(fid.read_cache[offset..end].to_vec())
|
|
}
|
|
|
|
/// Twrite handler.
|
|
fn handle_twrite(&self, data: &[u8], tag: u16) -> io::Result<Vec<u8>> {
|
|
if data.len() < 23 {
|
|
return self.error_response(tag, "twrite too short");
|
|
}
|
|
|
|
let fid = u32::from_le_bytes([data[7], data[8], data[9], data[10]]);
|
|
let _offset = u64::from_le_bytes([
|
|
data[11], data[12], data[13], data[14],
|
|
data[15], data[16], data[17], data[18],
|
|
]);
|
|
let count = u32::from_le_bytes([data[19], data[20], data[21], data[22]]) as usize;
|
|
|
|
if data.len() < 23 + count {
|
|
return self.error_response(tag, "twrite truncated");
|
|
}
|
|
|
|
let write_data = &data[23..23 + count];
|
|
|
|
let mut fids = self.fids.write().unwrap();
|
|
let f = match fids.get_mut(&fid) {
|
|
Some(f) => f,
|
|
None => return self.error_response(tag, "unknown fid"),
|
|
};
|
|
|
|
let mode = match f.open_mode {
|
|
Some(m) => m,
|
|
None => return self.error_response(tag, "not open"),
|
|
};
|
|
|
|
if !mode.is_writable() {
|
|
return self.error_response(tag, "not open for writing");
|
|
}
|
|
|
|
// Check if this is rdwr mode (has rdwr handler)
|
|
if f.node.rdwr_fn.is_some() {
|
|
// For rdwr files, write triggers immediate response
|
|
match f.node.rdwr(write_data) {
|
|
Ok(response) => {
|
|
f.read_cache = response;
|
|
}
|
|
Err(e) => return self.error_response(tag, &e.to_string()),
|
|
}
|
|
} else if f.node.write_fn.is_some() {
|
|
// Buffer writes for regular write files
|
|
// Clear read cache since content will change
|
|
f.read_cache.clear();
|
|
f.write_buf.extend_from_slice(write_data);
|
|
} else {
|
|
return self.error_response(tag, "not writable");
|
|
}
|
|
|
|
// Build Rwrite
|
|
let mut resp = Vec::new();
|
|
resp.extend_from_slice(&[0u8; 4]);
|
|
resp.push(119); // Rwrite
|
|
resp.extend_from_slice(&tag.to_le_bytes());
|
|
resp.extend_from_slice(&(count as u32).to_le_bytes());
|
|
|
|
let size = resp.len() as u32;
|
|
resp[0..4].copy_from_slice(&size.to_le_bytes());
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
/// Tclunk handler.
|
|
fn handle_tclunk(&self, data: &[u8], tag: u16) -> io::Result<Vec<u8>> {
|
|
if data.len() < 11 {
|
|
return self.error_response(tag, "tclunk too short");
|
|
}
|
|
|
|
let fid = u32::from_le_bytes([data[7], data[8], data[9], data[10]]);
|
|
|
|
let mut fids = self.fids.write().unwrap();
|
|
if let Some(f) = fids.remove(&fid) {
|
|
// Flush write buffer if not rdwr mode
|
|
if !f.write_buf.is_empty() && f.node.rdwr_fn.is_none() {
|
|
if let Err(e) = f.node.write(&f.write_buf) {
|
|
return self.error_response(tag, &e.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build Rclunk
|
|
let mut resp = Vec::new();
|
|
resp.extend_from_slice(&[0u8; 4]);
|
|
resp.push(121); // Rclunk
|
|
resp.extend_from_slice(&tag.to_le_bytes());
|
|
|
|
let size = resp.len() as u32;
|
|
resp[0..4].copy_from_slice(&size.to_le_bytes());
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
/// Tremove handler.
|
|
fn handle_tremove(&self, _data: &[u8], tag: u16) -> io::Result<Vec<u8>> {
|
|
self.error_response(tag, "remove not supported")
|
|
}
|
|
|
|
/// Tstat handler.
|
|
fn handle_tstat(&self, data: &[u8], tag: u16) -> io::Result<Vec<u8>> {
|
|
if data.len() < 11 {
|
|
return self.error_response(tag, "tstat too short");
|
|
}
|
|
|
|
let fid = u32::from_le_bytes([data[7], data[8], data[9], data[10]]);
|
|
|
|
let fids = self.fids.read().unwrap();
|
|
let f = match fids.get(&fid) {
|
|
Some(f) => f,
|
|
None => return self.error_response(tag, "unknown fid"),
|
|
};
|
|
|
|
let name = f.path.rsplit('/').next().unwrap_or("");
|
|
let name = if name.is_empty() { "/" } else { name };
|
|
let stat = self.node_stat(&f.node, &f.path, name);
|
|
|
|
// Build Rstat
|
|
let mut stat_buf = Vec::new();
|
|
self.encode_stat(&mut stat_buf, &stat);
|
|
|
|
let mut resp = Vec::new();
|
|
resp.extend_from_slice(&[0u8; 4]);
|
|
resp.push(125); // Rstat
|
|
resp.extend_from_slice(&tag.to_le_bytes());
|
|
resp.extend_from_slice(&(stat_buf.len() as u16).to_le_bytes());
|
|
resp.extend_from_slice(&stat_buf);
|
|
|
|
let size = resp.len() as u32;
|
|
resp[0..4].copy_from_slice(&size.to_le_bytes());
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
/// Twstat handler.
|
|
fn handle_twstat(&self, _data: &[u8], tag: u16) -> io::Result<Vec<u8>> {
|
|
self.error_response(tag, "wstat not supported")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// Helpers
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
/// Generate a Qid for a node.
|
|
fn node_qid(&self, node: &FsNode, path: &str) -> Qid {
|
|
let file_type = if node.is_dir {
|
|
FileType::DIR
|
|
} else {
|
|
FileType::FILE
|
|
};
|
|
|
|
let mut hasher = DefaultHasher::new();
|
|
path.hash(&mut hasher);
|
|
let path_hash = hasher.finish();
|
|
|
|
Qid {
|
|
file_type,
|
|
version: 0,
|
|
path: path_hash,
|
|
}
|
|
}
|
|
|
|
/// Generate a Stat for a node.
|
|
fn node_stat(&self, node: &FsNode, path: &str, name: &str) -> Stat {
|
|
let qid = self.node_qid(node, path);
|
|
|
|
let mut mode = FileMode::from_bits_truncate(node.mode);
|
|
if node.is_dir {
|
|
mode |= FileMode::DIR;
|
|
}
|
|
|
|
// Estimate length
|
|
let length = if node.is_dir {
|
|
0
|
|
} else {
|
|
node.read().map(|d| d.len() as u64).unwrap_or(0)
|
|
};
|
|
|
|
Stat {
|
|
type_: 0,
|
|
dev: 0,
|
|
qid,
|
|
mode,
|
|
atime: 0,
|
|
mtime: 0,
|
|
length,
|
|
name: name.to_string().into(),
|
|
uid: "ork".to_string().into(),
|
|
gid: "ork".to_string().into(),
|
|
muid: "ork".to_string().into(),
|
|
}
|
|
}
|
|
|
|
/// Build a response with just a qid.
|
|
fn qid_response(&self, msg_type: u8, tag: u16, qid: &Qid) -> io::Result<Vec<u8>> {
|
|
let mut resp = Vec::new();
|
|
resp.extend_from_slice(&[0u8; 4]);
|
|
resp.push(msg_type);
|
|
resp.extend_from_slice(&tag.to_le_bytes());
|
|
self.encode_qid(&mut resp, qid);
|
|
|
|
let size = resp.len() as u32;
|
|
resp[0..4].copy_from_slice(&size.to_le_bytes());
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
/// Encode a Qid into a buffer.
|
|
fn encode_qid(&self, buf: &mut Vec<u8>, qid: &Qid) {
|
|
buf.push(qid.file_type.bits());
|
|
buf.extend_from_slice(&qid.version.to_le_bytes());
|
|
buf.extend_from_slice(&qid.path.to_le_bytes());
|
|
}
|
|
|
|
/// Encode a Stat into a buffer.
|
|
fn encode_stat(&self, buf: &mut Vec<u8>, stat: &Stat) {
|
|
// Stat has a leading 2-byte size field
|
|
let start = buf.len();
|
|
buf.extend_from_slice(&[0u8; 2]); // size placeholder
|
|
|
|
buf.extend_from_slice(&stat.type_.to_le_bytes());
|
|
buf.extend_from_slice(&stat.dev.to_le_bytes());
|
|
self.encode_qid(buf, &stat.qid);
|
|
buf.extend_from_slice(&stat.mode.bits().to_le_bytes());
|
|
buf.extend_from_slice(&stat.atime.to_le_bytes());
|
|
buf.extend_from_slice(&stat.mtime.to_le_bytes());
|
|
buf.extend_from_slice(&stat.length.to_le_bytes());
|
|
|
|
// Strings
|
|
self.encode_string(buf, &stat.name);
|
|
self.encode_string(buf, &stat.uid);
|
|
self.encode_string(buf, &stat.gid);
|
|
self.encode_string(buf, &stat.muid);
|
|
|
|
// Fill in size
|
|
let stat_size = (buf.len() - start - 2) as u16;
|
|
buf[start..start + 2].copy_from_slice(&stat_size.to_le_bytes());
|
|
}
|
|
|
|
/// Encode a string into a buffer (2-byte length prefix).
|
|
fn encode_string(&self, buf: &mut Vec<u8>, s: &str) {
|
|
let bytes = s.as_bytes();
|
|
buf.extend_from_slice(&(bytes.len() as u16).to_le_bytes());
|
|
buf.extend_from_slice(bytes);
|
|
}
|
|
}
|