use arc_swap::ArcSwap;
use bytes::{Bytes, BytesMut};
use dashmap::DashMap;
use dashmap::mapref::entry::Entry;
use deku::prelude::*;
use futures::StreamExt;
use futures::stream::FuturesUnordered;
use ninep_proto::*;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU16, AtomicU32, Ordering};
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
use tokio::net::{TcpStream, UnixStream};
use tokio::sync::{Notify, mpsc, oneshot};
use tokio_util::codec::LengthDelimitedCodec;
use tracing::{debug, info, warn};
const NOTAG: u16 = 0xFFFF;
pub const NOFID: u32 = 0xFFFF_FFFF;
fn new_op_id() -> [u8; 16] {
use rand::RngCore;
let mut id = [0u8; 16];
rand::thread_rng().fill_bytes(&mut id);
id
}
const RECONNECT_BACKOFF_MIN: Duration = Duration::from_millis(50);
const RECONNECT_BACKOFF_MAX: Duration = Duration::from_millis(500);
const PROBE_TIMEOUT: Duration = Duration::from_secs(3);
const REQUEST_TIMEOUT: Duration = Duration::from_secs(8);
#[derive(Debug)]
pub enum ClientError {
Errno(u32),
Disconnected,
Unexpected(&'static str),
Codec(DekuError),
}
impl std::fmt::Display for ClientError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClientError::Errno(e) => write!(f, "server error: errno {e}"),
ClientError::Disconnected => write!(f, "9P connection lost"),
ClientError::Unexpected(m) => write!(f, "unexpected 9P reply to {m}"),
ClientError::Codec(e) => write!(f, "9P codec error: {e}"),
}
}
}
impl std::error::Error for ClientError {}
impl ClientError {
pub fn to_errno(&self) -> i32 {
match self {
ClientError::Errno(e) => *e as i32,
_ => libc::EIO,
}
}
}
pub type ClientResult<T> = Result<T, ClientError>;
mod ops;
pub use ops::{DirEntryCookie, ReaddirState, SetattrBuilder, SetattrTime};
#[derive(Clone)]
pub enum Target {
Tcp(SocketAddr),
Unix(PathBuf),
}
struct Conn {
writer_tx: mpsc::Sender<Vec<u8>>,
pending: DashMap<u16, oneshot::Sender<Bytes>>,
tag_ctr: AtomicU16,
dead: AtomicBool,
writer_shutdown: Notify,
reader_shutdown: Notify,
}
impl Conn {
fn shutdown(&self) {
self.dead.store(true, Ordering::Release);
self.reader_shutdown.notify_one();
self.writer_shutdown.notify_one();
}
}
#[derive(Clone)]
enum FidKind {
Attach {
afid: u32,
uname: String,
aname: String,
n_uname: u32,
root_inode: u64,
},
Inode { inode_id: u64, n_uname: u32 },
}
#[derive(Clone)]
struct FidRecord {
kind: FidKind,
opened: Option<u32>,
}
impl FidRecord {
fn inode_id(&self) -> u64 {
match self.kind {
FidKind::Attach { root_inode, .. } => root_inode,
FidKind::Inode { inode_id, .. } => inode_id,
}
}
fn n_uname(&self) -> u32 {
match self.kind {
FidKind::Attach { n_uname, .. } | FidKind::Inode { n_uname, .. } => n_uname,
}
}
}
#[derive(Clone)]
struct LockRecord {
fid: u32,
lock_type: LockType,
start: u64,
length: u64,
proc_id: u32,
client_id: Vec<u8>,
}
#[derive(Default, Clone)]
struct SessionState {
fids: HashMap<u32, FidRecord>,
locks: Vec<LockRecord>,
}
pub struct NinePClient {
targets: Vec<Target>,
requested_msize: u32,
conn: ArcSwap<Conn>,
live: AtomicBool,
live_notify: Notify,
reconnect_notify: Arc<Notify>,
msize: AtomicU32,
extensions: AtomicU8,
fid_ctr: AtomicU32,
fid_free: Mutex<Vec<u32>>,
state: Mutex<SessionState>,
}
impl NinePClient {
pub async fn connect_tcp(addr: SocketAddr, requested_msize: u32) -> std::io::Result<Arc<Self>> {
Self::connect(vec![Target::Tcp(addr)], requested_msize)
.await
.map_err(|e| std::io::Error::other(e.to_string()))
}
pub async fn connect_unix(
path: impl AsRef<Path>,
requested_msize: u32,
) -> std::io::Result<Arc<Self>> {
Self::connect(
vec![Target::Unix(path.as_ref().to_path_buf())],
requested_msize,
)
.await
.map_err(|e| std::io::Error::other(e.to_string()))
}
pub async fn connect_multi(
targets: Vec<Target>,
requested_msize: u32,
) -> std::io::Result<Arc<Self>> {
Self::connect(targets, requested_msize)
.await
.map_err(|e| std::io::Error::other(e.to_string()))
}
async fn connect(targets: Vec<Target>, requested_msize: u32) -> ClientResult<Arc<Self>> {
let reconnect_notify = Arc::new(Notify::new());
let (conn, msize, extensions) =
Self::probe(&targets, requested_msize, Arc::clone(&reconnect_notify)).await?;
let client = Arc::new(Self {
targets,
requested_msize,
conn: ArcSwap::new(conn),
live: AtomicBool::new(true),
live_notify: Notify::new(),
reconnect_notify,
msize: AtomicU32::new(msize),
extensions: AtomicU8::new(extensions),
fid_ctr: AtomicU32::new(1),
fid_free: Mutex::new(Vec::new()),
state: Mutex::new(SessionState::default()),
});
client.spawn_supervisor();
Ok(client)
}
async fn connect_once(
target: &Target,
requested_msize: u32,
reconnect_notify: Arc<Notify>,
) -> ClientResult<(Arc<Conn>, u32, u8)> {
let (read, write) = dial(target).await?;
let (writer_tx, writer_rx) = mpsc::channel::<Vec<u8>>(P9_CHANNEL_SIZE);
let conn = Arc::new(Conn {
writer_tx,
pending: DashMap::new(),
tag_ctr: AtomicU16::new(0),
dead: AtomicBool::new(false),
writer_shutdown: Notify::new(),
reader_shutdown: Notify::new(),
});
spawn_writer(
write,
writer_rx,
Arc::clone(&conn),
Arc::clone(&reconnect_notify),
);
spawn_reader(read, Arc::clone(&conn), reconnect_notify);
match tokio::time::timeout(PROBE_TIMEOUT, negotiate_on(&conn, requested_msize)).await {
Ok(Ok((msize, extensions))) => Ok((conn, msize, extensions)),
Ok(Err(e)) => {
conn.shutdown();
Err(e)
}
Err(_) => {
conn.shutdown();
Err(ClientError::Disconnected)
}
}
}
async fn probe(
targets: &[Target],
requested_msize: u32,
reconnect_notify: Arc<Notify>,
) -> ClientResult<(Arc<Conn>, u32, u8)> {
let mut probes = FuturesUnordered::new();
for target in targets {
let target = target.clone();
let notify = Arc::clone(&reconnect_notify);
probes.push(async move {
let (conn, msize, extensions) =
Self::connect_once(&target, requested_msize, notify).await?;
match tokio::time::timeout(PROBE_TIMEOUT, Self::leader_check(&conn)).await {
Ok(Ok(())) => Ok((conn, msize, extensions)),
Ok(Err(e)) => {
conn.shutdown();
Err(e)
}
Err(_) => {
conn.shutdown();
Err(ClientError::Disconnected)
}
}
});
}
let mut last_err = None;
let mut winner = None;
while let Some(res) = probes.next().await {
match res {
Ok(triple) => {
winner = Some(triple);
break;
}
Err(e) => last_err = Some(e),
}
}
if !probes.is_empty() {
tokio::spawn(async move {
while let Some(res) = probes.next().await {
if let Ok((conn, _, _)) = res {
conn.shutdown();
}
}
});
}
winner.ok_or_else(|| last_err.unwrap_or(ClientError::Disconnected))
}
async fn leader_check(conn: &Conn) -> ClientResult<()> {
const PROBE_FID: u32 = 0xFFFF_FFFE;
let n_uname = unsafe { libc::geteuid() };
match Self::send_raw_rpc(
conn,
Message::Tattach(Tattach {
fid: PROBE_FID,
afid: NOFID,
uname: P9String::new(Vec::new()),
aname: P9String::new(Vec::new()),
n_uname,
}),
)
.await
{
Ok(Message::Rattach(_)) => {}
Ok(_) => return Err(ClientError::Unexpected("probe attach")),
Err(e) => return Err(e),
}
let stat = Self::send_raw_rpc(
conn,
Message::Tgetattr(Tgetattr {
fid: PROBE_FID,
request_mask: GETATTR_ALL,
}),
)
.await;
let _ = Self::send_raw_rpc(conn, Message::Tclunk(Tclunk { fid: PROBE_FID })).await;
match stat {
Ok(Message::Rgetattr(_)) => Ok(()),
Ok(_) => Err(ClientError::Unexpected("probe getattr")),
Err(e) => Err(e),
}
}
fn spawn_supervisor(self: &Arc<Self>) {
let weak = Arc::downgrade(self);
let notify = Arc::clone(&self.reconnect_notify);
tokio::spawn(async move {
loop {
loop {
let notified = notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
let this = match weak.upgrade() {
Some(t) => t,
None => return,
};
if this.conn.load().dead.load(Ordering::Acquire) {
this.live.store(false, Ordering::Release);
break;
}
drop(this);
notified.await;
}
warn!("9P connection lost; reconnecting and replaying session…");
let mut backoff = RECONNECT_BACKOFF_MIN;
loop {
let this = match weak.upgrade() {
Some(t) => t,
None => return,
};
match this.reconnect_once().await {
Ok(()) => {
this.live.store(true, Ordering::Release);
this.live_notify.notify_waiters();
info!("9P session reconnected and restored");
break;
}
Err(e) => {
debug!("9P reconnect failed ({e}); retrying in {backoff:?}");
drop(this);
tokio::time::sleep(backoff).await;
backoff = (backoff * 2).min(RECONNECT_BACKOFF_MAX);
}
}
}
}
});
}
async fn reconnect_once(&self) -> ClientResult<()> {
let (conn, msize, extensions) = Self::probe(
&self.targets,
self.requested_msize,
Arc::clone(&self.reconnect_notify),
)
.await?;
self.msize.store(msize, Ordering::Relaxed);
self.extensions.store(extensions, Ordering::Relaxed);
if let Err(e) = self.replay(&conn).await {
conn.shutdown();
return Err(e);
}
let old = self.conn.swap(conn);
old.dead.store(true, Ordering::Release);
old.writer_shutdown.notify_one();
Ok(())
}
async fn replay(&self, conn: &Conn) -> ClientResult<()> {
let snapshot = self.state.lock().unwrap().clone();
let (attaches, rebinds): (Vec<_>, Vec<_>) = snapshot
.fids
.iter()
.partition(|(_, rec)| matches!(rec.kind, FidKind::Attach { .. }));
for (&fid, rec) in attaches.into_iter().chain(rebinds) {
let restored = Self::replay_fid(conn, fid, rec).await?;
if restored && let Some(flags) = rec.opened {
match Self::send_raw_rpc(conn, Message::Tlopen(Tlopen { fid, flags })).await {
Ok(Message::Rlopen(_)) => {}
Ok(_) => return Err(ClientError::Unexpected("replay lopen")),
Err(ClientError::Errno(_)) => {} Err(e) => return Err(e),
}
}
}
for lk in &snapshot.locks {
let body = Message::Tlock(Tlock {
fid: lk.fid,
lock_type: lk.lock_type,
flags: 0,
start: lk.start,
length: lk.length,
proc_id: lk.proc_id,
client_id: P9String::new(lk.client_id.clone()),
});
match Self::send_raw_rpc(conn, body).await {
Ok(_) | Err(ClientError::Errno(_)) => {}
Err(e) => return Err(e),
}
}
Ok(())
}
async fn replay_fid(conn: &Conn, fid: u32, rec: &FidRecord) -> ClientResult<bool> {
let confined_attach =
matches!(&rec.kind, FidKind::Attach { aname, .. } if !aname.is_empty());
let body = match &rec.kind {
FidKind::Attach {
afid,
uname,
aname,
n_uname,
..
} => Message::Tattach(Tattach {
fid,
afid: *afid,
uname: P9String::new(uname.clone().into_bytes()),
aname: P9String::new(aname.clone().into_bytes()),
n_uname: *n_uname,
}),
FidKind::Inode { inode_id, n_uname } => Message::Trebind(Trebind {
fid,
inode_id: *inode_id,
n_uname: *n_uname,
}),
};
match Self::send_raw_rpc(conn, body).await {
Ok(Message::Rattach(_)) | Ok(Message::Rrebind(_)) => Ok(true),
Ok(_) => Err(ClientError::Unexpected("replay fid")),
Err(e @ ClientError::Errno(_)) if confined_attach => Err(e),
Err(ClientError::Errno(_)) => Ok(false), Err(e) => Err(e),
}
}
pub fn msize(&self) -> u32 {
self.msize.load(Ordering::Relaxed)
}
pub fn extensions_enabled(&self) -> bool {
self.extensions.load(Ordering::Relaxed) >= 1
}
pub fn extensions_v2_enabled(&self) -> bool {
self.extensions.load(Ordering::Relaxed) >= 2
}
fn op_id_enabled(&self) -> bool {
self.extensions.load(Ordering::Relaxed) >= 3
}
pub fn max_io(&self) -> u32 {
self.msize().saturating_sub(P9_IOHDRSZ)
}
pub fn max_write_payload(&self) -> u32 {
self.msize().saturating_sub(P9_TWRITE_HDR)
}
pub fn alloc_fid(&self) -> u32 {
if let Some(fid) = self.fid_free.lock().unwrap().pop() {
return fid;
}
self.fid_ctr.fetch_add(1, Ordering::Relaxed)
}
pub fn free_fid(&self, fid: u32) {
self.fid_free.lock().unwrap().push(fid);
}
pub fn outstanding_fids(&self) -> usize {
let allocated = self.fid_ctr.load(Ordering::Relaxed).saturating_sub(1) as usize;
allocated.saturating_sub(self.fid_free.lock().unwrap().len())
}
async fn wait_until_live(&self) {
loop {
let notified = self.live_notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
if self.live.load(Ordering::Acquire) {
return;
}
notified.await;
}
}
fn alloc_tag(conn: &Conn, otx: oneshot::Sender<Bytes>) -> u16 {
let mut otx = Some(otx);
loop {
let candidate = conn.tag_ctr.fetch_add(1, Ordering::Relaxed);
if candidate == NOTAG {
continue;
}
match conn.pending.entry(candidate) {
Entry::Vacant(slot) => {
slot.insert(otx.take().unwrap());
return candidate;
}
Entry::Occupied(_) => continue,
}
}
}
async fn send_request(&self, op_id: [u8; 16], body: Message) -> ClientResult<Message> {
loop {
self.wait_until_live().await;
let conn = self.conn.load_full();
let (otx, orx) = oneshot::channel();
let tag = Self::alloc_tag(&conn, otx);
if conn.dead.load(Ordering::Acquire) {
conn.pending.remove(&tag);
self.reconnect_notify.notify_waiters();
tokio::task::yield_now().await;
continue;
}
let bytes = match P9Message::new_with_op_id(tag, op_id, body.clone())
.to_bytes_ctx(self.op_id_enabled())
{
Ok(b) => b,
Err(e) => {
conn.pending.remove(&tag);
return Err(ClientError::Codec(e));
}
};
if conn.writer_tx.send(bytes).await.is_err() {
conn.pending.remove(&tag);
tokio::task::yield_now().await;
continue;
}
match tokio::time::timeout(REQUEST_TIMEOUT, orx).await {
Ok(Ok(frame)) => {
let (_, msg) =
P9Message::from_bytes((&frame, 0)).map_err(ClientError::Codec)?;
if let Message::Rlerror(ref e) = msg.body
&& e.ecode == P9_ENOTLEADER
{
self.force_reprobe(&conn);
tokio::task::yield_now().await;
continue;
}
return Ok(msg.body);
}
Ok(Err(_)) => {
conn.pending.remove(&tag);
tokio::task::yield_now().await;
continue;
}
Err(_) => {
conn.pending.remove(&tag);
self.force_reprobe(&conn);
tokio::task::yield_now().await;
continue;
}
}
}
}
fn force_reprobe(&self, conn: &Arc<Conn>) {
conn.shutdown();
self.reconnect_notify.notify_waiters();
}
async fn send_raw(conn: &Conn, body: Message) -> ClientResult<Message> {
let (otx, orx) = oneshot::channel();
let tag = Self::alloc_tag(conn, otx);
let bytes = match P9Message::new(tag, body).to_bytes() {
Ok(b) => b,
Err(e) => {
conn.pending.remove(&tag);
return Err(ClientError::Codec(e));
}
};
if conn.writer_tx.send(bytes).await.is_err() {
conn.pending.remove(&tag);
return Err(ClientError::Disconnected);
}
let frame = orx.await.map_err(|_| ClientError::Disconnected)?;
let (_, msg) = P9Message::from_bytes((&frame, 0)).map_err(ClientError::Codec)?;
Ok(msg.body)
}
async fn send_raw_rpc(conn: &Conn, body: Message) -> ClientResult<Message> {
match Self::send_raw(conn, body).await? {
Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
other => Ok(other),
}
}
async fn rpc(&self, body: Message) -> ClientResult<Message> {
self.rpc_with_op_id([0u8; 16], body).await
}
async fn rpc_with_op_id(&self, op_id: [u8; 16], body: Message) -> ClientResult<Message> {
match self.send_request(op_id, body).await? {
Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
other => Ok(other),
}
}
pub async fn attach(
&self,
fid: u32,
afid: u32,
uname: &str,
aname: &str,
n_uname: u32,
) -> ClientResult<Qid> {
let resp = self
.rpc(Message::Tattach(Tattach {
fid,
afid,
uname: P9String::new(uname.as_bytes().to_vec()),
aname: P9String::new(aname.as_bytes().to_vec()),
n_uname,
}))
.await?;
match resp {
Message::Rattach(r) => {
let mut st = self.state.lock().unwrap();
st.fids.insert(
fid,
FidRecord {
kind: FidKind::Attach {
afid,
uname: uname.to_string(),
aname: aname.to_string(),
n_uname,
root_inode: r.qid.path,
},
opened: None,
},
);
Ok(r.qid)
}
_ => Err(ClientError::Unexpected("attach")),
}
}
pub async fn rebind(&self, fid: u32, inode_id: u64, n_uname: u32) -> ClientResult<Qid> {
let resp = self
.rpc(Message::Trebind(Trebind {
fid,
inode_id,
n_uname,
}))
.await?;
match resp {
Message::Rrebind(r) => {
self.state.lock().unwrap().fids.insert(
fid,
FidRecord {
kind: FidKind::Inode { inode_id, n_uname },
opened: None,
},
);
Ok(r.qid)
}
_ => Err(ClientError::Unexpected("rebind")),
}
}
pub async fn walk(&self, fid: u32, newfid: u32, names: &[&[u8]]) -> ClientResult<Vec<Qid>> {
let wnames = names
.iter()
.map(|n| P9String::new(n.to_vec()))
.collect::<Vec<_>>();
let resp = self
.rpc(Message::Twalk(Twalk {
fid,
newfid,
nwname: wnames.len() as u16,
wnames,
}))
.await?;
match resp {
Message::Rwalk(r) => {
if names.is_empty() || r.wqids.len() == names.len() {
let mut st = self.state.lock().unwrap();
let n_uname = st.fids.get(&fid).map(FidRecord::n_uname);
let inode_id = if names.is_empty() {
st.fids.get(&fid).map(FidRecord::inode_id)
} else {
r.wqids.last().map(|q| q.path)
};
if let (Some(inode_id), Some(n_uname)) = (inode_id, n_uname) {
st.fids.insert(
newfid,
FidRecord {
kind: FidKind::Inode { inode_id, n_uname },
opened: None,
},
);
}
}
Ok(r.wqids)
}
_ => Err(ClientError::Unexpected("walk")),
}
}
pub async fn walk_getattr(
&self,
fid: u32,
newfid: u32,
names: &[&[u8]],
) -> ClientResult<(Vec<Qid>, Stat)> {
let wnames = names
.iter()
.map(|n| P9String::new(n.to_vec()))
.collect::<Vec<_>>();
let resp = self
.rpc(Message::Twalkgetattr(Twalkgetattr {
fid,
newfid,
nwname: wnames.len() as u16,
wnames,
}))
.await?;
match resp {
Message::Rwalkgetattr(r) => {
{
let mut st = self.state.lock().unwrap();
let n_uname = st.fids.get(&fid).map(FidRecord::n_uname);
let inode_id = if names.is_empty() {
st.fids.get(&fid).map(FidRecord::inode_id)
} else {
r.wqids.last().map(|q| q.path)
};
if let (Some(inode_id), Some(n_uname)) = (inode_id, n_uname) {
st.fids.insert(
newfid,
FidRecord {
kind: FidKind::Inode { inode_id, n_uname },
opened: None,
},
);
}
}
Ok((r.wqids, r.stat))
}
_ => Err(ClientError::Unexpected("walk_getattr")),
}
}
pub async fn clunk(&self, fid: u32) -> ClientResult<()> {
let resp = self.rpc(Message::Tclunk(Tclunk { fid })).await;
{
let mut st = self.state.lock().unwrap();
st.fids.remove(&fid);
st.locks.retain(|l| l.fid != fid);
}
match resp? {
Message::Rclunk(_) => Ok(()),
_ => Err(ClientError::Unexpected("clunk")),
}
}
pub async fn getattr(&self, fid: u32, mask: u64) -> ClientResult<Stat> {
let resp = self
.rpc(Message::Tgetattr(Tgetattr {
fid,
request_mask: mask,
}))
.await?;
match resp {
Message::Rgetattr(r) => Ok(r.stat),
_ => Err(ClientError::Unexpected("getattr")),
}
}
pub async fn setattr(&self, ts: Tsetattr) -> ClientResult<()> {
match self.rpc(Message::Tsetattr(ts)).await? {
Message::Rsetattr(_) => Ok(()),
_ => Err(ClientError::Unexpected("setattr")),
}
}
pub async fn setattr_attr(&self, ts: Tsetattr) -> ClientResult<Stat> {
match self.rpc(Message::Tsetattrattr(ts)).await? {
Message::Rsetattrattr(r) => Ok(r.stat),
_ => Err(ClientError::Unexpected("setattr_attr")),
}
}
pub async fn lopen(&self, fid: u32, flags: u32) -> ClientResult<(Qid, u32)> {
match self.rpc(Message::Tlopen(Tlopen { fid, flags })).await? {
Message::Rlopen(r) => {
if let Some(rec) = self.state.lock().unwrap().fids.get_mut(&fid) {
rec.opened = Some(flags);
}
Ok((r.qid, r.iounit))
}
_ => Err(ClientError::Unexpected("lopen")),
}
}
pub async fn lopenat(&self, fid: u32, newfid: u32, flags: u32) -> ClientResult<(Qid, u32)> {
let resp = self
.rpc(Message::Tlopenat(Tlopenat { fid, newfid, flags }))
.await?;
match resp {
Message::Rlopenat(r) => {
let mut st = self.state.lock().unwrap();
if let Some(n_uname) = st.fids.get(&fid).map(FidRecord::n_uname) {
st.fids.insert(
newfid,
FidRecord {
kind: FidKind::Inode {
inode_id: r.qid.path,
n_uname,
},
opened: Some(flags),
},
);
}
Ok((r.qid, r.iounit))
}
_ => Err(ClientError::Unexpected("lopenat")),
}
}
pub async fn lcreate(
&self,
fid: u32,
name: &[u8],
flags: u32,
mode: u32,
gid: u32,
) -> ClientResult<(Qid, u32)> {
self.lcreate_op_id(fid, name, flags, mode, gid, new_op_id())
.await
}
pub async fn lcreate_op_id(
&self,
fid: u32,
name: &[u8],
flags: u32,
mode: u32,
gid: u32,
op_id: [u8; 16],
) -> ClientResult<(Qid, u32)> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Tlcreate(Tlcreate {
fid,
name: P9String::new(name.to_vec()),
flags,
mode,
gid,
}),
)
.await?;
match resp {
Message::Rlcreate(r) => {
let reopen = flags & !((libc::O_CREAT | libc::O_EXCL | libc::O_TRUNC) as u32);
let mut st = self.state.lock().unwrap();
if let Some(rec) = st.fids.get_mut(&fid) {
let n_uname = rec.n_uname();
rec.kind = FidKind::Inode {
inode_id: r.qid.path,
n_uname,
};
rec.opened = Some(reopen);
}
Ok((r.qid, r.iounit))
}
_ => Err(ClientError::Unexpected("lcreate")),
}
}
pub async fn lcreateattr(
&self,
dfid: u32,
newfid: u32,
name: &[u8],
flags: u32,
mode: u32,
gid: u32,
) -> ClientResult<(Stat, u32)> {
self.lcreateattr_op_id(dfid, newfid, name, flags, mode, gid, new_op_id())
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn lcreateattr_op_id(
&self,
dfid: u32,
newfid: u32,
name: &[u8],
flags: u32,
mode: u32,
gid: u32,
op_id: [u8; 16],
) -> ClientResult<(Stat, u32)> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Tlcreateattr(Tlcreateattr {
dfid,
newfid,
name: P9String::new(name.to_vec()),
flags,
mode,
gid,
}),
)
.await?;
match resp {
Message::Rlcreateattr(r) => {
let reopen = flags & !((libc::O_CREAT | libc::O_EXCL | libc::O_TRUNC) as u32);
let mut st = self.state.lock().unwrap();
if let Some(n_uname) = st.fids.get(&dfid).map(FidRecord::n_uname) {
st.fids.insert(
newfid,
FidRecord {
kind: FidKind::Inode {
inode_id: r.stat.qid.path,
n_uname,
},
opened: Some(reopen),
},
);
}
Ok((r.stat, r.iounit))
}
_ => Err(ClientError::Unexpected("lcreateattr")),
}
}
pub async fn read(&self, fid: u32, offset: u64, size: u32) -> ClientResult<Vec<u8>> {
Ok(self.read_bytes(fid, offset, size).await?.into())
}
pub async fn read_bytes(&self, fid: u32, offset: u64, size: u32) -> ClientResult<Bytes> {
if size == 0 {
return Ok(Bytes::new());
}
let max = self.max_io().max(1);
let first = self.read_once(fid, offset, size.min(max)).await?;
if size <= max || (first.len() as u32) < size.min(max) {
return Ok(first);
}
let mut out = BytesMut::with_capacity(size.min(max.saturating_mul(2)) as usize);
let mut off = offset + first.len() as u64;
out.extend_from_slice(&first);
while (out.len() as u32) < size {
let max = self.max_io().max(1);
let want = (size - out.len() as u32).min(max);
let data = self.read_once(fid, off, want).await?;
let got = data.len() as u32;
out.extend_from_slice(&data);
off += got as u64;
if got < want {
break; }
}
Ok(out.freeze())
}
async fn read_once(&self, fid: u32, offset: u64, count: u32) -> ClientResult<Bytes> {
let resp = self
.rpc(Message::Tread(Tread { fid, offset, count }))
.await?;
match resp {
Message::Rread(r) => Ok(r.data.0),
_ => Err(ClientError::Unexpected("read")),
}
}
pub async fn write(&self, fid: u32, offset: u64, data: &[u8]) -> ClientResult<u64> {
let mut written = 0usize;
while written < data.len() {
let max = self.max_write_payload().max(1) as usize;
let end = (written + max).min(data.len());
let chunk = &data[written..end];
let n = self.write_once(fid, offset + written as u64, chunk).await?;
if n == 0 {
break;
}
written += n as usize;
if (n as usize) < chunk.len() {
break; }
}
Ok(written as u64)
}
async fn write_once(&self, fid: u32, offset: u64, data: &[u8]) -> ClientResult<u32> {
let resp = self
.rpc(Message::Twrite(Twrite {
fid,
offset,
count: data.len() as u32,
data: DekuBytes::from(data.to_vec()),
}))
.await?;
match resp {
Message::Rwrite(r) => Ok(r.count),
_ => Err(ClientError::Unexpected("write")),
}
}
pub async fn readdir(&self, fid: u32, offset: u64, count: u32) -> ClientResult<Vec<DirEntry>> {
let resp = self
.rpc(Message::Treaddir(Treaddir { fid, offset, count }))
.await?;
match resp {
Message::Rreaddir(r) => r.to_entries().map_err(ClientError::Codec),
_ => Err(ClientError::Unexpected("readdir")),
}
}
pub async fn readdirplus(
&self,
fid: u32,
offset: u64,
count: u32,
) -> ClientResult<Vec<DirEntryPlus>> {
let resp = self
.rpc(Message::Treaddirattr(Treaddirattr { fid, offset, count }))
.await?;
match resp {
Message::Rreaddirattr(r) => r.to_entries().map_err(ClientError::Codec),
_ => Err(ClientError::Unexpected("readdirplus")),
}
}
pub async fn mkdir(&self, dfid: u32, name: &[u8], mode: u32, gid: u32) -> ClientResult<Qid> {
self.mkdir_op_id(dfid, name, mode, gid, new_op_id()).await
}
pub async fn mkdir_op_id(
&self,
dfid: u32,
name: &[u8],
mode: u32,
gid: u32,
op_id: [u8; 16],
) -> ClientResult<Qid> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Tmkdir(Tmkdir {
dfid,
name: P9String::new(name.to_vec()),
mode,
gid,
}),
)
.await?;
match resp {
Message::Rmkdir(r) => Ok(r.qid),
_ => Err(ClientError::Unexpected("mkdir")),
}
}
pub async fn mkdir_attr(
&self,
dfid: u32,
name: &[u8],
mode: u32,
gid: u32,
) -> ClientResult<Stat> {
self.mkdir_attr_op_id(dfid, name, mode, gid, [0u8; 16])
.await
}
pub async fn mkdir_attr_op_id(
&self,
dfid: u32,
name: &[u8],
mode: u32,
gid: u32,
op_id: [u8; 16],
) -> ClientResult<Stat> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Tmkdirattr(Tmkdir {
dfid,
name: P9String::new(name.to_vec()),
mode,
gid,
}),
)
.await?;
match resp {
Message::Rmkdirattr(r) => Ok(r.stat),
_ => Err(ClientError::Unexpected("mkdir_attr")),
}
}
pub async fn symlink(
&self,
dfid: u32,
name: &[u8],
target: &[u8],
gid: u32,
) -> ClientResult<Qid> {
self.symlink_op_id(dfid, name, target, gid, new_op_id())
.await
}
pub async fn symlink_op_id(
&self,
dfid: u32,
name: &[u8],
target: &[u8],
gid: u32,
op_id: [u8; 16],
) -> ClientResult<Qid> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Tsymlink(Tsymlink {
dfid,
name: P9String::new(name.to_vec()),
symtgt: P9String::new(target.to_vec()),
gid,
}),
)
.await?;
match resp {
Message::Rsymlink(r) => Ok(r.qid),
_ => Err(ClientError::Unexpected("symlink")),
}
}
pub async fn symlink_attr(
&self,
dfid: u32,
name: &[u8],
target: &[u8],
gid: u32,
) -> ClientResult<Stat> {
self.symlink_attr_op_id(dfid, name, target, gid, [0u8; 16])
.await
}
pub async fn symlink_attr_op_id(
&self,
dfid: u32,
name: &[u8],
target: &[u8],
gid: u32,
op_id: [u8; 16],
) -> ClientResult<Stat> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Tsymlinkattr(Tsymlink {
dfid,
name: P9String::new(name.to_vec()),
symtgt: P9String::new(target.to_vec()),
gid,
}),
)
.await?;
match resp {
Message::Rsymlinkattr(r) => Ok(r.stat),
_ => Err(ClientError::Unexpected("symlink_attr")),
}
}
pub async fn mknod(
&self,
dfid: u32,
name: &[u8],
mode: u32,
major: u32,
minor: u32,
gid: u32,
) -> ClientResult<Qid> {
self.mknod_op_id(dfid, name, mode, major, minor, gid, new_op_id())
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn mknod_op_id(
&self,
dfid: u32,
name: &[u8],
mode: u32,
major: u32,
minor: u32,
gid: u32,
op_id: [u8; 16],
) -> ClientResult<Qid> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Tmknod(Tmknod {
dfid,
name: P9String::new(name.to_vec()),
mode,
major,
minor,
gid,
}),
)
.await?;
match resp {
Message::Rmknod(r) => Ok(r.qid),
_ => Err(ClientError::Unexpected("mknod")),
}
}
pub async fn mknod_attr(
&self,
dfid: u32,
name: &[u8],
mode: u32,
major: u32,
minor: u32,
gid: u32,
) -> ClientResult<Stat> {
self.mknod_attr_op_id(dfid, name, mode, major, minor, gid, [0u8; 16])
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn mknod_attr_op_id(
&self,
dfid: u32,
name: &[u8],
mode: u32,
major: u32,
minor: u32,
gid: u32,
op_id: [u8; 16],
) -> ClientResult<Stat> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Tmknodattr(Tmknod {
dfid,
name: P9String::new(name.to_vec()),
mode,
major,
minor,
gid,
}),
)
.await?;
match resp {
Message::Rmknodattr(r) => Ok(r.stat),
_ => Err(ClientError::Unexpected("mknod_attr")),
}
}
pub async fn readlink(&self, fid: u32) -> ClientResult<Vec<u8>> {
match self.rpc(Message::Treadlink(Treadlink { fid })).await? {
Message::Rreadlink(r) => Ok(r.target.data),
_ => Err(ClientError::Unexpected("readlink")),
}
}
pub async fn link(&self, dfid: u32, fid: u32, name: &[u8]) -> ClientResult<()> {
self.link_op_id(dfid, fid, name, new_op_id()).await
}
pub async fn link_op_id(
&self,
dfid: u32,
fid: u32,
name: &[u8],
op_id: [u8; 16],
) -> ClientResult<()> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Tlink(Tlink {
dfid,
fid,
name: P9String::new(name.to_vec()),
}),
)
.await?;
match resp {
Message::Rlink(_) => Ok(()),
_ => Err(ClientError::Unexpected("link")),
}
}
pub async fn link_attr(&self, dfid: u32, fid: u32, name: &[u8]) -> ClientResult<Stat> {
self.link_attr_op_id(dfid, fid, name, [0u8; 16]).await
}
pub async fn link_attr_op_id(
&self,
dfid: u32,
fid: u32,
name: &[u8],
op_id: [u8; 16],
) -> ClientResult<Stat> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Tlinkattr(Tlink {
dfid,
fid,
name: P9String::new(name.to_vec()),
}),
)
.await?;
match resp {
Message::Rlinkattr(r) => Ok(r.stat),
_ => Err(ClientError::Unexpected("link_attr")),
}
}
pub async fn renameat(
&self,
olddirfid: u32,
oldname: &[u8],
newdirfid: u32,
newname: &[u8],
) -> ClientResult<()> {
self.renameat_op_id(olddirfid, oldname, newdirfid, newname, new_op_id())
.await
}
pub async fn renameat_op_id(
&self,
olddirfid: u32,
oldname: &[u8],
newdirfid: u32,
newname: &[u8],
op_id: [u8; 16],
) -> ClientResult<()> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Trenameat(Trenameat {
olddirfid,
oldname: P9String::new(oldname.to_vec()),
newdirfid,
newname: P9String::new(newname.to_vec()),
}),
)
.await?;
match resp {
Message::Rrenameat(_) => Ok(()),
_ => Err(ClientError::Unexpected("renameat")),
}
}
pub async fn unlinkat(&self, dirfid: u32, name: &[u8], flags: u32) -> ClientResult<()> {
self.unlinkat_op_id(dirfid, name, flags, new_op_id()).await
}
pub async fn unlinkat_op_id(
&self,
dirfid: u32,
name: &[u8],
flags: u32,
op_id: [u8; 16],
) -> ClientResult<()> {
let resp = self
.rpc_with_op_id(
op_id,
Message::Tunlinkat(Tunlinkat {
dirfid,
name: P9String::new(name.to_vec()),
flags,
}),
)
.await?;
match resp {
Message::Runlinkat(_) => Ok(()),
_ => Err(ClientError::Unexpected("unlinkat")),
}
}
pub async fn fsync(&self, fid: u32, datasync: u32) -> ClientResult<()> {
match self.rpc(Message::Tfsync(Tfsync { fid, datasync })).await? {
Message::Rfsync(_) => Ok(()),
_ => Err(ClientError::Unexpected("fsync")),
}
}
pub async fn statfs(&self, fid: u32) -> ClientResult<Rstatfs> {
match self.rpc(Message::Tstatfs(Tstatfs { fid })).await? {
Message::Rstatfs(r) => Ok(r),
_ => Err(ClientError::Unexpected("statfs")),
}
}
#[allow(clippy::too_many_arguments)]
pub async fn lock(
&self,
fid: u32,
lock_type: LockType,
flags: u32,
start: u64,
length: u64,
proc_id: u32,
client_id: &[u8],
) -> ClientResult<LockStatus> {
let resp = self
.rpc(Message::Tlock(Tlock {
fid,
lock_type,
flags,
start,
length,
proc_id,
client_id: P9String::new(client_id.to_vec()),
}))
.await?;
match resp {
Message::Rlock(r) => {
let mut st = self.state.lock().unwrap();
match lock_type {
LockType::Unlock => st.locks.retain(|l| {
!(l.fid == fid && ranges_overlap(l.start, l.length, start, length))
}),
_ if matches!(r.status, LockStatus::Success) => {
st.locks
.retain(|l| !(l.fid == fid && l.start == start && l.length == length));
st.locks.push(LockRecord {
fid,
lock_type,
start,
length,
proc_id,
client_id: client_id.to_vec(),
});
}
_ => {}
}
Ok(r.status)
}
_ => Err(ClientError::Unexpected("lock")),
}
}
pub async fn getlock(
&self,
fid: u32,
lock_type: LockType,
start: u64,
length: u64,
proc_id: u32,
client_id: &[u8],
) -> ClientResult<Rgetlock> {
let resp = self
.rpc(Message::Tgetlock(Tgetlock {
fid,
lock_type,
start,
length,
proc_id,
client_id: P9String::new(client_id.to_vec()),
}))
.await?;
match resp {
Message::Rgetlock(r) => Ok(r),
_ => Err(ClientError::Unexpected("getlock")),
}
}
}
impl Drop for NinePClient {
fn drop(&mut self) {
self.conn.load().shutdown();
self.reconnect_notify.notify_waiters();
}
}
fn ranges_overlap(a_start: u64, a_len: u64, b_start: u64, b_len: u64) -> bool {
let a_end = if a_len == 0 {
u64::MAX
} else {
a_start.saturating_add(a_len)
};
let b_end = if b_len == 0 {
u64::MAX
} else {
b_start.saturating_add(b_len)
};
a_start < b_end && b_start < a_end
}
async fn dial(
target: &Target,
) -> ClientResult<(
Box<dyn AsyncRead + Unpin + Send>,
Box<dyn AsyncWrite + Unpin + Send>,
)> {
match target {
Target::Tcp(addr) => {
let stream = tokio::time::timeout(PROBE_TIMEOUT, TcpStream::connect(addr))
.await
.map_err(|_| ClientError::Disconnected)?
.map_err(|_| ClientError::Disconnected)?;
stream.set_nodelay(true).ok();
let keepalive = socket2::TcpKeepalive::new()
.with_time(Duration::from_secs(45))
.with_interval(Duration::from_secs(15))
.with_retries(4);
let _ = socket2::SockRef::from(&stream).set_tcp_keepalive(&keepalive);
let (r, w) = stream.into_split();
Ok((Box::new(r), Box::new(w)))
}
Target::Unix(path) => {
let stream = tokio::time::timeout(PROBE_TIMEOUT, UnixStream::connect(path))
.await
.map_err(|_| ClientError::Disconnected)?
.map_err(|_| ClientError::Disconnected)?;
let (r, w) = stream.into_split();
Ok((Box::new(r), Box::new(w)))
}
}
}
async fn negotiate_on(conn: &Conn, requested: u32) -> ClientResult<(u32, u8)> {
let (otx, orx) = oneshot::channel();
conn.pending.insert(NOTAG, otx);
let body = Message::Tversion(Tversion {
msize: requested,
version: P9String::new(VERSION_9P2000L_ZEROFS3.to_vec()),
});
let bytes = match P9Message::new(NOTAG, body).to_bytes() {
Ok(b) => b,
Err(e) => {
conn.pending.remove(&NOTAG);
return Err(ClientError::Codec(e));
}
};
if conn.writer_tx.send(bytes).await.is_err() {
conn.pending.remove(&NOTAG);
return Err(ClientError::Disconnected);
}
let frame = orx.await.map_err(|_| ClientError::Disconnected)?;
let (_, msg) = P9Message::from_bytes((&frame, 0)).map_err(ClientError::Codec)?;
match msg.body {
Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
Message::Rversion(rv) => {
let vstr = rv.version.as_str().unwrap_or("");
if !vstr.contains("9P2000.L") {
warn!("server negotiated unsupported version: {:?}", vstr);
return Err(ClientError::Unexpected("version"));
}
let extensions = if vstr.contains(".zerofs3") {
3
} else if vstr.contains(".zerofs2") {
2
} else if vstr.contains(".zerofs") {
1
} else {
0
};
let negotiated = rv.msize.min(requested);
if negotiated < 4096 {
warn!("server negotiated msize {negotiated} below minimum 4096");
return Err(ClientError::Unexpected("version"));
}
debug!("9P version negotiated, msize={negotiated}, extensions={extensions}");
Ok((negotiated, extensions))
}
_ => Err(ClientError::Unexpected("version")),
}
}
fn spawn_writer(
write: Box<dyn AsyncWrite + Unpin + Send>,
mut rx: mpsc::Receiver<Vec<u8>>,
conn: Arc<Conn>,
reconnect: Arc<Notify>,
) {
tokio::spawn(async move {
let mut writer = tokio::io::BufWriter::with_capacity(64 * 1024, write);
loop {
tokio::select! {
biased;
_ = conn.writer_shutdown.notified() => break,
maybe = rx.recv() => {
let Some(frame) = maybe else { break };
if writer.write_all(&frame).await.is_err() {
break;
}
let mut failed = false;
while let Ok(more) = rx.try_recv() {
if writer.write_all(&more).await.is_err() {
failed = true;
break;
}
}
if failed || writer.flush().await.is_err() {
break;
}
}
}
}
conn.dead.store(true, Ordering::Release);
reconnect.notify_waiters();
});
}
fn spawn_reader(read: Box<dyn AsyncRead + Unpin + Send>, conn: Arc<Conn>, reconnect: Arc<Notify>) {
tokio::spawn(async move {
let mut framed = LengthDelimitedCodec::builder()
.little_endian()
.length_field_offset(0)
.length_field_length(P9_SIZE_FIELD_LEN)
.length_adjustment(0)
.num_skip(0)
.max_frame_length(P9_MAX_MSIZE as usize)
.new_read(read);
loop {
let next = tokio::select! {
biased;
_ = conn.reader_shutdown.notified() => break,
next = framed.next() => next,
};
let frame = match next {
Some(Ok(buf)) => buf.freeze(),
Some(Err(e)) => {
warn!("9P client read failed: {e}");
break;
}
None => break,
};
if frame.len() < P9_HEADER_SIZE {
warn!(
"9P client: response frame too short ({} bytes)",
frame.len()
);
continue;
}
let tag = u16::from_le_bytes([frame[5], frame[6]]);
if let Some((_, tx)) = conn.pending.remove(&tag) {
let _ = tx.send(frame);
} else {
debug!("9P client: response for unknown tag {tag}");
}
}
conn.dead.store(true, Ordering::Release);
conn.pending.clear();
conn.writer_shutdown.notify_one();
reconnect.notify_waiters();
});
}