Add 9P client to ork-cli
Simple 9P2000 client supporting: - Connect via Unix socket or TCP - read/write/rdwr operations - Directory listing Preparation for reworking CLI to use 9P server.
This commit is contained in:
parent
9e2fcb9010
commit
94862b7f26
|
|
@ -0,0 +1,418 @@
|
||||||
|
//! Simple 9P2000 client for connecting to ork-server.
|
||||||
|
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::os::unix::net::UnixStream;
|
||||||
|
|
||||||
|
const MSIZE: u32 = 8192;
|
||||||
|
|
||||||
|
/// 9P client connection.
|
||||||
|
pub struct Client {
|
||||||
|
stream: Box<dyn ReadWrite>,
|
||||||
|
msize: u32,
|
||||||
|
fid_counter: u32,
|
||||||
|
root_fid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ReadWrite: Read + Write + Send {}
|
||||||
|
impl<T: Read + Write + Send> ReadWrite for T {}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
/// Connect to a 9P server.
|
||||||
|
pub fn connect(addr: &str) -> io::Result<Self> {
|
||||||
|
let stream: Box<dyn ReadWrite> = if addr.starts_with("unix://") {
|
||||||
|
Box::new(UnixStream::connect(&addr[7..])?)
|
||||||
|
} else {
|
||||||
|
let tcp_addr = if addr.starts_with("tcp://") {
|
||||||
|
&addr[6..]
|
||||||
|
} else {
|
||||||
|
addr
|
||||||
|
};
|
||||||
|
Box::new(TcpStream::connect(tcp_addr)?)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut client = Self {
|
||||||
|
stream,
|
||||||
|
msize: MSIZE,
|
||||||
|
fid_counter: 1,
|
||||||
|
root_fid: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Version handshake
|
||||||
|
client.version()?;
|
||||||
|
|
||||||
|
// Attach to root
|
||||||
|
client.root_fid = client.fid_counter;
|
||||||
|
client.fid_counter += 1;
|
||||||
|
client.attach(client.root_fid)?;
|
||||||
|
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a new fid.
|
||||||
|
fn new_fid(&mut self) -> u32 {
|
||||||
|
let fid = self.fid_counter;
|
||||||
|
self.fid_counter += 1;
|
||||||
|
fid
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send Tversion and receive Rversion.
|
||||||
|
fn version(&mut self) -> io::Result<()> {
|
||||||
|
let version = "9P2000";
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
msg.extend_from_slice(&[0u8; 4]); // size placeholder
|
||||||
|
msg.push(100); // Tversion
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // tag
|
||||||
|
msg.extend_from_slice(&MSIZE.to_le_bytes());
|
||||||
|
msg.extend_from_slice(&(version.len() as u16).to_le_bytes());
|
||||||
|
msg.extend_from_slice(version.as_bytes());
|
||||||
|
|
||||||
|
let size = msg.len() as u32;
|
||||||
|
msg[0..4].copy_from_slice(&size.to_le_bytes());
|
||||||
|
|
||||||
|
self.stream.write_all(&msg)?;
|
||||||
|
self.stream.flush()?;
|
||||||
|
|
||||||
|
let resp = self.read_response()?;
|
||||||
|
if resp[4] == 107 { // Rerror
|
||||||
|
return Err(self.parse_error(&resp));
|
||||||
|
}
|
||||||
|
if resp[4] != 101 { // Rversion
|
||||||
|
return Err(io::Error::new(io::ErrorKind::InvalidData, "unexpected response"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.msize = u32::from_le_bytes([resp[7], resp[8], resp[9], resp[10]]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send Tattach.
|
||||||
|
fn attach(&mut self, fid: u32) -> io::Result<()> {
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
msg.extend_from_slice(&[0u8; 4]); // size
|
||||||
|
msg.push(104); // Tattach
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // tag
|
||||||
|
msg.extend_from_slice(&fid.to_le_bytes()); // fid
|
||||||
|
msg.extend_from_slice(&u32::MAX.to_le_bytes()); // afid (no auth)
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // uname len
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // aname len
|
||||||
|
|
||||||
|
let size = msg.len() as u32;
|
||||||
|
msg[0..4].copy_from_slice(&size.to_le_bytes());
|
||||||
|
|
||||||
|
self.stream.write_all(&msg)?;
|
||||||
|
self.stream.flush()?;
|
||||||
|
|
||||||
|
let resp = self.read_response()?;
|
||||||
|
if resp[4] == 107 {
|
||||||
|
return Err(self.parse_error(&resp));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walk to a path and return a new fid.
|
||||||
|
fn walk(&mut self, path: &str) -> io::Result<u32> {
|
||||||
|
let newfid = self.new_fid();
|
||||||
|
|
||||||
|
let parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
|
||||||
|
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
msg.extend_from_slice(&[0u8; 4]); // size
|
||||||
|
msg.push(110); // Twalk
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // tag
|
||||||
|
msg.extend_from_slice(&self.root_fid.to_le_bytes()); // fid
|
||||||
|
msg.extend_from_slice(&newfid.to_le_bytes()); // newfid
|
||||||
|
msg.extend_from_slice(&(parts.len() as u16).to_le_bytes()); // nwname
|
||||||
|
|
||||||
|
for part in &parts {
|
||||||
|
msg.extend_from_slice(&(part.len() as u16).to_le_bytes());
|
||||||
|
msg.extend_from_slice(part.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = msg.len() as u32;
|
||||||
|
msg[0..4].copy_from_slice(&size.to_le_bytes());
|
||||||
|
|
||||||
|
self.stream.write_all(&msg)?;
|
||||||
|
self.stream.flush()?;
|
||||||
|
|
||||||
|
let resp = self.read_response()?;
|
||||||
|
if resp[4] == 107 {
|
||||||
|
return Err(self.parse_error(&resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check we walked all components
|
||||||
|
let nwqid = u16::from_le_bytes([resp[7], resp[8]]) as usize;
|
||||||
|
if nwqid != parts.len() {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::NotFound, "path not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(newfid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a fid with the given mode.
|
||||||
|
fn open(&mut self, fid: u32, mode: u8) -> io::Result<()> {
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
msg.extend_from_slice(&[0u8; 4]); // size
|
||||||
|
msg.push(112); // Topen
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // tag
|
||||||
|
msg.extend_from_slice(&fid.to_le_bytes());
|
||||||
|
msg.push(mode);
|
||||||
|
|
||||||
|
let size = msg.len() as u32;
|
||||||
|
msg[0..4].copy_from_slice(&size.to_le_bytes());
|
||||||
|
|
||||||
|
self.stream.write_all(&msg)?;
|
||||||
|
self.stream.flush()?;
|
||||||
|
|
||||||
|
let resp = self.read_response()?;
|
||||||
|
if resp[4] == 107 {
|
||||||
|
return Err(self.parse_error(&resp));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clunk (close) a fid.
|
||||||
|
fn clunk(&mut self, fid: u32) -> io::Result<()> {
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
msg.extend_from_slice(&[0u8; 4]); // size
|
||||||
|
msg.push(120); // Tclunk
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // tag
|
||||||
|
msg.extend_from_slice(&fid.to_le_bytes());
|
||||||
|
|
||||||
|
let size = msg.len() as u32;
|
||||||
|
msg[0..4].copy_from_slice(&size.to_le_bytes());
|
||||||
|
|
||||||
|
self.stream.write_all(&msg)?;
|
||||||
|
self.stream.flush()?;
|
||||||
|
|
||||||
|
let resp = self.read_response()?;
|
||||||
|
if resp[4] == 107 {
|
||||||
|
return Err(self.parse_error(&resp));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read file content.
|
||||||
|
pub fn read(&mut self, path: &str) -> io::Result<Vec<u8>> {
|
||||||
|
let fid = self.walk(path)?;
|
||||||
|
self.open(fid, 0)?; // OREAD
|
||||||
|
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut offset = 0u64;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
msg.extend_from_slice(&[0u8; 4]); // size
|
||||||
|
msg.push(116); // Tread
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // tag
|
||||||
|
msg.extend_from_slice(&fid.to_le_bytes());
|
||||||
|
msg.extend_from_slice(&offset.to_le_bytes());
|
||||||
|
msg.extend_from_slice(&(self.msize - 24).to_le_bytes()); // count
|
||||||
|
|
||||||
|
let size = msg.len() as u32;
|
||||||
|
msg[0..4].copy_from_slice(&size.to_le_bytes());
|
||||||
|
|
||||||
|
self.stream.write_all(&msg)?;
|
||||||
|
self.stream.flush()?;
|
||||||
|
|
||||||
|
let resp = self.read_response()?;
|
||||||
|
if resp[4] == 107 {
|
||||||
|
self.clunk(fid)?;
|
||||||
|
return Err(self.parse_error(&resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = u32::from_le_bytes([resp[7], resp[8], resp[9], resp[10]]) as usize;
|
||||||
|
if count == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.extend_from_slice(&resp[11..11 + count]);
|
||||||
|
offset += count as u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.clunk(fid)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read file as string.
|
||||||
|
pub fn read_string(&mut self, path: &str) -> io::Result<String> {
|
||||||
|
let data = self.read(path)?;
|
||||||
|
Ok(String::from_utf8_lossy(&data).trim().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write to a file.
|
||||||
|
pub fn write(&mut self, path: &str, data: &[u8]) -> io::Result<()> {
|
||||||
|
let fid = self.walk(path)?;
|
||||||
|
self.open(fid, 1)?; // OWRITE
|
||||||
|
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
msg.extend_from_slice(&[0u8; 4]); // size
|
||||||
|
msg.push(118); // Twrite
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // tag
|
||||||
|
msg.extend_from_slice(&fid.to_le_bytes());
|
||||||
|
msg.extend_from_slice(&0u64.to_le_bytes()); // offset
|
||||||
|
msg.extend_from_slice(&(data.len() as u32).to_le_bytes());
|
||||||
|
msg.extend_from_slice(data);
|
||||||
|
|
||||||
|
let size = msg.len() as u32;
|
||||||
|
msg[0..4].copy_from_slice(&size.to_le_bytes());
|
||||||
|
|
||||||
|
self.stream.write_all(&msg)?;
|
||||||
|
self.stream.flush()?;
|
||||||
|
|
||||||
|
let resp = self.read_response()?;
|
||||||
|
self.clunk(fid)?;
|
||||||
|
|
||||||
|
if resp[4] == 107 {
|
||||||
|
return Err(self.parse_error(&resp));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write then read (for rdwr files like new, ctl).
|
||||||
|
pub fn rdwr(&mut self, path: &str, data: &[u8]) -> io::Result<Vec<u8>> {
|
||||||
|
let fid = self.walk(path)?;
|
||||||
|
self.open(fid, 2)?; // ORDWR
|
||||||
|
|
||||||
|
// Write
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
msg.extend_from_slice(&[0u8; 4]); // size
|
||||||
|
msg.push(118); // Twrite
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // tag
|
||||||
|
msg.extend_from_slice(&fid.to_le_bytes());
|
||||||
|
msg.extend_from_slice(&0u64.to_le_bytes()); // offset
|
||||||
|
msg.extend_from_slice(&(data.len() as u32).to_le_bytes());
|
||||||
|
msg.extend_from_slice(data);
|
||||||
|
|
||||||
|
let size = msg.len() as u32;
|
||||||
|
msg[0..4].copy_from_slice(&size.to_le_bytes());
|
||||||
|
|
||||||
|
self.stream.write_all(&msg)?;
|
||||||
|
self.stream.flush()?;
|
||||||
|
|
||||||
|
let resp = self.read_response()?;
|
||||||
|
if resp[4] == 107 {
|
||||||
|
self.clunk(fid)?;
|
||||||
|
return Err(self.parse_error(&resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read response
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
msg.extend_from_slice(&[0u8; 4]); // size
|
||||||
|
msg.push(116); // Tread
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // tag
|
||||||
|
msg.extend_from_slice(&fid.to_le_bytes());
|
||||||
|
msg.extend_from_slice(&0u64.to_le_bytes()); // offset
|
||||||
|
msg.extend_from_slice(&(self.msize - 24).to_le_bytes()); // count
|
||||||
|
|
||||||
|
let size = msg.len() as u32;
|
||||||
|
msg[0..4].copy_from_slice(&size.to_le_bytes());
|
||||||
|
|
||||||
|
self.stream.write_all(&msg)?;
|
||||||
|
self.stream.flush()?;
|
||||||
|
|
||||||
|
let resp = self.read_response()?;
|
||||||
|
self.clunk(fid)?;
|
||||||
|
|
||||||
|
if resp[4] == 107 {
|
||||||
|
return Err(self.parse_error(&resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = u32::from_le_bytes([resp[7], resp[8], resp[9], resp[10]]) as usize;
|
||||||
|
Ok(resp[11..11 + count].to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List directory contents.
|
||||||
|
pub fn list(&mut self, path: &str) -> io::Result<Vec<String>> {
|
||||||
|
let fid = self.walk(path)?;
|
||||||
|
self.open(fid, 0)?; // OREAD
|
||||||
|
|
||||||
|
let mut data = Vec::new();
|
||||||
|
let mut offset = 0u64;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
msg.extend_from_slice(&[0u8; 4]); // size
|
||||||
|
msg.push(116); // Tread
|
||||||
|
msg.extend_from_slice(&0u16.to_le_bytes()); // tag
|
||||||
|
msg.extend_from_slice(&fid.to_le_bytes());
|
||||||
|
msg.extend_from_slice(&offset.to_le_bytes());
|
||||||
|
msg.extend_from_slice(&(self.msize - 24).to_le_bytes()); // count
|
||||||
|
|
||||||
|
let size = msg.len() as u32;
|
||||||
|
msg[0..4].copy_from_slice(&size.to_le_bytes());
|
||||||
|
|
||||||
|
self.stream.write_all(&msg)?;
|
||||||
|
self.stream.flush()?;
|
||||||
|
|
||||||
|
let resp = self.read_response()?;
|
||||||
|
if resp[4] == 107 {
|
||||||
|
self.clunk(fid)?;
|
||||||
|
return Err(self.parse_error(&resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = u32::from_le_bytes([resp[7], resp[8], resp[9], resp[10]]) as usize;
|
||||||
|
if count == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.extend_from_slice(&resp[11..11 + count]);
|
||||||
|
offset += count as u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.clunk(fid)?;
|
||||||
|
|
||||||
|
// Parse directory entries (stat structures)
|
||||||
|
let mut names = Vec::new();
|
||||||
|
let mut pos = 0;
|
||||||
|
while pos + 2 < data.len() {
|
||||||
|
let stat_size = u16::from_le_bytes([data[pos], data[pos + 1]]) as usize;
|
||||||
|
if pos + 2 + stat_size > data.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip to name field: size(2) + type(2) + dev(4) + qid(13) + mode(4) + atime(4) + mtime(4) + length(8) = 41
|
||||||
|
let name_offset = pos + 2 + 41;
|
||||||
|
if name_offset + 2 > data.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name_len = u16::from_le_bytes([data[name_offset], data[name_offset + 1]]) as usize;
|
||||||
|
if name_offset + 2 + name_len > data.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = String::from_utf8_lossy(&data[name_offset + 2..name_offset + 2 + name_len]).to_string();
|
||||||
|
names.push(name);
|
||||||
|
|
||||||
|
pos += 2 + stat_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(names)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a 9P response message.
|
||||||
|
fn read_response(&mut self) -> io::Result<Vec<u8>> {
|
||||||
|
let mut size_buf = [0u8; 4];
|
||||||
|
self.stream.read_exact(&mut size_buf)?;
|
||||||
|
|
||||||
|
let size = u32::from_le_bytes(size_buf) as usize;
|
||||||
|
let mut buf = vec![0u8; size];
|
||||||
|
buf[0..4].copy_from_slice(&size_buf);
|
||||||
|
self.stream.read_exact(&mut buf[4..])?;
|
||||||
|
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse an Rerror message.
|
||||||
|
fn parse_error(&self, resp: &[u8]) -> io::Error {
|
||||||
|
if resp.len() < 9 {
|
||||||
|
return io::Error::new(io::ErrorKind::InvalidData, "error response too short");
|
||||||
|
}
|
||||||
|
let len = u16::from_le_bytes([resp[7], resp[8]]) as usize;
|
||||||
|
if resp.len() < 9 + len {
|
||||||
|
return io::Error::new(io::ErrorKind::InvalidData, "error response truncated");
|
||||||
|
}
|
||||||
|
let msg = String::from_utf8_lossy(&resp[9..9 + len]).to_string();
|
||||||
|
io::Error::new(io::ErrorKind::Other, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue