use crate::{
Result,
fs::{FileMeta, FileType, IoUnit, Mode, Perm, Stat},
sansio::{
protocol::{Data, RawStat, Rdata, Rmessage, Tdata, Tmessage},
server::{
Attached, E_ALREADY_ATTACHED, E_CREATE_NON_DIR, E_UNKNOWN_FID, Either, Session,
SessionType, Unattached,
},
},
sync::{SyncNineP, SyncServerStream, SyncStream},
};
use simple_coro::CoroState;
use std::{
collections::btree_map::Entry,
fs,
mem::size_of,
net::TcpListener,
os::unix::net::UnixListener,
path::PathBuf,
sync::mpsc::Receiver,
thread::{JoinHandle, spawn},
};
pub use crate::sansio::server::{ClientId, Server, socket_dir, socket_path};
#[derive(Debug)]
pub enum ReadOutcome {
Immediate(Vec<u8>),
Blocked(Receiver<Vec<u8>>),
}
#[derive(Debug)]
struct Socket {
path: PathBuf,
listener: UnixListener,
}
impl Drop for Socket {
fn drop(&mut self) {
let _ = fs::remove_file(&self.path);
}
}
fn unix_socket(path: impl Into<PathBuf>) -> Socket {
let path = path.into();
if let Some(dir) = path.parent() {
let _ = fs::create_dir_all(dir);
}
let _ = fs::remove_file(&path);
let listener = UnixListener::bind(&path).unwrap();
Socket { path, listener }
}
fn tcp_socket(port: u16) -> TcpListener {
let addr = format!("127.0.0.1:{port}");
TcpListener::bind(addr).unwrap()
}
pub trait Serve9p: Send + Sync + 'static {
fn walk(&self, cid: ClientId, parent_qid: u64, child: &str, uname: &str) -> Result<FileMeta>;
fn open(&self, cid: ClientId, qid: u64, mode: Mode, uname: &str) -> Result<IoUnit>;
#[allow(unused_variables)]
fn clunk(&self, cid: ClientId, qid: u64) {}
fn create(
&self,
cid: ClientId,
parent: u64,
name: &str,
perm: Perm,
mode: Mode,
uname: &str,
) -> Result<(FileMeta, IoUnit)>;
fn read(
&self,
cid: ClientId,
qid: u64,
offset: usize,
count: usize,
uname: &str,
) -> Result<ReadOutcome>;
fn read_dir(&self, cid: ClientId, qid: u64, uname: &str) -> Result<Vec<Stat>>;
fn write(
&self,
cid: ClientId,
qid: u64,
offset: usize,
data: Vec<u8>,
uname: &str,
) -> Result<usize>;
fn remove(&self, cid: ClientId, qid: u64, uname: &str) -> Result<()>;
fn stat(&self, cid: ClientId, qid: u64, uname: &str) -> Result<Stat>;
fn write_stat(&self, cid: ClientId, qid: u64, stat: Stat, uname: &str) -> Result<()>;
}
impl<S> Server<S>
where
S: Serve9p,
{
pub fn serve_tcp(mut self, port: u16) -> JoinHandle<()> {
spawn(move || {
let listener = tcp_socket(port);
for stream in listener.incoming() {
let stream = stream.unwrap();
let session = self.new_session(stream);
spawn(move || session.handle_connection());
}
})
}
pub fn serve_socket(self, socket_name: impl Into<String>) -> JoinHandle<()> {
let socket_name = socket_name.into();
let path = socket_dir().join(socket_name);
self.serve_socket_with_custom_path(path)
}
pub fn serve_socket_with_custom_path(mut self, socket_path: PathBuf) -> JoinHandle<()> {
spawn(move || {
let sock = unix_socket(socket_path);
for stream in sock.listener.incoming() {
let stream = stream.unwrap();
let session = self.new_session(stream);
spawn(move || session.handle_connection());
}
})
}
}
impl<T, S, U> Session<T, S, U>
where
T: SessionType,
S: Serve9p,
U: SyncStream,
{
fn reply(&mut self, tag: u16, resp: Result<Rdata>) {
let r: Rmessage = (tag, resp).into();
let _ = r.write_to(&mut self.stream);
}
}
impl<S, U> Session<Unattached, S, U>
where
S: Serve9p,
U: SyncServerStream,
{
fn handle_connection(mut self) {
loop {
let t = match Tmessage::read_from(&self.buf, &mut self.stream) {
Ok(t) => t,
Err(_) => return,
};
match self.handle_tmessage_unattached(t) {
Either::L((tag, resp)) => self.reply(tag, resp),
Either::R((tag, st, aqid)) => {
self.reply(tag, Ok(Rdata::Attach { aqid }));
return self.into_attached(st).handle_connection();
}
}
}
}
}
impl<S, U> Session<Attached, S, U>
where
S: Serve9p,
U: SyncServerStream,
{
fn clunk_and_clear(&mut self) {
for &qid in self.state.fids.values() {
self.s.clunk(self.client_id, qid);
}
self.state.fids.clear();
}
fn handle_connection(mut self) {
use Tdata::*;
loop {
let t = match Tmessage::read_from(&self.buf, &mut self.stream) {
Ok(t) => t,
Err(_) => return self.clunk_and_clear(),
};
let Tmessage { tag, content } = t;
let resp = match content {
Auth { .. } | Attach { .. } => Err(E_ALREADY_ATTACHED.into()),
Flush { .. } => Ok(Rdata::Flush {}),
Version { msize, version } => {
let rdata = self.handle_version(msize, version);
self.clunk_and_clear();
Ok(rdata)
}
Walk {
fid,
new_fid,
wnames,
} => self.handle_walk(fid, new_fid, wnames),
Clunk { fid } => self.handle_clunk(fid),
Stat { fid } => self.handle_stat(fid),
Open { fid, mode } => self.handle_open(fid, Mode::new(mode)),
Create {
fid,
name,
perm,
mode,
} => self.handle_create(fid, name, Perm::new(perm), Mode::new(mode)),
Read { fid, offset, count } => match self.handle_read(tag, fid, offset, count) {
Ok(Some(resp)) => Ok(resp),
Err(err) => Err(err),
Ok(None) => continue,
},
Write { fid, offset, data } => self.handle_write(fid, offset, data.0),
Remove { fid } => self.handle_remove(fid),
Wstat { fid, stat, .. } => self.handle_wstat(fid, stat),
};
self.reply(tag, resp);
}
}
fn handle_walk(&mut self, fid: u32, new_fid: u32, wnames: Vec<String>) -> Result<Rdata> {
let client_id = self.client_id;
let mut coro = self
.session_state
.handle_attached_walk(fid, new_fid, &wnames);
loop {
coro = match coro.resume() {
CoroState::Complete(res) => return res,
CoroState::Pending(c, (qid, name, uname)) => {
let fm = self.s.walk(client_id, qid, name, uname)?;
c.send(fm)
}
};
}
}
fn handle_clunk(&mut self, fid: u32) -> Result<Rdata> {
match self.state.fids.entry(fid) {
Entry::Occupied(ent) => {
let qid = ent.remove();
self.s.clunk(self.client_id, qid);
Ok(Rdata::Clunk {})
}
Entry::Vacant(_) => Err(E_UNKNOWN_FID.to_string()),
}
}
fn handle_stat(&mut self, fid: u32) -> Result<Rdata> {
let fm = self.try_file_meta(fid)?;
let s = self.s.stat(self.client_id, fm.qid, &self.state.uname)?;
let stat: RawStat = s.into();
let size = stat.size + size_of::<u16>() as u16;
Ok(Rdata::Stat { size, stat })
}
fn handle_wstat(&mut self, fid: u32, raw_stat: RawStat) -> Result<Rdata> {
let stat: Stat = raw_stat.try_into()?;
let fm = self.try_file_meta(fid)?;
self.s
.write_stat(self.client_id, fm.qid, stat, &self.state.uname)?;
Ok(Rdata::Wstat {})
}
fn handle_open(&mut self, fid: u32, mode: Mode) -> Result<Rdata> {
let fm = self.try_file_meta(fid)?;
let iounit = self
.s
.open(self.client_id, fm.qid, mode, &self.state.uname)?;
Ok(Rdata::Open {
qid: fm.as_qid(),
iounit,
})
}
fn handle_create(&mut self, fid: u32, name: String, perm: Perm, mode: Mode) -> Result<Rdata> {
let fm = self.try_file_meta(fid)?;
if fm.ty != FileType::Directory {
return Err(E_CREATE_NON_DIR.to_string());
}
let (fm, iounit) =
self.s
.create(self.client_id, fm.qid, &name, perm, mode, &self.state.uname)?;
let qid = fm.as_qid();
self.state.fids.insert(fid, fm.qid);
self.qids.entry(fm.qid).or_insert(fm);
Ok(Rdata::Create { qid, iounit })
}
fn handle_read(
&mut self,
tag: u16,
fid: u32,
offset: u64,
count: u32,
) -> Result<Option<Rdata>> {
let cid = self.client_id;
let coro = self.session_state.handle_attached_read(fid, offset, count);
let (offset, count) = (offset as usize, count as usize);
match coro.resume() {
CoroState::Complete(res) => res,
CoroState::Pending(c, Either::L((qid, uname))) => {
let stats = self.s.read_dir(cid, qid, uname)?;
c.send(stats).resume().unwrap()
}
CoroState::Pending(_, Either::R((qid, uname))) => {
let outcome = self.s.read(cid, qid, offset, count, uname)?;
match outcome {
ReadOutcome::Immediate(data) => Ok(Some(Rdata::Read { data: Data(data) })),
ReadOutcome::Blocked(chan) => {
let mut stream = self.stream.try_clone()?;
spawn(move || {
let data = chan.recv().unwrap_or_default();
let resp = Ok(Rdata::Read { data: Data(data) });
let r: Rmessage = (tag, resp).into();
let _ = r.write_to(&mut stream);
});
Ok(None)
}
}
}
}
}
fn handle_write(&mut self, fid: u32, offset: u64, data: Vec<u8>) -> Result<Rdata> {
let fm = self.try_file_meta(fid)?;
if offset > u32::MAX as u64 {
return Err(format!("offset too large: {offset} > {}", u32::MAX));
}
let count = self.s.write(
self.client_id,
fm.qid,
offset as usize,
data,
&self.state.uname,
)? as u32;
Ok(Rdata::Write { count })
}
fn handle_remove(&mut self, fid: u32) -> Result<Rdata> {
let fm = self.try_file_meta(fid)?;
self.s.remove(self.client_id, fm.qid, &self.state.uname)?;
Ok(Rdata::Remove {})
}
}