use crate::{
Result,
fs::{FileMeta, FileType, IoUnit, Mode, Perm, Stat},
sansio::{
protocol::{Data, RawStat, Rdata, Tdata, Tmessage},
server::{
Attached, E_CREATE_NON_DIR, E_UNKNOWN_FID, Either, Session, SessionType, Unattached,
},
},
tokio::{AsyncNineP, AsyncStream},
};
use simple_coro::CoroState;
use std::{collections::btree_map::Entry, fs, future::Future, mem::size_of, path::PathBuf};
use tokio::{
net::{TcpListener, UnixListener},
sync::mpsc::{Receiver, UnboundedSender, unbounded_channel},
task::{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 }
}
async fn tcp_socket(port: u16) -> TcpListener {
let addr = format!("127.0.0.1:{port}");
TcpListener::bind(addr).await.unwrap()
}
pub trait AsyncServe9p: Send + Sync + 'static {
fn walk(
&self,
cid: ClientId,
parent_qid: u64,
child: &str,
uname: &str,
) -> impl Future<Output = Result<FileMeta>> + Send;
fn open(
&self,
cid: ClientId,
qid: u64,
mode: Mode,
uname: &str,
) -> impl Future<Output = Result<IoUnit>> + Send;
#[allow(unused_variables)]
fn clunk(&self, cid: ClientId, qid: u64) -> impl Future<Output = ()> + Send {
async {}
}
fn create(
&self,
cid: ClientId,
parent: u64,
name: &str,
perm: Perm,
mode: Mode,
uname: &str,
) -> impl Future<Output = Result<(FileMeta, IoUnit)>> + Send;
fn read(
&self,
cid: ClientId,
qid: u64,
offset: usize,
count: usize,
uname: &str,
) -> impl Future<Output = Result<ReadOutcome>> + Send;
fn read_dir(
&self,
cid: ClientId,
qid: u64,
uname: &str,
) -> impl Future<Output = Result<Vec<Stat>>> + Send;
fn write(
&self,
cid: ClientId,
qid: u64,
offset: usize,
data: Vec<u8>,
uname: &str,
) -> impl Future<Output = Result<usize>> + Send;
fn remove(
&self,
cid: ClientId,
qid: u64,
uname: &str,
) -> impl Future<Output = Result<()>> + Send;
fn stat(
&self,
cid: ClientId,
qid: u64,
uname: &str,
) -> impl Future<Output = Result<Stat>> + Send;
fn write_stat(
&self,
cid: ClientId,
qid: u64,
stat: Stat,
uname: &str,
) -> impl Future<Output = Result<()>> + Send;
}
impl<S> Server<S>
where
S: AsyncServe9p,
{
pub fn serve_tcp_async(mut self, port: u16) -> JoinHandle<()> {
spawn(async move {
let listener = tcp_socket(port).await;
loop {
if let Ok((stream, _addr)) = listener.accept().await {
let session = self.new_session(stream);
spawn(session.handle_connection_async());
}
}
})
}
pub fn serve_socket_async(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_async(path)
}
pub fn serve_socket_with_custom_path_async(mut self, socket_path: PathBuf) -> JoinHandle<()> {
spawn(async move {
let sock = unix_socket(socket_path);
loop {
if let Ok((stream, _addr)) = sock.listener.accept().await {
let session = self.new_session(stream);
spawn(session.handle_connection_async());
}
}
})
}
}
impl<T, S, U> Session<T, S, U>
where
T: SessionType,
S: AsyncServe9p,
U: AsyncStream,
{
async fn reply_async(&mut self, tag: u16, resp: Result<Rdata>) {
self.stream.reply(tag, resp).await
}
}
impl<S, U> Session<Unattached, S, U>
where
S: AsyncServe9p,
U: AsyncStream,
{
async fn handle_connection_async(mut self) {
loop {
let t = match Tmessage::read_from(&self.buf, &mut self.stream).await {
Ok(t) => t,
Err(_) => return,
};
match self.handle_tmessage_unattached(t) {
Either::L((tag, resp)) => self.reply_async(tag, resp).await,
Either::R((tag, st, aqid)) => {
self.reply_async(tag, Ok(Rdata::Attach { aqid })).await;
return self.into_attached(st).handle_connection_async().await;
}
}
}
}
}
impl<S, U> Session<Attached, S, U>
where
S: AsyncServe9p,
U: AsyncStream,
{
async fn clunk_and_clear_async(&mut self) {
for &qid in self.state.fids.values() {
self.s.clunk(self.client_id, qid).await;
}
self.state.fids.clear();
}
async fn handle_connection_async(mut self) {
use Tdata::*;
let (tx, mut rx) = unbounded_channel();
loop {
let Tmessage { tag, content } = tokio::select! {
Some((tag, data)) = rx.recv() => {
self.stream
.reply(tag, Ok(Rdata::Read { data: Data(data) }))
.await;
continue;
},
res = Tmessage::read_from(&self.buf, &mut self.stream) => match res {
Ok(t) => t,
Err(_) => return self.clunk_and_clear_async().await,
},
else => continue,
};
let resp = match content {
Version { msize, version } => {
let resp = self.handle_version(msize, version);
self.clunk_and_clear_async().await;
Ok(resp)
}
Auth { .. } | Attach { .. } => Err("session is already attached".into()),
Flush { .. } => Ok(Rdata::Flush {}),
Walk {
fid,
new_fid,
wnames,
} => self.handle_walk_async(fid, new_fid, wnames).await,
Clunk { fid } => self.handle_clunk_async(fid).await,
Stat { fid } => self.handle_stat_async(fid).await,
Open { fid, mode } => self.handle_open_async(fid, Mode::new(mode)).await,
Create {
fid,
name,
perm,
mode,
} => {
self.handle_create_async(fid, name, Perm::new(perm), Mode::new(mode))
.await
}
Read { fid, offset, count } => {
match self.handle_read_async(tag, fid, offset, count, &tx).await {
Ok(Some(resp)) => Ok(resp),
Err(err) => Err(err),
Ok(None) => continue,
}
}
Write { fid, offset, data } => self.handle_write_async(fid, offset, data.0).await,
Remove { fid } => self.handle_remove_async(fid).await,
Wstat { fid, stat, .. } => self.handle_wstat_async(fid, stat).await,
};
self.reply_async(tag, resp).await;
}
}
async fn handle_walk_async(
&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).await?;
c.send(fm)
}
};
}
}
async fn handle_clunk_async(&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).await;
Ok(Rdata::Clunk {})
}
Entry::Vacant(_) => Err(E_UNKNOWN_FID.to_string()),
}
}
async fn handle_stat_async(&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)
.await?;
let stat: RawStat = s.into();
let size = stat.size + size_of::<u16>() as u16;
Ok(Rdata::Stat { size, stat })
}
async fn handle_wstat_async(&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)
.await?;
Ok(Rdata::Wstat {})
}
async fn handle_open_async(&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)
.await?;
Ok(Rdata::Open {
qid: fm.as_qid(),
iounit,
})
}
async fn handle_create_async(
&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)
.await?;
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 })
}
async fn handle_read_async(
&mut self,
tag: u16,
fid: u32,
offset: u64,
count: u32,
tx: &UnboundedSender<(u16, Vec<u8>)>,
) -> 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).await?;
c.send(stats).resume().unwrap()
}
CoroState::Pending(_, Either::R((qid, uname))) => {
let outcome = self.s.read(cid, qid, offset, count, uname).await?;
match outcome {
ReadOutcome::Immediate(data) => Ok(Some(Rdata::Read { data: Data(data) })),
ReadOutcome::Blocked(mut chan) => {
let tx = tx.clone();
spawn(async move {
let data = chan.recv().await.unwrap_or_default();
tx.send((tag, data))
});
Ok(None)
}
}
}
}
}
async fn handle_write_async(&mut self, fid: u32, offset: u64, data: Vec<u8>) -> Result<Rdata> {
if offset > u32::MAX as u64 {
return Err(format!("offset too large: {offset} > {}", u32::MAX));
}
let fm = self.try_file_meta(fid)?;
let count = self
.s
.write(
self.client_id,
fm.qid,
offset as usize,
data,
&self.state.uname,
)
.await? as u32;
Ok(Rdata::Write { count })
}
async fn handle_remove_async(&mut self, fid: u32) -> Result<Rdata> {
let fm = self.try_file_meta(fid)?;
self.s
.remove(self.client_id, fm.qid, &self.state.uname)
.await?;
Ok(Rdata::Remove {})
}
}