1use arc_swap::ArcSwap;
27use bytes::{Bytes, BytesMut};
28use dashmap::mapref::entry::Entry;
29use dashmap::{DashMap, DashSet};
30use deku::prelude::*;
31use futures::StreamExt;
32use futures::stream::FuturesUnordered;
33pub use ninep_proto::NOFID;
34use ninep_proto::retry::MUTATION_RETRY_HORIZON;
35use ninep_proto::*;
36use std::collections::HashMap;
37#[cfg(not(target_arch = "wasm32"))]
38use std::net::{IpAddr, SocketAddr};
39#[cfg(not(target_arch = "wasm32"))]
40use std::path::{Path, PathBuf};
41use std::sync::Arc;
42use std::sync::Mutex;
43use std::sync::atomic::{AtomicBool, AtomicU16, AtomicU32, AtomicU64, Ordering};
44use std::time::Duration;
45#[cfg(not(target_arch = "wasm32"))]
46use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
47#[cfg(not(target_arch = "wasm32"))]
48use tokio::net::{TcpStream, UnixStream};
49use tokio::sync::{Notify, mpsc, oneshot};
50#[cfg(not(target_arch = "wasm32"))]
51use tokio_util::codec::LengthDelimitedCodec;
52use tracing::{debug, info, warn};
53use uuid::Uuid;
54
55mod linux;
56mod runtime;
57#[cfg(target_arch = "wasm32")]
58mod web_transport;
59
60const NOTAG: u16 = 0xFFFF;
62#[cfg(not(target_arch = "wasm32"))]
64const DEFAULT_9P_PORT: u16 = 5564;
65
66const RECONNECT_BACKOFF_MIN: Duration = Duration::from_millis(50);
67const RECONNECT_BACKOFF_MAX: Duration = Duration::from_millis(500);
68const PROBE_TIMEOUT: Duration = Duration::from_secs(3);
70const REQUEST_TIMEOUT: Duration = Duration::from_secs(8);
72const LIVENESS_WINDOW: Duration = Duration::from_secs(3);
74const MAX_LIVENESS_EXTRA_WINDOWS: u32 = 7;
76
77const _: () = assert!(
78 LIVENESS_WINDOW.as_nanos() < REQUEST_TIMEOUT.as_nanos(),
79 "the liveness window must be shorter than the request timeout"
80);
81
82#[derive(Default)]
84struct OpAttemptState {
85 origin_epoch: Option<u64>,
88 started: Option<runtime::Clock>,
89}
90
91impl OpAttemptState {
92 fn dispatch_frame<T>(
94 &mut self,
95 has_op_id: bool,
96 connection_epoch: u64,
97 dispatch: impl FnOnce(u8, u64) -> ClientResult<T>,
98 ) -> ClientResult<(u8, T)> {
99 let flags = if has_op_id && self.origin_epoch.is_some() {
100 P9_OP_FLAG_RETRY
101 } else {
102 0
103 };
104 let origin_epoch = if has_op_id {
105 self.origin_epoch.unwrap_or(connection_epoch)
106 } else {
107 0
108 };
109 let result = dispatch(flags, origin_epoch)?;
110 if has_op_id {
111 self.origin_epoch.get_or_insert(origin_epoch);
112 if self.started.is_none() {
113 self.started = Some(runtime::Clock::now());
114 }
115 }
116 Ok((flags, result))
117 }
118
119 fn proven_predispatch(&mut self, sent_flags: u8) {
120 if sent_flags & P9_OP_FLAG_RETRY == 0 {
121 self.origin_epoch = None;
122 self.started = None;
123 }
124 }
125
126 fn retry_budget(&self) -> ClientResult<Option<Duration>> {
127 let Some(started) = self.started.as_ref() else {
128 return Ok(None);
129 };
130 let elapsed = Duration::from_millis(started.elapsed_millis());
131 let retry_horizon = MUTATION_RETRY_HORIZON;
132 if elapsed >= retry_horizon {
133 return Err(ClientError::Disconnected);
134 }
135 Ok(Some(retry_horizon - elapsed))
136 }
137}
138
139struct StatefulCancellationGuard<'a> {
142 client: &'a NinePClient,
143 dispatched_conn: &'a Mutex<Option<Arc<Conn>>>,
144 armed: bool,
145}
146
147impl StatefulCancellationGuard<'_> {
148 fn disarm(&mut self) {
149 self.armed = false;
150 }
151}
152
153impl Drop for StatefulCancellationGuard<'_> {
154 fn drop(&mut self) {
155 let conn = self
156 .armed
157 .then(|| self.dispatched_conn.lock().unwrap().take())
158 .flatten();
159 if let Some(conn) = conn {
160 self.client.force_reprobe(&conn);
161 }
162 }
163}
164
165const REPLAY_TIMEOUT: Duration = Duration::from_secs(30);
167
168#[derive(Debug)]
169pub enum ClientError {
170 Errno(u32),
172 Disconnected,
174 Unexpected(&'static str),
176 Codec(DekuError),
178}
179
180impl std::fmt::Display for ClientError {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 match self {
183 ClientError::Errno(e) => write!(f, "server error: errno {e}"),
184 ClientError::Disconnected => write!(f, "9P connection lost"),
185 ClientError::Unexpected(m) => write!(f, "unexpected 9P reply to {m}"),
186 ClientError::Codec(e) => write!(f, "9P codec error: {e}"),
187 }
188 }
189}
190
191impl std::error::Error for ClientError {}
192
193impl ClientError {
194 pub fn to_errno(&self) -> i32 {
197 match self {
198 ClientError::Errno(e) => *e as i32,
199 _ => linux::EIO,
200 }
201 }
202}
203
204pub type ClientResult<T> = Result<T, ClientError>;
205
206async fn await_resend_bounded<T>(
209 attempt: &OpAttemptState,
210 future: impl std::future::Future<Output = T>,
211) -> ClientResult<T> {
212 match attempt.retry_budget()? {
213 Some(remaining) => runtime::timeout(remaining, future)
214 .await
215 .map_err(|_| ClientError::Disconnected),
216 None => Ok(future.await),
217 }
218}
219
220#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
222pub struct TrafficStats {
223 pub bytes_sent: u64,
224 pub bytes_received: u64,
225 pub operations: u64,
226}
227
228#[derive(Default)]
229struct TrafficCounters {
230 bytes_sent: AtomicU64,
231 bytes_received: AtomicU64,
232 operations: AtomicU64,
233}
234
235mod ops;
236pub use ops::{DirEntryCookie, ReaddirState, SetattrBuilder, SetattrTime};
237
238#[derive(Clone, Debug, PartialEq, Eq)]
244pub enum Target {
245 #[cfg(not(target_arch = "wasm32"))]
246 Tcp(SocketAddr),
247 #[cfg(not(target_arch = "wasm32"))]
249 TcpHost(String),
250 #[cfg(not(target_arch = "wasm32"))]
251 Unix(PathBuf),
252 #[cfg(target_arch = "wasm32")]
254 WebSocket(String),
255}
256
257impl Target {
258 pub fn parse_list(spec: &str) -> Result<Vec<Self>, String> {
261 let targets = spec
262 .split(',')
263 .map(str::trim)
264 .filter(|spec| !spec.is_empty())
265 .map(str::parse)
266 .collect::<Result<Vec<_>, _>>()?;
267 if targets.is_empty() {
268 Err("no 9P target given".into())
269 } else {
270 Ok(targets)
271 }
272 }
273}
274
275#[cfg(not(target_arch = "wasm32"))]
276fn parse_numeric_tcp_endpoint(endpoint: &str) -> Option<SocketAddr> {
277 if let Ok(addr) = endpoint.parse::<SocketAddr>() {
278 return Some(addr);
279 }
280
281 if let Some(literal) = endpoint
283 .strip_prefix('[')
284 .and_then(|literal| literal.strip_suffix(']'))
285 && let Ok(ip @ IpAddr::V6(_)) = literal.parse::<IpAddr>()
286 {
287 return Some(SocketAddr::new(ip, DEFAULT_9P_PORT));
288 }
289
290 if !endpoint.starts_with('[')
293 && let Some((host, port)) = endpoint.rsplit_once(':')
294 && let Ok(ip @ IpAddr::V6(_)) = host.parse::<IpAddr>()
295 && let Ok(port) = port.parse::<u16>()
296 {
297 return Some(SocketAddr::new(ip, port));
298 }
299
300 endpoint
301 .parse::<IpAddr>()
302 .ok()
303 .map(|ip| SocketAddr::new(ip, DEFAULT_9P_PORT))
304}
305
306impl std::str::FromStr for Target {
307 type Err = String;
308
309 fn from_str(spec: &str) -> Result<Self, Self::Err> {
310 let spec = spec.trim();
311 if spec.is_empty() {
312 return Err("empty 9P target".into());
313 }
314
315 #[cfg(target_arch = "wasm32")]
316 {
317 return if spec.starts_with("ws://") || spec.starts_with("wss://") {
318 Ok(Self::WebSocket(spec.into()))
319 } else {
320 Err(format!(
321 "browser clients require a ws:// or wss:// target, got {spec:?}"
322 ))
323 };
324 }
325
326 #[cfg(not(target_arch = "wasm32"))]
327 {
328 if let Some(path) = spec.strip_prefix("unix:") {
329 return Ok(Self::Unix(path.strip_prefix("//").unwrap_or(path).into()));
330 }
331 let endpoint = spec.strip_prefix("tcp://").unwrap_or(spec);
332 if endpoint.starts_with('/') || endpoint.starts_with('.') {
333 return Ok(Self::Unix(endpoint.into()));
334 }
335 if let Some(addr) = parse_numeric_tcp_endpoint(endpoint) {
336 return Ok(Self::Tcp(addr));
337 }
338 Ok(Self::TcpHost(if endpoint.contains(':') {
339 endpoint.into()
340 } else {
341 format!("{endpoint}:{DEFAULT_9P_PORT}")
342 }))
343 }
344 }
345}
346
347#[cfg(test)]
348mod target_parse_tests {
349 use super::*;
350
351 #[test]
352 fn target_grammar_matrix() {
353 #[cfg(not(target_arch = "wasm32"))]
354 {
355 let cases = [
356 ("unix:///run/z.sock", r#"Unix("/run/z.sock")"#),
357 ("./z.sock", r#"Unix("./z.sock")"#),
358 ("tcp://127.0.0.1:6000", "Tcp(127.0.0.1:6000)"),
359 ("127.0.0.1", "Tcp(127.0.0.1:5564)"),
360 ("::1", "Tcp([::1]:5564)"),
361 ("leader.example", r#"TcpHost("leader.example:5564")"#),
362 ("leader.example:6000", r#"TcpHost("leader.example:6000")"#),
363 ];
364 for (spec, expected) in cases {
365 assert_eq!(format!("{:?}", spec.parse::<Target>().unwrap()), expected);
366 }
367 assert_eq!(
368 format!(
369 "{:?}",
370 Target::parse_list("retired.invalid, ,leader.example:6000,").unwrap()
371 ),
372 r#"[TcpHost("retired.invalid:5564"), TcpHost("leader.example:6000")]"#
373 );
374 }
375
376 #[cfg(target_arch = "wasm32")]
377 {
378 let targets = Target::parse_list("ws://node-a:5564, ,wss://node-b/9p").unwrap();
379 assert_eq!(targets.len(), 2);
380 assert!(matches!(&targets[0], Target::WebSocket(url) if url == "ws://node-a:5564"));
381 assert!("tcp://node-a:5564".parse::<Target>().is_err());
382 }
383
384 assert!(Target::parse_list(" , ").is_err());
385 assert!(" ".parse::<Target>().is_err());
386 }
387
388 #[cfg(not(target_arch = "wasm32"))]
389 #[test]
390 fn legacy_ipv6_ambiguity_matches_documented_mapping() {
391 let cases = [
392 ("::1", "[::1]:5564"),
393 ("::1:5564", "[::1]:5564"),
394 ("fd00::10", "[fd00::10]:5564"),
395 ("fd00::10:5564", "[fd00::10]:5564"),
396 ("tcp://fd00::10:6000", "[fd00::10]:6000"),
397 ("[fd00::10]:6000", "[fd00::10]:6000"),
398 ("[fd00::10]", "[fd00::10]:5564"),
399 ("tcp://[::1]", "[::1]:5564"),
400 ("[fd00::10:5564]", "[fd00::10:5564]:5564"),
401 ];
402
403 for (spec, expected) in cases {
404 assert_eq!(
405 spec.parse::<Target>(),
406 Ok(Target::Tcp(expected.parse::<SocketAddr>().unwrap())),
407 "target {spec:?}"
408 );
409 }
410 }
411}
412
413struct Conn {
415 writer_tx: mpsc::Sender<Vec<u8>>,
416 pending: DashMap<u16, oneshot::Sender<Bytes>>,
417 tag_ctr: AtomicU16,
418 lineage_token: AtomicU64,
420 writer_epoch: AtomicU64,
422 dead: AtomicBool,
424 base: runtime::Clock,
426 last_alive: AtomicU64,
428 probe_lock: tokio::sync::Mutex<()>,
430 writer_shutdown: Notify,
432 reader_shutdown: Notify,
434 counters: Arc<TrafficCounters>,
435}
436
437impl Conn {
438 fn shutdown(&self) {
440 self.dead.store(true, Ordering::Release);
441 self.reader_shutdown.notify_one();
442 self.writer_shutdown.notify_one();
443 }
444
445 fn mark_alive(&self) {
447 self.last_alive
448 .store(self.base.elapsed_millis(), Ordering::Relaxed);
449 }
450
451 fn within(now_ms: u64, last_ms: u64, window: Duration) -> bool {
453 now_ms.saturating_sub(last_ms) < window.as_millis() as u64
454 }
455
456 fn heard_within(&self, window: Duration) -> bool {
458 Self::within(
459 self.base.elapsed_millis(),
460 self.last_alive.load(Ordering::Relaxed),
461 window,
462 )
463 }
464
465 fn deliver(&self, frame: Bytes) {
466 self.counters
467 .bytes_received
468 .fetch_add(frame.len() as u64, Ordering::Relaxed);
469 self.mark_alive();
470 if frame.len() < P9_HEADER_SIZE {
471 warn!(
472 "9P client: response frame too short ({} bytes)",
473 frame.len()
474 );
475 return;
476 }
477 let tag = u16::from_le_bytes([frame[5], frame[6]]);
478 if let Some((_, pending)) = self.pending.remove(&tag) {
479 let _ = pending.send(frame);
480 } else {
481 debug!("9P client: response for unknown tag {tag}");
482 }
483 }
484
485 fn connection_lost(&self, reconnect: &Notify) {
486 self.dead.store(true, Ordering::Release);
487 self.pending.clear();
488 self.writer_shutdown.notify_one();
489 reconnect.notify_waiters();
490 }
491}
492
493struct PendingTag<C: std::ops::Deref<Target = Conn>> {
496 conn: C,
497 tag: u16,
498 dispatched: bool,
499}
500
501impl<C: std::ops::Deref<Target = Conn>> PendingTag<C> {
502 fn mark_dispatched(&mut self) {
503 self.dispatched = true;
504 }
505}
506
507impl<C: std::ops::Deref<Target = Conn>> Drop for PendingTag<C> {
508 fn drop(&mut self) {
509 if !self.dispatched {
510 self.conn.pending.remove(&self.tag);
511 }
512 }
513}
514
515#[derive(Clone)]
517struct FidRecord {
518 inode_id: u64,
519 root_inode: u64,
521 n_uname: u32,
522 uname: Vec<u8>,
524 opened: Option<u32>,
526}
527
528impl FidRecord {
529 fn replay_identity(&self) -> (u32, u64, Vec<u8>) {
530 (self.n_uname, self.root_inode, self.uname.clone())
531 }
532}
533
534#[derive(Clone)]
535struct LockRecord {
536 fid: u32,
537 lock_type: LockType,
538 start: u64,
539 length: u64,
540 proc_id: u32,
541 client_id: Vec<u8>,
542}
543
544#[derive(Default, Clone)]
546struct SessionState {
547 fids: HashMap<u32, FidRecord>,
548 locks: Vec<LockRecord>,
549 default_root: Option<u64>,
551}
552
553impl SessionState {
554 fn forget_fid(&mut self, fid: u32) {
555 self.fids.remove(&fid);
556 self.locks.retain(|lock| lock.fid != fid);
557 }
558}
559
560pub struct NinePClient {
561 targets: Vec<Target>,
563 conn: ArcSwap<Conn>,
565 live: AtomicBool,
567 terminal_errno: AtomicU32,
569 live_notify: Notify,
570 reconnect_notify: Arc<Notify>,
571 msize: u32,
574 msize_mismatch_warned: Arc<AtomicBool>,
576 fid_ctr: AtomicU32,
577 fid_free: Mutex<Vec<u32>>,
578 state: Mutex<SessionState>,
580 stale_fids: DashSet<u32>,
582 session_transition: tokio::sync::Mutex<()>,
584 unsynced: DashMap<u32, Unsynced>,
586 counters: Arc<TrafficCounters>,
587}
588
589#[derive(Default)]
592struct Unsynced {
593 oldest: Option<u64>,
594 generation: u64,
595 reported: bool,
596}
597
598impl Unsynced {
599 fn note(&mut self, token: u64) {
601 if self.reported || self.oldest.is_none() {
602 self.oldest = Some(token);
603 self.reported = false;
604 }
605 self.generation = self.generation.wrapping_add(1);
606 }
607
608 fn snapshot(&self) -> (Option<u64>, u64) {
609 (self.oldest, self.generation)
610 }
611
612 fn clear_if_unchanged(&mut self, generation: u64) {
614 if self.generation == generation {
615 self.oldest = None;
616 self.reported = false;
617 }
618 }
619
620 fn report_if_unchanged(&mut self, generation: u64) {
622 if self.generation == generation {
623 self.reported = true;
624 }
625 }
626}
627
628impl NinePClient {
629 #[cfg(not(target_arch = "wasm32"))]
631 pub async fn connect_tcp(addr: SocketAddr, requested_msize: u32) -> std::io::Result<Arc<Self>> {
632 Self::connect(vec![Target::Tcp(addr)], requested_msize)
633 .await
634 .map_err(|e| std::io::Error::other(e.to_string()))
635 }
636
637 #[cfg(not(target_arch = "wasm32"))]
639 pub async fn connect_unix(
640 path: impl AsRef<Path>,
641 requested_msize: u32,
642 ) -> std::io::Result<Arc<Self>> {
643 Self::connect(
644 vec![Target::Unix(path.as_ref().to_path_buf())],
645 requested_msize,
646 )
647 .await
648 .map_err(|e| std::io::Error::other(e.to_string()))
649 }
650
651 pub async fn connect_multi(
653 targets: Vec<Target>,
654 requested_msize: u32,
655 ) -> std::io::Result<Arc<Self>> {
656 Self::connect(targets, requested_msize)
657 .await
658 .map_err(|e| std::io::Error::other(e.to_string()))
659 }
660
661 #[cfg(target_arch = "wasm32")]
663 pub async fn connect_websocket(url: &str, requested_msize: u32) -> ClientResult<Arc<Self>> {
664 Self::connect(vec![Target::WebSocket(url.to_string())], requested_msize).await
665 }
666
667 async fn connect(targets: Vec<Target>, requested_msize: u32) -> ClientResult<Arc<Self>> {
668 let reconnect_notify = Arc::new(Notify::new());
669 let counters = Arc::new(TrafficCounters::default());
670 let msize_mismatch_warned = Arc::new(AtomicBool::new(false));
671 let (conn, msize) = Self::probe(
672 &targets,
673 requested_msize,
674 None,
675 Arc::clone(&reconnect_notify),
676 Arc::clone(&counters),
677 Arc::clone(&msize_mismatch_warned),
678 )
679 .await?;
680
681 let client = Arc::new(Self {
682 targets,
683 conn: ArcSwap::new(conn),
684 live: AtomicBool::new(true),
685 terminal_errno: AtomicU32::new(0),
686 live_notify: Notify::new(),
687 reconnect_notify,
688 msize,
689 msize_mismatch_warned,
690 fid_ctr: AtomicU32::new(1),
691 fid_free: Mutex::new(Vec::new()),
692 state: Mutex::new(SessionState::default()),
693 stale_fids: DashSet::new(),
694 session_transition: tokio::sync::Mutex::new(()),
695 unsynced: DashMap::new(),
696 counters,
697 });
698 client.spawn_supervisor();
699 Ok(client)
700 }
701
702 async fn connect_once(
704 target: &Target,
705 requested_msize: u32,
706 required_msize: Option<u32>,
707 reconnect_notify: Arc<Notify>,
708 counters: Arc<TrafficCounters>,
709 msize_mismatch_warned: Arc<AtomicBool>,
710 ) -> ClientResult<(Arc<Conn>, u32)> {
711 let transport = dial(target).await?;
712 let (writer_tx, writer_rx) = mpsc::channel::<Vec<u8>>(P9_CHANNEL_SIZE);
713 let conn = Arc::new(Conn {
714 writer_tx,
715 pending: DashMap::new(),
716 tag_ctr: AtomicU16::new(0),
717 lineage_token: AtomicU64::new(0),
718 writer_epoch: AtomicU64::new(0),
719 dead: AtomicBool::new(false),
720 base: runtime::Clock::now(),
721 last_alive: AtomicU64::new(0),
722 probe_lock: tokio::sync::Mutex::new(()),
723 writer_shutdown: Notify::new(),
724 reader_shutdown: Notify::new(),
725 counters,
726 });
727
728 match transport {
729 #[cfg(not(target_arch = "wasm32"))]
730 DialedTransport::Native { read, write } => {
731 spawn_writer(
732 write,
733 writer_rx,
734 Arc::clone(&conn),
735 Arc::clone(&reconnect_notify),
736 );
737 spawn_reader(read, Arc::clone(&conn), reconnect_notify);
738 }
739 #[cfg(target_arch = "wasm32")]
740 DialedTransport::WebSocket(io) => {
741 web_transport::spawn(io, writer_rx, Arc::clone(&conn), reconnect_notify);
742 }
743 }
744
745 match runtime::timeout(PROBE_TIMEOUT, negotiate_on(&conn, requested_msize)).await {
746 Ok(Ok(msize)) => {
747 if let Some(required) = required_msize
748 && msize != required
749 {
750 if !msize_mismatch_warned.swap(true, Ordering::AcqRel) {
751 warn!(
752 "9P reconnect candidate negotiated msize {msize}; logical session requires {required}"
753 );
754 } else {
755 debug!(
756 "9P reconnect candidate still negotiates msize {msize}; logical session requires {required}"
757 );
758 }
759 conn.shutdown();
760 return Err(ClientError::Unexpected("version"));
761 }
762 Ok((conn, msize))
763 }
764 Ok(Err(e)) => {
766 conn.shutdown();
767 Err(e)
768 }
769 Err(_) => {
770 conn.shutdown();
771 Err(ClientError::Disconnected)
772 }
773 }
774 }
775
776 async fn probe(
778 targets: &[Target],
779 requested_msize: u32,
780 required_msize: Option<u32>,
781 reconnect_notify: Arc<Notify>,
782 counters: Arc<TrafficCounters>,
783 msize_mismatch_warned: Arc<AtomicBool>,
784 ) -> ClientResult<(Arc<Conn>, u32)> {
785 let mut probes = FuturesUnordered::new();
786 for target in targets {
787 let target = target.clone();
788 let notify = Arc::clone(&reconnect_notify);
789 let counters = Arc::clone(&counters);
790 let warned = Arc::clone(&msize_mismatch_warned);
791 probes.push(async move {
792 Self::connect_once(
793 &target,
794 requested_msize,
795 required_msize,
796 notify,
797 counters,
798 warned,
799 )
800 .await
801 });
802 }
803
804 let mut last_err = None;
805 let mut winner = None;
806 while let Some(res) = probes.next().await {
807 match res {
808 Ok(triple) => {
809 winner = Some(triple);
810 break;
811 }
812 Err(e) => last_err = Some(e),
813 }
814 }
815
816 if !probes.is_empty() {
818 runtime::spawn(async move {
819 while let Some(res) = probes.next().await {
820 if let Ok((conn, _)) = res {
821 conn.shutdown();
822 }
823 }
824 });
825 }
826
827 winner.ok_or_else(|| last_err.unwrap_or(ClientError::Disconnected))
828 }
829
830 fn spawn_supervisor(self: &Arc<Self>) {
832 let weak = Arc::downgrade(self);
833 let notify = Arc::clone(&self.reconnect_notify);
834 runtime::spawn(async move {
835 loop {
836 loop {
838 let notified = notify.notified();
839 tokio::pin!(notified);
840 notified.as_mut().enable();
841 let this = match weak.upgrade() {
842 Some(t) => t,
843 None => return,
844 };
845 if this.conn.load().dead.load(Ordering::Acquire) {
846 this.live.store(false, Ordering::Release);
847 break;
848 }
849 drop(this);
850 notified.await;
851 }
852
853 warn!("9P connection lost; reconnecting and replaying session…");
854 let mut backoff = RECONNECT_BACKOFF_MIN;
855 loop {
856 let this = match weak.upgrade() {
857 Some(t) => t,
858 None => return,
859 };
860 match this.reconnect_once().await {
861 Ok(()) => {
862 this.live.store(true, Ordering::Release);
863 this.live_notify.notify_waiters();
864 info!("9P session reconnected and restored");
865 break;
866 }
867 Err(e) => {
868 if this.terminal_errno.load(Ordering::Acquire) != 0 {
869 warn!("9P session replay failed permanently: {e}");
870 return;
871 }
872 debug!("9P reconnect failed ({e}); retrying in {backoff:?}");
873 drop(this);
874 runtime::sleep(backoff).await;
875 backoff = (backoff * 2).min(RECONNECT_BACKOFF_MAX);
876 }
877 }
878 }
879 }
880 });
881 }
882
883 async fn reconnect_once(&self) -> ClientResult<()> {
885 let msize = self.msize();
886 let (conn, msize) = Self::probe(
887 &self.targets,
888 msize,
889 Some(msize),
890 Arc::clone(&self.reconnect_notify),
891 Arc::clone(&self.counters),
892 Arc::clone(&self.msize_mismatch_warned),
893 )
894 .await?;
895
896 let _transition = self.session_transition.lock().await;
898 debug_assert_eq!(msize, self.msize);
899 match runtime::timeout(REPLAY_TIMEOUT, self.replay(&conn)).await {
900 Ok(Ok(())) => {}
901 Ok(Err(e)) => {
902 self.discard_replay_candidate(&conn).await;
903 return Err(e);
904 }
905 Err(_) => {
906 self.discard_replay_candidate(&conn).await;
907 warn!("9P session replay stalled past {REPLAY_TIMEOUT:?}; retrying reconnect");
908 return Err(ClientError::Disconnected);
909 }
910 }
911 let old = self.conn.swap(conn);
912 old.dead.store(true, Ordering::Release);
913 old.writer_shutdown.notify_one();
914
915 Ok(())
916 }
917
918 async fn discard_replay_candidate(&self, conn: &Conn) {
921 match runtime::timeout(PROBE_TIMEOUT, version_on(conn, self.msize)).await {
922 Ok(Ok(_)) => {}
923 Ok(Err(error)) => {
924 debug!("9P replay candidate reset failed ({error}); closing connection");
925 }
926 Err(_) => {
927 debug!("9P replay candidate reset timed out; closing connection");
928 }
929 }
930 conn.shutdown();
931 }
932
933 async fn replay(&self, conn: &Conn) -> ClientResult<()> {
935 let snapshot = self.state.lock().unwrap().clone();
936 let mut gone_unopened = Vec::new();
937 let mut stale_opened = Vec::new();
938
939 let (roots, descendants): (Vec<_>, Vec<_>) = snapshot
940 .fids
941 .iter()
942 .partition(|(_, rec)| rec.inode_id == rec.root_inode);
943 for (&fid, rec) in roots.into_iter().chain(descendants) {
944 let has_lock = snapshot.locks.iter().any(|lock| lock.fid == fid);
945 let restored = self.replay_fid(conn, fid, rec).await?;
946 if !restored {
947 if has_lock {
948 return Err(self.replay_state_lost("fid with a held lock could not be rebound"));
949 }
950 if rec.opened.is_some() {
951 stale_opened.push(fid);
952 } else {
953 gone_unopened.push(fid);
954 }
955 continue;
956 }
957 if let Some(flags) = rec.opened {
958 match Self::send_raw_rpc(conn, Message::Tlopen(Tlopen { fid, flags })).await {
959 Ok(Message::Rlopen(_)) => {}
960 Ok(_) => {
961 return Err(
962 self.replay_state_lost("opened fid returned a non-Rlopen reply")
963 );
964 }
965 Err(ClientError::Errno(errno)) if Self::replay_state_lost_errno(errno) => {
966 if has_lock {
967 return Err(self.replay_state_lost("locked fid could not be reopened"));
968 }
969 match Self::send_raw_rpc(conn, Message::Tclunk(Tclunk { fid })).await {
970 Ok(Message::Rclunk(_)) => {}
971 Ok(_) => return Err(ClientError::Unexpected("replay clunk")),
972 Err(error) => return Err(error),
973 }
974 stale_opened.push(fid);
975 }
976 Err(e) => return Err(e),
977 }
978 }
979 }
980
981 let mut conflict_backoff = RECONNECT_BACKOFF_MIN;
982 'lock_replay: loop {
983 let mut acquired = Vec::new();
985 for lk in &snapshot.locks {
986 let body = Message::Tlock(Tlock {
987 fid: lk.fid,
988 lock_type: lk.lock_type,
989 flags: 0,
990 start: lk.start,
991 length: lk.length,
992 proc_id: lk.proc_id,
993 client_id: P9String::new(lk.client_id.clone()),
994 });
995 match Self::send_raw_rpc(conn, body).await {
996 Ok(Message::Rlock(r)) if matches!(r.status, LockStatus::Success) => {
997 acquired.push(lk);
998 }
999 Ok(Message::Rlock(Rlock {
1000 status: LockStatus::Blocked,
1001 }))
1002 | Err(ClientError::Errno(linux::EAGAIN)) => {
1003 let rolled_back = Self::rollback_replayed_locks(conn, &acquired).await;
1004 if !rolled_back {
1005 return Err(ClientError::Errno(linux::EAGAIN));
1006 }
1007 runtime::sleep(conflict_backoff).await;
1008 conflict_backoff = (conflict_backoff * 2).min(RECONNECT_BACKOFF_MAX);
1009 continue 'lock_replay;
1010 }
1011 Ok(Message::Rlock(_)) => {
1012 Self::rollback_replayed_locks(conn, &acquired).await;
1013 return Err(self.replay_state_lost("recorded lock was not reacquired"));
1014 }
1015 Ok(_) => {
1016 Self::rollback_replayed_locks(conn, &acquired).await;
1017 return Err(
1018 self.replay_state_lost("lock replay returned a non-Rlock reply")
1019 );
1020 }
1021 Err(ClientError::Errno(errno)) if Self::replay_state_lost_errno(errno) => {
1022 Self::rollback_replayed_locks(conn, &acquired).await;
1023 return Err(self.replay_state_lost("recorded lock was refused"));
1024 }
1025 Err(e) => {
1026 Self::rollback_replayed_locks(conn, &acquired).await;
1027 return Err(e);
1028 }
1029 }
1030 }
1031 if !gone_unopened.is_empty() || !stale_opened.is_empty() {
1032 let mut state = self.state.lock().unwrap();
1033 for fid in gone_unopened.iter().chain(&stale_opened) {
1034 state.fids.remove(fid);
1035 }
1036 state.locks.retain(|lock| {
1037 !gone_unopened.contains(&lock.fid) && !stale_opened.contains(&lock.fid)
1038 });
1039 drop(state);
1040 for fid in &stale_opened {
1041 self.stale_fids.insert(*fid);
1042 }
1043 }
1044 return Ok(());
1045 }
1046 }
1047
1048 async fn rollback_replayed_locks(conn: &Conn, acquired: &[&LockRecord]) -> bool {
1050 if acquired.is_empty() {
1051 return true;
1052 }
1053 let rollback = async {
1054 let mut requests = FuturesUnordered::new();
1055 for lk in acquired.iter().rev() {
1056 requests.push(Self::send_raw_rpc(
1057 conn,
1058 Message::Tlock(Tlock {
1059 fid: lk.fid,
1060 lock_type: LockType::Unlock,
1061 flags: 0,
1062 start: lk.start,
1063 length: lk.length,
1064 proc_id: lk.proc_id,
1065 client_id: P9String::new(lk.client_id.clone()),
1066 }),
1067 ));
1068 }
1069 let mut acknowledged = true;
1070 while let Some(result) = requests.next().await {
1071 if !matches!(
1072 result,
1073 Ok(Message::Rlock(Rlock {
1074 status: LockStatus::Success
1075 }))
1076 ) {
1077 debug!("9P lock replay rollback was not acknowledged");
1078 acknowledged = false;
1079 }
1080 }
1081 acknowledged
1082 };
1083 match runtime::timeout(PROBE_TIMEOUT, rollback).await {
1084 Ok(acknowledged) => acknowledged,
1085 Err(_) => {
1086 warn!("9P lock replay rollback timed out; discarding candidate session");
1087 false
1088 }
1089 }
1090 }
1091
1092 async fn replay_fid(&self, conn: &Conn, fid: u32, rec: &FidRecord) -> ClientResult<bool> {
1094 let body = Message::Trebind(Trebind {
1095 fid,
1096 inode_id: rec.inode_id,
1097 root_inode: rec.root_inode,
1098 flags: P9_REBIND_REPLAY
1099 | if rec.opened.is_some() {
1100 P9_REBIND_OPENED
1101 } else {
1102 0
1103 },
1104 uname: P9String::new(rec.uname.clone()),
1105 n_uname: rec.n_uname,
1106 });
1107 match Self::send_raw_rpc(conn, body).await {
1108 Ok(Message::Rrebind(_)) => Ok(true),
1109 Ok(_) => Err(ClientError::Unexpected("replay fid")),
1110 Err(ClientError::Errno(errno)) if Self::replay_state_lost_errno(errno) => Ok(false),
1111 Err(e) => Err(e),
1112 }
1113 }
1114
1115 fn replay_state_lost_errno(errno: u32) -> bool {
1116 matches!(errno, linux::ENOENT | linux::ESTALE)
1117 }
1118
1119 fn validate_fids(&self, fids: impl IntoIterator<Item = u32>) -> ClientResult<()> {
1120 if fids.into_iter().any(|fid| self.stale_fids.contains(&fid)) {
1121 return Err(ClientError::Errno(linux::ESTALE));
1122 }
1123 Ok(())
1124 }
1125
1126 pub fn msize(&self) -> u32 {
1128 self.msize
1129 }
1130
1131 pub fn is_connected(&self) -> bool {
1134 self.live.load(Ordering::Acquire)
1135 }
1136
1137 pub fn traffic_stats(&self) -> TrafficStats {
1139 TrafficStats {
1140 bytes_sent: self.counters.bytes_sent.load(Ordering::Relaxed),
1141 bytes_received: self.counters.bytes_received.load(Ordering::Relaxed),
1142 operations: self.counters.operations.load(Ordering::Relaxed),
1143 }
1144 }
1145
1146 fn note_unsynced(&self, fid: u32, token: u64) {
1148 self.unsynced.entry(fid).or_default().note(token);
1149 }
1150
1151 fn snapshot_unsynced(&self, fid: u32) -> (Option<u64>, u64) {
1153 self.unsynced.get(&fid).map_or((None, 0), |u| u.snapshot())
1154 }
1155
1156 fn clear_unsynced_if_unchanged(&self, fid: u32, generation: u64) {
1159 if let Some(mut u) = self.unsynced.get_mut(&fid) {
1160 u.clear_if_unchanged(generation);
1161 }
1162 self.unsynced.remove_if(&fid, |_, u| u.oldest.is_none());
1163 }
1164
1165 fn report_unsynced_if_unchanged(&self, fid: u32, generation: u64) {
1167 if let Some(mut u) = self.unsynced.get_mut(&fid) {
1168 u.report_if_unchanged(generation);
1169 }
1170 }
1171
1172 fn forget_unsynced(&self, fid: u32) {
1174 self.unsynced.remove(&fid);
1175 }
1176
1177 pub fn max_io(&self) -> u32 {
1180 self.msize().saturating_sub(P9_IOHDRSZ)
1181 }
1182
1183 pub fn max_write_payload(&self) -> u32 {
1188 self.msize()
1189 .saturating_sub(P9_TWRITE_HDR)
1190 .saturating_sub(P9_OP_ENVELOPE_LEN as u32)
1191 }
1192
1193 pub fn alloc_fid(&self) -> u32 {
1195 if let Some(fid) = self.fid_free.lock().unwrap().pop() {
1196 return fid;
1197 }
1198 self.fid_ctr.fetch_add(1, Ordering::Relaxed)
1199 }
1200
1201 pub fn free_fid(&self, fid: u32) {
1203 self.forget_unsynced(fid);
1204 self.stale_fids.remove(&fid);
1205 self.fid_free.lock().unwrap().push(fid);
1206 }
1207
1208 pub fn outstanding_fids(&self) -> usize {
1210 let allocated = self.fid_ctr.load(Ordering::Relaxed).saturating_sub(1) as usize;
1211 allocated.saturating_sub(self.fid_free.lock().unwrap().len())
1212 }
1213
1214 fn fail_session(&self, errno: u32) {
1216 let _ = self
1217 .terminal_errno
1218 .compare_exchange(0, errno, Ordering::AcqRel, Ordering::Acquire);
1219 self.live.store(false, Ordering::Release);
1220 self.live_notify.notify_waiters();
1221 }
1222
1223 fn replay_state_lost(&self, reason: &str) -> ClientError {
1224 warn!("9P session replay cannot preserve observed state: {reason}");
1225 self.fail_session(linux::ESTALE);
1226 ClientError::Errno(linux::ESTALE)
1227 }
1228
1229 async fn wait_until_live(&self) -> ClientResult<()> {
1231 loop {
1232 let notified = self.live_notify.notified();
1233 tokio::pin!(notified);
1234 notified.as_mut().enable();
1236 let terminal_errno = self.terminal_errno.load(Ordering::Acquire);
1237 if terminal_errno != 0 {
1238 return Err(ClientError::Errno(terminal_errno));
1239 }
1240 if self.live.load(Ordering::Acquire) {
1241 return Ok(());
1242 }
1243 notified.await;
1244 }
1245 }
1246
1247 fn register_tag(
1250 conn: &Conn,
1251 tag: u16,
1252 tx: oneshot::Sender<Bytes>,
1253 ) -> Result<u16, oneshot::Sender<Bytes>> {
1254 match conn.pending.entry(tag) {
1255 Entry::Vacant(slot) => {
1256 slot.insert(tx);
1257 Ok(tag)
1258 }
1259 Entry::Occupied(_) => Err(tx),
1260 }
1261 }
1262
1263 fn alloc_tag(
1265 conn: &Conn,
1266 mut tx: oneshot::Sender<Bytes>,
1267 ) -> Result<u16, oneshot::Sender<Bytes>> {
1268 for _ in 0..=usize::from(NOTAG) {
1270 let candidate = conn.tag_ctr.fetch_add(1, Ordering::Relaxed);
1271 if candidate == NOTAG {
1272 continue;
1273 }
1274 match Self::register_tag(conn, candidate, tx) {
1275 Ok(tag) => return Ok(tag),
1276 Err(returned) => tx = returned,
1277 }
1278 }
1279 Err(tx)
1280 }
1281
1282 async fn send_request_on_current(
1284 &self,
1285 op_id: [u8; 16],
1286 body: Message,
1287 attempt: &mut OpAttemptState,
1288 stateful_dispatched_conn: Option<&Mutex<Option<Arc<Conn>>>>,
1289 ) -> ClientResult<(Message, Arc<Conn>)> {
1290 'resend: loop {
1291 self.validate_fids(body.request_fids())?;
1292 await_resend_bounded(attempt, self.wait_until_live()).await??;
1293 let conn = self.conn.load_full();
1294 self.validate_fids(body.request_fids())?;
1295
1296 let permit = match await_resend_bounded(attempt, conn.writer_tx.reserve()).await? {
1298 Ok(permit) => permit,
1299 Err(_) => {
1300 runtime::yield_now().await;
1301 continue;
1302 }
1303 };
1304
1305 if conn.dead.load(Ordering::Acquire) {
1307 drop(permit);
1308 self.reconnect_notify.notify_waiters();
1309 runtime::yield_now().await;
1310 continue;
1311 }
1312
1313 let (otx, mut orx) = oneshot::channel();
1314 let tag = match Self::alloc_tag(&conn, otx) {
1315 Ok(tag) => tag,
1316 Err(_) => {
1317 drop(permit);
1319 self.force_reprobe(&conn);
1320 runtime::yield_now().await;
1321 continue;
1322 }
1323 };
1324 let mut pending = PendingTag {
1325 conn: Arc::clone(&conn),
1326 tag,
1327 dispatched: false,
1328 };
1329
1330 if conn.dead.load(Ordering::Acquire) {
1333 drop(permit);
1334 runtime::yield_now().await;
1335 continue;
1336 }
1337
1338 let has_op_id = op_id != [0u8; 16];
1340 let connection_epoch = conn.writer_epoch.load(Ordering::Relaxed);
1341 let (op_flags, ()) =
1342 attempt.dispatch_frame(has_op_id, connection_epoch, |op_flags, origin_epoch| {
1343 let bytes = P9Message::new_with_op_id_flags_and_origin(
1344 tag,
1345 op_id,
1346 op_flags,
1347 origin_epoch,
1348 body.clone(),
1349 )
1350 .to_bytes_ctx(true)
1351 .map_err(ClientError::Codec)?;
1352
1353 pending.mark_dispatched();
1355 permit.send(bytes);
1356 if let Some(dispatched_conn) = stateful_dispatched_conn {
1357 *dispatched_conn.lock().unwrap() = Some(Arc::clone(&conn));
1358 }
1359 Ok(())
1360 })?;
1361 let mut extra_windows = 0u32;
1363 let frame = loop {
1364 match runtime::timeout(REQUEST_TIMEOUT, &mut orx).await {
1365 Ok(Ok(frame)) => break frame,
1366 Ok(Err(_)) => {
1367 runtime::yield_now().await;
1369 continue 'resend;
1370 }
1371 Err(_) => {
1372 if extra_windows < MAX_LIVENESS_EXTRA_WINDOWS
1373 && Self::conn_alive(&conn).await
1374 {
1375 extra_windows += 1;
1376 continue;
1377 }
1378 self.force_reprobe(&conn);
1379 runtime::yield_now().await;
1380 continue 'resend;
1381 }
1382 }
1383 };
1384
1385 let (_, msg) = P9Message::from_bytes((&frame, 0)).map_err(ClientError::Codec)?;
1386 if let Message::Rlerror(ref e) = msg.body
1387 && matches!(e.ecode, P9_ENOTLEADER | P9_ENOTLEADER_CLEAN)
1388 {
1389 if e.ecode == P9_ENOTLEADER_CLEAN {
1390 attempt.proven_predispatch(op_flags);
1391 if let Some(dispatched_conn) = stateful_dispatched_conn {
1392 dispatched_conn.lock().unwrap().take();
1393 }
1394 }
1395 self.force_reprobe(&conn);
1396 runtime::yield_now().await;
1397 continue;
1398 }
1399 if !matches!(msg.body, Message::Rlerror(_)) {
1401 let token = conn.lineage_token.load(Ordering::Relaxed);
1402 for fid in body.durability_fids() {
1403 self.note_unsynced(fid, token);
1404 }
1405 }
1406 return Ok((msg.body, conn));
1407 }
1408 }
1409
1410 async fn send_request(&self, body: Message) -> ClientResult<Message> {
1412 let op_id = if body.is_mutation() {
1413 Uuid::new_v4().into_bytes()
1414 } else {
1415 [0u8; 16]
1416 };
1417 let mut attempt = OpAttemptState::default();
1418 self.send_request_on_current(op_id, body, &mut attempt, None)
1419 .await
1420 .map(|(response, _)| response)
1421 }
1422
1423 async fn accept_stateful_response(
1425 &self,
1426 response_conn: &Arc<Conn>,
1427 ) -> Option<tokio::sync::MutexGuard<'_, ()>> {
1428 let transition = self.session_transition.lock().await;
1429 let current = self.conn.load_full();
1430 if self.live.load(Ordering::Acquire)
1431 && !response_conn.dead.load(Ordering::Acquire)
1432 && Arc::ptr_eq(response_conn, ¤t)
1433 {
1434 Some(transition)
1435 } else {
1436 None
1437 }
1438 }
1439
1440 async fn send_stateful_request(
1443 &self,
1444 body: Message,
1445 ) -> ClientResult<(Message, tokio::sync::MutexGuard<'_, ()>)> {
1446 let op_id = if body.is_mutation() {
1447 Uuid::new_v4().into_bytes()
1448 } else {
1449 [0u8; 16]
1450 };
1451 let mut attempt = OpAttemptState::default();
1452 let dispatched_conn = Mutex::new(None);
1453 let mut cancellation = StatefulCancellationGuard {
1454 client: self,
1455 dispatched_conn: &dispatched_conn,
1456 armed: true,
1457 };
1458 let result = loop {
1459 let (response, response_conn) = match self
1460 .send_request_on_current(op_id, body.clone(), &mut attempt, Some(&dispatched_conn))
1461 .await
1462 {
1463 Ok(response) => response,
1464 Err(error) => break Err(error),
1465 };
1466 if let Some(transition) = self.accept_stateful_response(&response_conn).await {
1467 break Ok((response, transition));
1468 }
1469 };
1470 if !matches!(
1472 &result,
1473 Err(_)
1474 | Ok((
1475 Message::Rlerror(Rlerror {
1476 ecode: P9_EOPIDSTALE
1477 }),
1478 _
1479 ))
1480 ) {
1481 cancellation.disarm();
1482 }
1483 result
1484 }
1485
1486 async fn conn_alive(conn: &Conn) -> bool {
1488 if conn.dead.load(Ordering::Acquire) {
1489 return false;
1490 }
1491 if conn.heard_within(LIVENESS_WINDOW) {
1492 return true;
1493 }
1494 let _guard = conn.probe_lock.lock().await;
1495 if conn.dead.load(Ordering::Acquire) {
1496 return false;
1497 }
1498 if conn.heard_within(LIVENESS_WINDOW) {
1499 return true;
1500 }
1501 matches!(
1502 runtime::timeout(PROBE_TIMEOUT, query_lineage_token(conn)).await,
1503 Ok(Ok(()))
1504 )
1505 }
1506
1507 fn force_reprobe(&self, conn: &Arc<Conn>) {
1509 conn.shutdown();
1510 self.reconnect_notify.notify_waiters();
1511 }
1512
1513 async fn send_raw(conn: &Conn, body: Message) -> ClientResult<Message> {
1516 Self::send_raw_at_tag(conn, None, body).await
1517 }
1518
1519 async fn send_raw_at_tag(
1521 conn: &Conn,
1522 exact_tag: Option<u16>,
1523 body: Message,
1524 ) -> ClientResult<Message> {
1525 let permit = conn
1526 .writer_tx
1527 .reserve()
1528 .await
1529 .map_err(|_| ClientError::Disconnected)?;
1530 if conn.dead.load(Ordering::Acquire) {
1531 drop(permit);
1532 return Err(ClientError::Disconnected);
1533 }
1534 let (otx, orx) = oneshot::channel();
1535 let tag = match exact_tag {
1536 Some(tag) => Self::register_tag(conn, tag, otx)
1537 .map_err(|_| ClientError::Unexpected("raw tag already registered"))?,
1538 None => match Self::alloc_tag(conn, otx) {
1539 Ok(tag) => tag,
1540 Err(_) => {
1541 drop(permit);
1542 conn.shutdown();
1543 return Err(ClientError::Disconnected);
1544 }
1545 },
1546 };
1547 let mut pending = PendingTag {
1548 conn,
1549 tag,
1550 dispatched: false,
1551 };
1552 if conn.dead.load(Ordering::Acquire) {
1553 drop(permit);
1554 return Err(ClientError::Disconnected);
1555 }
1556 let bytes = match P9Message::new(tag, body).to_bytes() {
1557 Ok(b) => b,
1558 Err(e) => return Err(ClientError::Codec(e)),
1559 };
1560 pending.mark_dispatched();
1561 permit.send(bytes);
1562 let frame = orx.await.map_err(|_| ClientError::Disconnected)?;
1563 let (_, msg) = P9Message::from_bytes((&frame, 0)).map_err(ClientError::Codec)?;
1564 if msg.tag != tag {
1565 return Err(ClientError::Unexpected("response tag"));
1566 }
1567 Ok(msg.body)
1568 }
1569
1570 async fn send_raw_rpc(conn: &Conn, body: Message) -> ClientResult<Message> {
1573 match Self::send_raw(conn, body).await? {
1574 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
1575 other => Ok(other),
1576 }
1577 }
1578
1579 async fn rpc(&self, body: Message) -> ClientResult<Message> {
1581 match self.send_request(body).await? {
1582 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
1583 other => Ok(other),
1584 }
1585 }
1586
1587 async fn rpc_stateful(
1589 &self,
1590 body: Message,
1591 ) -> ClientResult<(Message, tokio::sync::MutexGuard<'_, ()>)> {
1592 let (response, transition) = self.send_stateful_request(body).await?;
1593 match response {
1594 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
1595 other => Ok((other, transition)),
1596 }
1597 }
1598
1599 pub async fn attach(
1600 &self,
1601 fid: u32,
1602 afid: u32,
1603 uname: &str,
1604 aname: &str,
1605 n_uname: u32,
1606 ) -> ClientResult<Qid> {
1607 let (resp, _transition) = self
1608 .rpc_stateful(Message::Tattach(Tattach {
1609 fid,
1610 afid,
1611 uname: P9String::new(uname.as_bytes().to_vec()),
1612 aname: P9String::new(aname.as_bytes().to_vec()),
1613 n_uname,
1614 }))
1615 .await?;
1616 match resp {
1617 Message::Rattach(r) => {
1618 let mut st = self.state.lock().unwrap();
1619 st.fids.insert(
1620 fid,
1621 FidRecord {
1622 inode_id: r.qid.path,
1623 root_inode: r.qid.path,
1624 n_uname,
1625 uname: uname.as_bytes().to_vec(),
1626 opened: None,
1627 },
1628 );
1629 st.default_root = Some(r.qid.path);
1630 Ok(r.qid)
1631 }
1632 _ => Err(ClientError::Unexpected("attach")),
1633 }
1634 }
1635
1636 pub async fn rebind(&self, fid: u32, inode_id: u64, n_uname: u32) -> ClientResult<Qid> {
1639 let root_inode = self.state.lock().unwrap().default_root.unwrap_or(0);
1640 let (resp, _transition) = self
1641 .rpc_stateful(Message::Trebind(Trebind {
1642 fid,
1643 inode_id,
1644 root_inode,
1645 flags: 0,
1646 uname: P9String::new(Vec::new()),
1647 n_uname,
1648 }))
1649 .await?;
1650 match resp {
1651 Message::Rrebind(r) => {
1652 self.state.lock().unwrap().fids.insert(
1653 fid,
1654 FidRecord {
1655 inode_id,
1656 root_inode,
1657 n_uname,
1658 uname: Vec::new(),
1659 opened: None,
1660 },
1661 );
1662 Ok(r.qid)
1663 }
1664 _ => Err(ClientError::Unexpected("rebind")),
1665 }
1666 }
1667
1668 pub async fn walk(&self, fid: u32, newfid: u32, names: &[&[u8]]) -> ClientResult<Vec<Qid>> {
1669 let wnames = names
1670 .iter()
1671 .map(|n| P9String::new(n.to_vec()))
1672 .collect::<Vec<_>>();
1673 let (resp, _transition) = self
1674 .rpc_stateful(Message::Twalk(Twalk {
1675 fid,
1676 newfid,
1677 nwname: wnames.len() as u16,
1678 wnames,
1679 }))
1680 .await?;
1681 match resp {
1682 Message::Rwalk(r) => {
1683 if names.is_empty() || r.wqids.len() == names.len() {
1685 let mut st = self.state.lock().unwrap();
1686 let identity = st.fids.get(&fid).map(FidRecord::replay_identity);
1687 let inode_id = if names.is_empty() {
1688 st.fids.get(&fid).map(|rec| rec.inode_id)
1689 } else {
1690 r.wqids.last().map(|q| q.path)
1691 };
1692 if let (Some(inode_id), Some((n_uname, root_inode, uname))) =
1693 (inode_id, identity)
1694 {
1695 st.fids.insert(
1696 newfid,
1697 FidRecord {
1698 inode_id,
1699 root_inode,
1700 n_uname,
1701 uname,
1702 opened: None,
1703 },
1704 );
1705 }
1706 }
1707 Ok(r.wqids)
1708 }
1709 _ => Err(ClientError::Unexpected("walk")),
1710 }
1711 }
1712
1713 pub async fn walk_getattr(
1716 &self,
1717 fid: u32,
1718 newfid: u32,
1719 names: &[&[u8]],
1720 ) -> ClientResult<(Vec<Qid>, Stat)> {
1721 let wnames = names
1722 .iter()
1723 .map(|n| P9String::new(n.to_vec()))
1724 .collect::<Vec<_>>();
1725 let (resp, _transition) = self
1726 .rpc_stateful(Message::Twalkgetattr(Twalkgetattr {
1727 fid,
1728 newfid,
1729 nwname: wnames.len() as u16,
1730 wnames,
1731 }))
1732 .await?;
1733 match resp {
1734 Message::Rwalkgetattr(r) => {
1735 {
1736 let mut st = self.state.lock().unwrap();
1737 let identity = st.fids.get(&fid).map(FidRecord::replay_identity);
1738 let inode_id = if names.is_empty() {
1739 st.fids.get(&fid).map(|rec| rec.inode_id)
1740 } else {
1741 r.wqids.last().map(|q| q.path)
1742 };
1743 if let (Some(inode_id), Some((n_uname, root_inode, uname))) =
1744 (inode_id, identity)
1745 {
1746 st.fids.insert(
1747 newfid,
1748 FidRecord {
1749 inode_id,
1750 root_inode,
1751 n_uname,
1752 uname,
1753 opened: None,
1754 },
1755 );
1756 }
1757 }
1758 Ok((r.wqids, r.stat))
1759 }
1760 _ => Err(ClientError::Unexpected("walk_getattr")),
1761 }
1762 }
1763
1764 pub async fn clunk(&self, fid: u32) -> ClientResult<()> {
1765 if self.stale_fids.contains(&fid) && self.clear_stale_fid(fid).await {
1766 return Ok(());
1767 }
1768 let (resp, _transition) = match self
1769 .send_stateful_request(Message::Tclunk(Tclunk { fid }))
1770 .await
1771 {
1772 Ok(result) => result,
1773 Err(error) => {
1774 if matches!(&error, ClientError::Errno(errno) if *errno == linux::ESTALE)
1775 && self.clear_stale_fid(fid).await
1776 {
1777 return Ok(());
1778 }
1779 return Err(error);
1780 }
1781 };
1782 let mut st = self.state.lock().unwrap();
1784 st.forget_fid(fid);
1785 drop(st);
1786 self.stale_fids.remove(&fid);
1787 match resp {
1788 Message::Rclunk(_) => Ok(()),
1789 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
1790 _ => Err(ClientError::Unexpected("clunk")),
1791 }
1792 }
1793
1794 async fn clear_stale_fid(&self, fid: u32) -> bool {
1795 let _transition = self.session_transition.lock().await;
1796 if self.terminal_errno.load(Ordering::Acquire) != 0 {
1797 return false;
1798 }
1799 if self.stale_fids.remove(&fid).is_none() {
1800 return false;
1801 }
1802 let mut state = self.state.lock().unwrap();
1803 state.forget_fid(fid);
1804 true
1805 }
1806
1807 pub async fn getattr(&self, fid: u32, mask: u64) -> ClientResult<Stat> {
1808 let resp = self
1809 .rpc(Message::Tgetattr(Tgetattr {
1810 fid,
1811 request_mask: mask,
1812 }))
1813 .await?;
1814 match resp {
1815 Message::Rgetattr(r) => Ok(r.stat),
1816 _ => Err(ClientError::Unexpected("getattr")),
1817 }
1818 }
1819
1820 pub async fn setattr(&self, ts: Tsetattr) -> ClientResult<()> {
1821 match self.rpc(Message::Tsetattr(ts)).await? {
1822 Message::Rsetattr(_) => Ok(()),
1823 _ => Err(ClientError::Unexpected("setattr")),
1824 }
1825 }
1826
1827 pub async fn fallocate(
1830 &self,
1831 fid: u32,
1832 offset: u64,
1833 length: u64,
1834 mode: u32,
1835 ) -> ClientResult<()> {
1836 match self
1837 .rpc(Message::Tfallocate(Tfallocate {
1838 fid,
1839 offset,
1840 length,
1841 mode,
1842 }))
1843 .await?
1844 {
1845 Message::Rfallocate(_) => Ok(()),
1846 _ => Err(ClientError::Unexpected("fallocate")),
1847 }
1848 }
1849
1850 pub async fn setattr_attr(&self, ts: Tsetattr) -> ClientResult<Stat> {
1852 match self.rpc(Message::Tsetattrattr(ts)).await? {
1853 Message::Rsetattrattr(r) => Ok(r.stat),
1854 _ => Err(ClientError::Unexpected("setattr_attr")),
1855 }
1856 }
1857
1858 pub async fn lopen(&self, fid: u32, flags: u32) -> ClientResult<(Qid, u32)> {
1859 let (response, _transition) = self
1860 .rpc_stateful(Message::Tlopen(Tlopen { fid, flags }))
1861 .await?;
1862 match response {
1863 Message::Rlopen(r) => {
1864 if let Some(rec) = self.state.lock().unwrap().fids.get_mut(&fid) {
1865 rec.opened = Some(flags);
1866 }
1867 Ok((r.qid, r.iounit))
1868 }
1869 _ => Err(ClientError::Unexpected("lopen")),
1870 }
1871 }
1872
1873 pub async fn lopenat(&self, fid: u32, newfid: u32, flags: u32) -> ClientResult<(Qid, u32)> {
1876 let (resp, _transition) = self
1877 .rpc_stateful(Message::Tlopenat(Tlopenat { fid, newfid, flags }))
1878 .await?;
1879 match resp {
1880 Message::Rlopenat(r) => {
1881 let mut st = self.state.lock().unwrap();
1882 if let Some((n_uname, root_inode, uname)) =
1883 st.fids.get(&fid).map(FidRecord::replay_identity)
1884 {
1885 st.fids.insert(
1886 newfid,
1887 FidRecord {
1888 inode_id: r.qid.path,
1889 root_inode,
1890 n_uname,
1891 uname,
1892 opened: Some(flags),
1893 },
1894 );
1895 }
1896 Ok((r.qid, r.iounit))
1897 }
1898 _ => Err(ClientError::Unexpected("lopenat")),
1899 }
1900 }
1901
1902 pub async fn lopenatread(
1908 &self,
1909 fid: u32,
1910 newfid: u32,
1911 flags: u32,
1912 count: u32,
1913 ) -> ClientResult<(Qid, u32, Bytes, bool)> {
1914 let (resp, _transition) = self
1915 .rpc_stateful(Message::Tlopenatread(Tlopenatread {
1916 fid,
1917 newfid,
1918 flags,
1919 count,
1920 }))
1921 .await?;
1922 match resp {
1923 Message::Rlopenatread(r) => {
1924 let mut st = self.state.lock().unwrap();
1925 if let Some((n_uname, root_inode, uname)) =
1926 st.fids.get(&fid).map(FidRecord::replay_identity)
1927 {
1928 st.fids.insert(
1929 newfid,
1930 FidRecord {
1931 inode_id: r.qid.path,
1932 root_inode,
1933 n_uname,
1934 uname,
1935 opened: Some(flags),
1936 },
1937 );
1938 }
1939 drop(st);
1940 Ok((r.qid, r.iounit, r.data.0, r.eof != 0))
1941 }
1942 _ => Err(ClientError::Unexpected("lopenatread")),
1943 }
1944 }
1945
1946 pub async fn lcreate(
1947 &self,
1948 fid: u32,
1949 name: &[u8],
1950 flags: u32,
1951 mode: u32,
1952 gid: u32,
1953 ) -> ClientResult<(Qid, u32)> {
1954 let (resp, _transition) = self
1955 .rpc_stateful(Message::Tlcreate(Tlcreate {
1956 fid,
1957 name: P9String::new(name.to_vec()),
1958 flags,
1959 mode,
1960 gid,
1961 }))
1962 .await?;
1963 match resp {
1964 Message::Rlcreate(r) => {
1965 let reopen = flags & !(linux::O_CREAT | linux::O_EXCL | linux::O_TRUNC);
1967 let mut st = self.state.lock().unwrap();
1968 if let Some(rec) = st.fids.get_mut(&fid) {
1969 rec.inode_id = r.qid.path;
1970 rec.opened = Some(reopen);
1971 }
1972 Ok((r.qid, r.iounit))
1973 }
1974 _ => Err(ClientError::Unexpected("lcreate")),
1975 }
1976 }
1977
1978 pub async fn lcreateattr(
1983 &self,
1984 dfid: u32,
1985 newfid: u32,
1986 name: &[u8],
1987 flags: u32,
1988 mode: u32,
1989 gid: u32,
1990 ) -> ClientResult<(Stat, u32)> {
1991 let (resp, _transition) = self
1992 .rpc_stateful(Message::Tlcreateattr(Tlcreateattr {
1993 dfid,
1994 newfid,
1995 name: P9String::new(name.to_vec()),
1996 flags,
1997 mode,
1998 gid,
1999 }))
2000 .await?;
2001 match resp {
2002 Message::Rlcreateattr(r) => {
2003 let reopen = flags & !(linux::O_CREAT | linux::O_EXCL | linux::O_TRUNC);
2004 let mut st = self.state.lock().unwrap();
2005 if let Some((n_uname, root_inode, uname)) =
2006 st.fids.get(&dfid).map(FidRecord::replay_identity)
2007 {
2008 st.fids.insert(
2009 newfid,
2010 FidRecord {
2011 inode_id: r.stat.qid.path,
2012 root_inode,
2013 n_uname,
2014 uname,
2015 opened: Some(reopen),
2016 },
2017 );
2018 }
2019 Ok((r.stat, r.iounit))
2020 }
2021 _ => Err(ClientError::Unexpected("lcreateattr")),
2022 }
2023 }
2024
2025 pub async fn read(&self, fid: u32, offset: u64, size: u32) -> ClientResult<Vec<u8>> {
2028 Ok(self.read_bytes(fid, offset, size).await?.into())
2029 }
2030
2031 pub async fn read_bytes(&self, fid: u32, offset: u64, size: u32) -> ClientResult<Bytes> {
2035 if size == 0 {
2036 return Ok(Bytes::new());
2037 }
2038 let size = size as usize;
2039 let max = self.max_io().max(1) as usize;
2040 let first_count = size.min(max);
2041 let first = self.read_once(fid, offset, first_count as u32).await?;
2042 if size <= max || first.len() < first_count {
2043 return Ok(first);
2044 }
2045 let mut out = BytesMut::with_capacity(size.min(max.saturating_mul(2)));
2047 let mut off = offset + first.len() as u64;
2048 out.extend_from_slice(&first);
2049 while out.len() < size {
2050 let want = (size - out.len()).min(max);
2051 let data = self.read_once(fid, off, want as u32).await?;
2052 let got = data.len();
2053 out.extend_from_slice(&data);
2054 off += got as u64;
2055 if got < want {
2056 break;
2057 }
2058 }
2059 Ok(out.freeze())
2060 }
2061
2062 async fn read_once(&self, fid: u32, offset: u64, count: u32) -> ClientResult<Bytes> {
2063 let resp = self
2064 .rpc(Message::Tread(Tread { fid, offset, count }))
2065 .await?;
2066 match resp {
2067 Message::Rread(r) if r.data.len() <= count as usize => Ok(r.data.0),
2068 Message::Rread(_) => Err(ClientError::Unexpected("read count")),
2069 _ => Err(ClientError::Unexpected("read")),
2070 }
2071 }
2072
2073 pub async fn write(&self, fid: u32, offset: u64, data: &[u8]) -> ClientResult<u64> {
2076 let mut written = 0usize;
2077 while written < data.len() {
2078 let (n, attempted) = self
2079 .write_once(fid, offset + written as u64, &data[written..])
2080 .await?;
2081 written += n as usize;
2082 if n < attempted {
2083 break;
2084 }
2085 }
2086 Ok(written as u64)
2087 }
2088
2089 async fn write_once(
2090 &self,
2091 fid: u32,
2092 offset: u64,
2093 remaining: &[u8],
2094 ) -> ClientResult<(u32, u32)> {
2095 let attempted = remaining
2096 .len()
2097 .min(self.max_write_payload().max(1) as usize);
2098 let data = &remaining[..attempted];
2099 let resp = self
2100 .rpc(Message::Twrite(Twrite {
2101 fid,
2102 offset,
2103 count: attempted as u32,
2104 data: DekuBytes::from(data.to_vec()),
2105 }))
2106 .await?;
2107 match resp {
2108 Message::Rwrite(r) if r.count <= attempted as u32 => Ok((r.count, attempted as u32)),
2109 Message::Rwrite(_) => Err(ClientError::Unexpected("write count")),
2110 _ => Err(ClientError::Unexpected("write")),
2111 }
2112 }
2113
2114 pub async fn readdir(&self, fid: u32, offset: u64, count: u32) -> ClientResult<Vec<DirEntry>> {
2115 let resp = self
2116 .rpc(Message::Treaddir(Treaddir { fid, offset, count }))
2117 .await?;
2118 match resp {
2119 Message::Rreaddir(r) => r.to_entries().map_err(ClientError::Codec),
2120 _ => Err(ClientError::Unexpected("readdir")),
2121 }
2122 }
2123
2124 pub async fn readdirplus(
2127 &self,
2128 fid: u32,
2129 offset: u64,
2130 count: u32,
2131 ) -> ClientResult<Vec<DirEntryPlus>> {
2132 let resp = self
2133 .rpc(Message::Treaddirattr(Treaddirattr { fid, offset, count }))
2134 .await?;
2135 match resp {
2136 Message::Rreaddirattr(r) => r.to_entries().map_err(ClientError::Codec),
2137 _ => Err(ClientError::Unexpected("readdirplus")),
2138 }
2139 }
2140
2141 pub async fn mkdir(&self, dfid: u32, name: &[u8], mode: u32, gid: u32) -> ClientResult<Qid> {
2142 let resp = self
2143 .rpc(Message::Tmkdir(Tmkdir {
2144 dfid,
2145 name: P9String::new(name.to_vec()),
2146 mode,
2147 gid,
2148 }))
2149 .await?;
2150 match resp {
2151 Message::Rmkdir(r) => Ok(r.qid),
2152 _ => Err(ClientError::Unexpected("mkdir")),
2153 }
2154 }
2155
2156 pub async fn mkdir_attr(
2158 &self,
2159 dfid: u32,
2160 name: &[u8],
2161 mode: u32,
2162 gid: u32,
2163 ) -> ClientResult<Stat> {
2164 let resp = self
2165 .rpc(Message::Tmkdirattr(Tmkdir {
2166 dfid,
2167 name: P9String::new(name.to_vec()),
2168 mode,
2169 gid,
2170 }))
2171 .await?;
2172 match resp {
2173 Message::Rmkdirattr(r) => Ok(r.stat),
2174 _ => Err(ClientError::Unexpected("mkdir_attr")),
2175 }
2176 }
2177
2178 pub async fn symlink(
2179 &self,
2180 dfid: u32,
2181 name: &[u8],
2182 target: &[u8],
2183 gid: u32,
2184 ) -> ClientResult<Qid> {
2185 let resp = self
2186 .rpc(Message::Tsymlink(Tsymlink {
2187 dfid,
2188 name: P9String::new(name.to_vec()),
2189 symtgt: P9String::new(target.to_vec()),
2190 gid,
2191 }))
2192 .await?;
2193 match resp {
2194 Message::Rsymlink(r) => Ok(r.qid),
2195 _ => Err(ClientError::Unexpected("symlink")),
2196 }
2197 }
2198
2199 pub async fn symlink_attr(
2201 &self,
2202 dfid: u32,
2203 name: &[u8],
2204 target: &[u8],
2205 gid: u32,
2206 ) -> ClientResult<Stat> {
2207 let resp = self
2208 .rpc(Message::Tsymlinkattr(Tsymlink {
2209 dfid,
2210 name: P9String::new(name.to_vec()),
2211 symtgt: P9String::new(target.to_vec()),
2212 gid,
2213 }))
2214 .await?;
2215 match resp {
2216 Message::Rsymlinkattr(r) => Ok(r.stat),
2217 _ => Err(ClientError::Unexpected("symlink_attr")),
2218 }
2219 }
2220
2221 pub async fn mknod(
2222 &self,
2223 dfid: u32,
2224 name: &[u8],
2225 mode: u32,
2226 major: u32,
2227 minor: u32,
2228 gid: u32,
2229 ) -> ClientResult<Qid> {
2230 let resp = self
2231 .rpc(Message::Tmknod(Tmknod {
2232 dfid,
2233 name: P9String::new(name.to_vec()),
2234 mode,
2235 major,
2236 minor,
2237 gid,
2238 }))
2239 .await?;
2240 match resp {
2241 Message::Rmknod(r) => Ok(r.qid),
2242 _ => Err(ClientError::Unexpected("mknod")),
2243 }
2244 }
2245
2246 pub async fn mknod_attr(
2248 &self,
2249 dfid: u32,
2250 name: &[u8],
2251 mode: u32,
2252 major: u32,
2253 minor: u32,
2254 gid: u32,
2255 ) -> ClientResult<Stat> {
2256 let resp = self
2257 .rpc(Message::Tmknodattr(Tmknod {
2258 dfid,
2259 name: P9String::new(name.to_vec()),
2260 mode,
2261 major,
2262 minor,
2263 gid,
2264 }))
2265 .await?;
2266 match resp {
2267 Message::Rmknodattr(r) => Ok(r.stat),
2268 _ => Err(ClientError::Unexpected("mknod_attr")),
2269 }
2270 }
2271
2272 pub async fn readlink(&self, fid: u32) -> ClientResult<Vec<u8>> {
2273 match self.rpc(Message::Treadlink(Treadlink { fid })).await? {
2274 Message::Rreadlink(r) => Ok(r.target.data),
2275 _ => Err(ClientError::Unexpected("readlink")),
2276 }
2277 }
2278
2279 pub async fn link(&self, dfid: u32, fid: u32, name: &[u8]) -> ClientResult<()> {
2280 let resp = self
2281 .rpc(Message::Tlink(Tlink {
2282 dfid,
2283 fid,
2284 name: P9String::new(name.to_vec()),
2285 }))
2286 .await?;
2287 match resp {
2288 Message::Rlink(_) => Ok(()),
2289 _ => Err(ClientError::Unexpected("link")),
2290 }
2291 }
2292
2293 pub async fn link_attr(&self, dfid: u32, fid: u32, name: &[u8]) -> ClientResult<Stat> {
2296 let resp = self
2297 .rpc(Message::Tlinkattr(Tlink {
2298 dfid,
2299 fid,
2300 name: P9String::new(name.to_vec()),
2301 }))
2302 .await?;
2303 match resp {
2304 Message::Rlinkattr(r) => Ok(r.stat),
2305 _ => Err(ClientError::Unexpected("link_attr")),
2306 }
2307 }
2308
2309 pub async fn renameat(
2310 &self,
2311 olddirfid: u32,
2312 oldname: &[u8],
2313 newdirfid: u32,
2314 newname: &[u8],
2315 ) -> ClientResult<()> {
2316 let resp = self
2317 .rpc(Message::Trenameat(Trenameat {
2318 olddirfid,
2319 oldname: P9String::new(oldname.to_vec()),
2320 newdirfid,
2321 newname: P9String::new(newname.to_vec()),
2322 }))
2323 .await?;
2324 match resp {
2325 Message::Rrenameat(_) => Ok(()),
2326 _ => Err(ClientError::Unexpected("renameat")),
2327 }
2328 }
2329
2330 pub async fn unlinkat(&self, dirfid: u32, name: &[u8], flags: u32) -> ClientResult<()> {
2331 let resp = self
2332 .rpc(Message::Tunlinkat(Tunlinkat {
2333 dirfid,
2334 name: P9String::new(name.to_vec()),
2335 flags,
2336 }))
2337 .await?;
2338 match resp {
2339 Message::Runlinkat(_) => Ok(()),
2340 _ => Err(ClientError::Unexpected("unlinkat")),
2341 }
2342 }
2343
2344 pub async fn fsync_inode(&self, fids: &[u32], primary: u32, datasync: u32) -> ClientResult<()> {
2347 self.validate_fids(fids.iter().copied())?;
2348 let mut token: Option<u64> = None;
2350 let mut snaps: Vec<(u32, u64)> = Vec::with_capacity(fids.len());
2351 for &fid in fids {
2352 let (oldest, generation) = self.snapshot_unsynced(fid);
2353 if let Some(t) = oldest {
2354 token = Some(token.map_or(t, |w| w.min(t)));
2355 }
2356 snaps.push((fid, generation));
2357 }
2358 match self
2359 .rpc(Message::Tfsyncdur(Tfsyncdur {
2360 fid: primary,
2361 datasync,
2362 token: token.unwrap_or(0),
2363 }))
2364 .await
2365 {
2366 Ok(Message::Rfsync(_)) => {
2367 for (fid, generation) in snaps {
2368 self.clear_unsynced_if_unchanged(fid, generation);
2369 }
2370 Ok(())
2371 }
2372 Ok(_) => Err(ClientError::Unexpected("fsync")),
2373 Err(ClientError::Errno(e)) if e == linux::ESTALE => {
2374 for (fid, generation) in snaps {
2376 self.report_unsynced_if_unchanged(fid, generation);
2377 }
2378 Err(ClientError::Errno(e))
2379 }
2380 Err(e) => Err(e),
2381 }
2382 }
2383
2384 pub async fn fsync(&self, fid: u32, datasync: u32) -> ClientResult<()> {
2386 self.fsync_inode(&[fid], fid, datasync).await
2387 }
2388
2389 pub async fn fsync_all(&self, primary: u32, datasync: u32) -> ClientResult<()> {
2391 let mut fids = vec![primary];
2392 for entry in &self.unsynced {
2393 let fid = *entry.key();
2394 if fid != primary {
2395 fids.push(fid);
2396 }
2397 }
2398 self.fsync_inode(&fids, primary, datasync).await
2399 }
2400
2401 pub async fn statfs(&self, fid: u32) -> ClientResult<Rstatfs> {
2402 match self.rpc(Message::Tstatfs(Tstatfs { fid })).await? {
2403 Message::Rstatfs(r) => Ok(r),
2404 _ => Err(ClientError::Unexpected("statfs")),
2405 }
2406 }
2407
2408 #[allow(clippy::too_many_arguments)]
2413 pub async fn lock(
2414 &self,
2415 fid: u32,
2416 lock_type: LockType,
2417 flags: u32,
2418 start: u64,
2419 length: u64,
2420 proc_id: u32,
2421 client_id: &[u8],
2422 ) -> ClientResult<LockStatus> {
2423 let (resp, _transition) = self
2424 .rpc_stateful(Message::Tlock(Tlock {
2425 fid,
2426 lock_type,
2427 flags,
2428 start,
2429 length,
2430 proc_id,
2431 client_id: P9String::new(client_id.to_vec()),
2432 }))
2433 .await?;
2434 match resp {
2435 Message::Rlock(r) => {
2436 let mut st = self.state.lock().unwrap();
2437 match lock_type {
2438 LockType::Unlock if matches!(r.status, LockStatus::Success) => {
2439 unlock_recorded_range(&mut st.locks, fid, start, length);
2440 }
2441 _ if matches!(r.status, LockStatus::Success) => {
2442 replace_recorded_lock(
2443 &mut st.locks,
2444 LockRecord {
2445 fid,
2446 lock_type,
2447 start,
2448 length,
2449 proc_id,
2450 client_id: client_id.to_vec(),
2451 },
2452 );
2453 }
2454 _ => {}
2455 }
2456 Ok(r.status)
2457 }
2458 _ => Err(ClientError::Unexpected("lock")),
2459 }
2460 }
2461
2462 pub async fn getlock(
2464 &self,
2465 fid: u32,
2466 lock_type: LockType,
2467 start: u64,
2468 length: u64,
2469 proc_id: u32,
2470 client_id: &[u8],
2471 ) -> ClientResult<Rgetlock> {
2472 let resp = self
2473 .rpc(Message::Tgetlock(Tgetlock {
2474 fid,
2475 lock_type,
2476 start,
2477 length,
2478 proc_id,
2479 client_id: P9String::new(client_id.to_vec()),
2480 }))
2481 .await?;
2482 match resp {
2483 Message::Rgetlock(r) => Ok(r),
2484 _ => Err(ClientError::Unexpected("getlock")),
2485 }
2486 }
2487}
2488
2489impl Drop for NinePClient {
2490 fn drop(&mut self) {
2491 self.conn.load().shutdown();
2493 self.reconnect_notify.notify_waiters();
2494 }
2495}
2496
2497fn subtract_lock_record(
2499 held: LockRecord,
2500 unlock_start: u64,
2501 unlock_length: u64,
2502) -> Vec<LockRecord> {
2503 ninep_proto::subtract_lock_range(held.start, held.length, unlock_start, unlock_length)
2504 .into_iter()
2505 .map(|(start, length)| LockRecord {
2506 start,
2507 length,
2508 ..held.clone()
2509 })
2510 .collect()
2511}
2512
2513fn unlock_recorded_range(locks: &mut Vec<LockRecord>, fid: u32, start: u64, length: u64) {
2514 let prior = std::mem::take(locks);
2515 for held in prior {
2516 if held.fid == fid {
2517 locks.extend(subtract_lock_record(held, start, length));
2518 } else {
2519 locks.push(held);
2520 }
2521 }
2522}
2523
2524fn replace_recorded_lock(locks: &mut Vec<LockRecord>, replacement: LockRecord) {
2526 unlock_recorded_range(
2527 locks,
2528 replacement.fid,
2529 replacement.start,
2530 replacement.length,
2531 );
2532 locks.push(replacement);
2533}
2534
2535enum DialedTransport {
2536 #[cfg(not(target_arch = "wasm32"))]
2537 Native {
2538 read: Box<dyn AsyncRead + Unpin + Send>,
2539 write: Box<dyn AsyncWrite + Unpin + Send>,
2540 },
2541 #[cfg(target_arch = "wasm32")]
2542 WebSocket(web_transport::WebSocketIo),
2543}
2544
2545async fn dial(target: &Target) -> ClientResult<DialedTransport> {
2548 match target {
2549 #[cfg(not(target_arch = "wasm32"))]
2550 Target::Tcp(addr) => {
2551 let stream = runtime::timeout(PROBE_TIMEOUT, TcpStream::connect(addr))
2552 .await
2553 .map_err(|_| ClientError::Disconnected)?
2554 .map_err(|_| ClientError::Disconnected)?;
2555 configure_tcp(stream)
2556 }
2557 #[cfg(not(target_arch = "wasm32"))]
2558 Target::TcpHost(endpoint) => {
2559 let stream = runtime::timeout(PROBE_TIMEOUT, TcpStream::connect(endpoint.as_str()))
2561 .await
2562 .map_err(|_| ClientError::Disconnected)?
2563 .map_err(|_| ClientError::Disconnected)?;
2564 configure_tcp(stream)
2565 }
2566 #[cfg(not(target_arch = "wasm32"))]
2567 Target::Unix(path) => {
2568 let stream = runtime::timeout(PROBE_TIMEOUT, UnixStream::connect(path))
2569 .await
2570 .map_err(|_| ClientError::Disconnected)?
2571 .map_err(|_| ClientError::Disconnected)?;
2572 let (r, w) = stream.into_split();
2573 Ok(DialedTransport::Native {
2574 read: Box::new(r),
2575 write: Box::new(w),
2576 })
2577 }
2578 #[cfg(target_arch = "wasm32")]
2579 Target::WebSocket(url) => web_transport::connect(url)
2580 .await
2581 .map(DialedTransport::WebSocket),
2582 }
2583}
2584
2585#[cfg(not(target_arch = "wasm32"))]
2586fn configure_tcp(stream: TcpStream) -> ClientResult<DialedTransport> {
2587 stream.set_nodelay(true).ok();
2588 let keepalive = socket2::TcpKeepalive::new()
2589 .with_time(Duration::from_secs(45))
2590 .with_interval(Duration::from_secs(15))
2591 .with_retries(4);
2592 let _ = socket2::SockRef::from(&stream).set_tcp_keepalive(&keepalive);
2593 let (r, w) = stream.into_split();
2594 Ok(DialedTransport::Native {
2595 read: Box::new(r),
2596 write: Box::new(w),
2597 })
2598}
2599
2600#[cfg(all(test, not(target_arch = "wasm32")))]
2601mod target_dial_tests {
2602 use super::*;
2603 use tokio::io::AsyncReadExt;
2604 use tokio::net::TcpListener;
2605
2606 async fn recv_message(stream: &mut TcpStream) -> P9Message {
2607 let mut size = [0u8; P9_SIZE_FIELD_LEN];
2608 stream.read_exact(&mut size).await.unwrap();
2609 let frame_len = u32::from_le_bytes(size) as usize;
2610 let mut frame = Vec::with_capacity(frame_len);
2611 frame.extend_from_slice(&size);
2612 frame.resize(frame_len, 0);
2613 stream
2614 .read_exact(&mut frame[P9_SIZE_FIELD_LEN..])
2615 .await
2616 .unwrap();
2617 P9Message::from_bytes((&frame, 0)).unwrap().1
2618 }
2619
2620 async fn send_message(stream: &mut TcpStream, tag: u16, body: Message) {
2621 stream
2622 .write_all(&P9Message::new(tag, body).to_bytes().unwrap())
2623 .await
2624 .unwrap();
2625 }
2626
2627 #[tokio::test]
2628 async fn hostname_lookup_failure_is_isolated_from_healthy_target_dials() {
2629 let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
2630 let healthy_addr = listener.local_addr().unwrap();
2631 let accept = tokio::spawn(async move { listener.accept().await.unwrap() });
2632
2633 let targets = [
2634 Target::TcpHost("not a valid hostname:5564".to_string()),
2635 Target::Tcp(healthy_addr),
2636 ];
2637 let mut dials = FuturesUnordered::new();
2638 for target in targets {
2639 dials.push(async move { dial(&target).await });
2640 }
2641
2642 let mut successes = 0;
2643 let mut failures = 0;
2644 while let Some(result) = dials.next().await {
2645 match result {
2646 Ok(transport) => {
2647 successes += 1;
2648 drop(transport);
2649 }
2650 Err(_) => failures += 1,
2651 }
2652 }
2653 assert_eq!(successes, 1);
2654 assert_eq!(failures, 1);
2655 accept.await.unwrap();
2656 }
2657
2658 #[tokio::test]
2659 async fn reconnect_rejects_a_smaller_negotiated_msize() {
2660 let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
2661 let addr = listener.local_addr().unwrap();
2662 let server = tokio::spawn(async move {
2663 let (mut stream, _) = listener.accept().await.unwrap();
2664 let version = recv_message(&mut stream).await;
2665 assert!(matches!(version.body, Message::Tversion(_)));
2666 send_message(
2667 &mut stream,
2668 version.tag,
2669 Message::Rversion(Rversion {
2670 msize: 4096,
2671 version: P9String::new(VERSION_9P2000L_ZEROFS.to_vec()),
2672 }),
2673 )
2674 .await;
2675
2676 let lineage = recv_message(&mut stream).await;
2677 assert!(matches!(lineage.body, Message::Tgetlineage(_)));
2678 send_message(
2679 &mut stream,
2680 lineage.tag,
2681 Message::Rgetlineage(Rgetlineage {
2682 token: 1,
2683 writer_epoch: 1,
2684 }),
2685 )
2686 .await;
2687 });
2688
2689 let result = NinePClient::connect_once(
2690 &Target::Tcp(addr),
2691 8192,
2692 Some(8192),
2693 Arc::new(Notify::new()),
2694 Arc::new(TrafficCounters::default()),
2695 Arc::new(AtomicBool::new(false)),
2696 )
2697 .await;
2698 assert!(matches!(result, Err(ClientError::Unexpected("version"))));
2699 server.await.unwrap();
2700 }
2701}
2702
2703async fn negotiate_on(conn: &Conn, requested: u32) -> ClientResult<u32> {
2705 let negotiated = version_on(conn, requested).await?;
2706 query_lineage_token(conn).await?;
2707 debug!("ZeroFS 9P dialect negotiated, msize={negotiated}");
2708 Ok(negotiated)
2709}
2710
2711async fn version_on(conn: &Conn, requested: u32) -> ClientResult<u32> {
2713 match NinePClient::send_raw_at_tag(
2715 conn,
2716 Some(NOTAG),
2717 Message::Tversion(Tversion {
2718 msize: requested,
2719 version: P9String::new(VERSION_9P2000L_ZEROFS.to_vec()),
2720 }),
2721 )
2722 .await?
2723 {
2724 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
2725 Message::Rversion(rv) => {
2726 if rv.version.data != VERSION_9P2000L_ZEROFS {
2727 warn!(
2728 "server did not accept the required ZeroFS dialect: {:?}",
2729 rv.version.as_str().unwrap_or("<non-UTF-8>")
2730 );
2731 return Err(ClientError::Unexpected("version"));
2732 }
2733 let negotiated = rv.msize.min(requested);
2735 if negotiated < 4096 {
2736 warn!("server negotiated msize {negotiated} below minimum 4096");
2737 return Err(ClientError::Unexpected("version"));
2738 }
2739 Ok(negotiated)
2740 }
2741 _ => Err(ClientError::Unexpected("version")),
2742 }
2743}
2744
2745async fn query_lineage_token(conn: &Conn) -> ClientResult<()> {
2747 match NinePClient::send_raw(conn, Message::Tgetlineage(Tgetlineage)).await? {
2748 Message::Rgetlineage(r) => {
2749 conn.lineage_token.store(r.token, Ordering::Relaxed);
2750 conn.writer_epoch.store(r.writer_epoch, Ordering::Relaxed);
2751 Ok(())
2752 }
2753 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
2754 _ => Err(ClientError::Unexpected("getlineage")),
2755 }
2756}
2757
2758#[cfg(not(target_arch = "wasm32"))]
2759fn spawn_writer(
2760 write: Box<dyn AsyncWrite + Unpin + Send>,
2761 mut rx: mpsc::Receiver<Vec<u8>>,
2762 conn: Arc<Conn>,
2763 reconnect: Arc<Notify>,
2764) {
2765 runtime::spawn(async move {
2766 let mut writer = tokio::io::BufWriter::with_capacity(64 * 1024, write);
2767 loop {
2768 tokio::select! {
2769 biased;
2770 _ = conn.writer_shutdown.notified() => break,
2772 maybe = rx.recv() => {
2773 let Some(frame) = maybe else { break };
2774 conn.counters.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
2775 conn.counters.operations.fetch_add(1, Ordering::Relaxed);
2776 if writer.write_all(&frame).await.is_err() {
2777 break;
2778 }
2779 let mut failed = false;
2780 while let Ok(more) = rx.try_recv() {
2781 conn.counters.bytes_sent.fetch_add(more.len() as u64, Ordering::Relaxed);
2782 conn.counters.operations.fetch_add(1, Ordering::Relaxed);
2783 if writer.write_all(&more).await.is_err() {
2784 failed = true;
2785 break;
2786 }
2787 }
2788 if failed || writer.flush().await.is_err() {
2789 break;
2790 }
2791 }
2792 }
2793 }
2794 conn.dead.store(true, Ordering::Release);
2795 reconnect.notify_waiters();
2796 });
2797}
2798
2799#[cfg(not(target_arch = "wasm32"))]
2800fn spawn_reader(read: Box<dyn AsyncRead + Unpin + Send>, conn: Arc<Conn>, reconnect: Arc<Notify>) {
2801 runtime::spawn(async move {
2802 let mut framed = LengthDelimitedCodec::builder()
2803 .little_endian()
2804 .length_field_offset(0)
2805 .length_field_length(P9_SIZE_FIELD_LEN)
2806 .length_adjustment(0)
2807 .num_skip(0)
2808 .max_frame_length(P9_MAX_MSIZE as usize)
2809 .new_read(read);
2810
2811 loop {
2812 let next = tokio::select! {
2813 biased;
2814 _ = conn.reader_shutdown.notified() => break,
2816 next = framed.next() => next,
2817 };
2818 let frame = match next {
2819 Some(Ok(buf)) => buf.freeze(),
2820 Some(Err(e)) => {
2821 warn!("9P client read failed: {e}");
2822 break;
2823 }
2824 None => break,
2825 };
2826 conn.deliver(frame);
2827 }
2828
2829 conn.connection_lost(&reconnect);
2830 });
2831}
2832
2833#[cfg(test)]
2834mod lock_range_tests {
2835 use super::*;
2836
2837 fn held(start: u64, length: u64) -> LockRecord {
2838 LockRecord {
2839 fid: 9,
2840 lock_type: LockType::WriteLock,
2841 start,
2842 length,
2843 proc_id: 42,
2844 client_id: b"client".to_vec(),
2845 }
2846 }
2847
2848 #[test]
2849 fn lock_record_subtraction_preserves_metadata() {
2850 let survivors = subtract_lock_record(held(10, 90), 30, 20);
2851 assert_eq!(
2852 survivors
2853 .iter()
2854 .map(|record| (record.start, record.length))
2855 .collect::<Vec<_>>(),
2856 vec![(10, 20), (50, 50)]
2857 );
2858 assert!(survivors.iter().all(|record| {
2859 record.fid == 9
2860 && matches!(record.lock_type, LockType::WriteLock)
2861 && record.proc_id == 42
2862 && record.client_id == b"client"
2863 }));
2864 }
2865
2866 #[test]
2867 fn subrange_relock_preserves_tails_for_replay() {
2868 let mut locks = vec![held(0, 100)];
2869 replace_recorded_lock(
2870 &mut locks,
2871 LockRecord {
2872 lock_type: LockType::ReadLock,
2873 start: 20,
2874 length: 30,
2875 ..held(0, 0)
2876 },
2877 );
2878 locks.sort_by_key(|lock| lock.start);
2879 let shape: Vec<_> = locks
2880 .iter()
2881 .map(|lock| {
2882 (
2883 lock.start,
2884 lock.length,
2885 match lock.lock_type {
2886 LockType::ReadLock => "read",
2887 LockType::WriteLock => "write",
2888 LockType::Unlock => "unlock",
2889 },
2890 )
2891 })
2892 .collect();
2893 assert_eq!(
2894 shape,
2895 vec![(0, 20, "write"), (20, 30, "read"), (50, 50, "write")]
2896 );
2897 }
2898}
2899
2900#[cfg(test)]
2901mod durability_tracking_tests {
2902 use super::Unsynced;
2903
2904 #[test]
2905 fn fsync_clears_the_obligation_when_quiescent() {
2906 let mut u = Unsynced::default();
2907 u.note(7);
2908 let (oldest, generation) = u.snapshot();
2909 assert_eq!(oldest, Some(7));
2910 u.clear_if_unchanged(generation);
2911 assert_eq!(u.snapshot().0, None);
2912 }
2913
2914 #[test]
2915 fn a_repeated_fsync_on_one_fid_keeps_failing_until_a_redo() {
2916 let mut u = Unsynced::default();
2917 u.note(1);
2918 let (oldest, generation) = u.snapshot();
2919 assert_eq!(oldest, Some(1));
2920 u.report_if_unchanged(generation);
2921 assert_eq!(
2922 u.snapshot().0,
2923 Some(1),
2924 "a reported loss must persist so a repeated fsync still fails"
2925 );
2926 u.note(2);
2927 assert_eq!(
2928 u.snapshot().0,
2929 Some(2),
2930 "a redo after a report advances the obligation (no livelock)"
2931 );
2932 let (_, gen2) = u.snapshot();
2933 u.clear_if_unchanged(gen2);
2934 assert_eq!(u.snapshot().0, None);
2935 }
2936
2937 #[test]
2938 fn an_estale_and_redo_on_one_fid_does_not_discharge_a_sibling_fid() {
2939 use std::collections::HashMap;
2940 let mut map: HashMap<u32, Unsynced> = HashMap::new();
2941 map.entry(10).or_default().note(1);
2942 map.entry(20).or_default().note(1);
2943 let (_, g) = map.get(&10).unwrap().snapshot();
2944 map.get_mut(&10).unwrap().report_if_unchanged(g);
2945 map.get_mut(&10).unwrap().note(2);
2946 let (_, g2) = map.get(&10).unwrap().snapshot();
2947 map.get_mut(&10).unwrap().clear_if_unchanged(g2);
2948 if map.get(&10).unwrap().snapshot().0.is_none() {
2949 map.remove(&10);
2950 }
2951 assert_eq!(
2952 map.get(&20).unwrap().snapshot().0,
2953 Some(1),
2954 "fid 10's ESTALE+redo+success cycle must not touch fid 20"
2955 );
2956 }
2957
2958 #[test]
2959 fn fsync_does_not_erase_a_write_from_its_window() {
2960 let mut u = Unsynced::default();
2961 u.note(7);
2962 let (_, generation) = u.snapshot();
2963 u.note(7);
2964 u.clear_if_unchanged(generation);
2965 assert_eq!(
2966 u.snapshot().0,
2967 Some(7),
2968 "a write racing the fsync must stay tracked"
2969 );
2970 }
2971
2972 #[test]
2973 fn oldest_token_is_kept_across_a_lineage_change() {
2974 let mut u = Unsynced::default();
2975 u.note(5);
2976 u.note(9);
2977 assert_eq!(u.snapshot().0, Some(5), "the oldest (riskiest) token wins");
2978 }
2979
2980 #[test]
2981 fn nothing_tracked_reports_none() {
2982 let u = Unsynced::default();
2983 assert_eq!(u.snapshot(), (None, 0));
2984 }
2985}
2986
2987#[cfg(test)]
2988fn test_conn_with_receiver() -> (Arc<Conn>, mpsc::Receiver<Vec<u8>>) {
2989 let (writer_tx, rx) = mpsc::channel(1);
2990 let conn = Arc::new(Conn {
2991 writer_tx,
2992 pending: DashMap::new(),
2993 tag_ctr: AtomicU16::new(0),
2994 lineage_token: AtomicU64::new(0),
2995 writer_epoch: AtomicU64::new(0),
2996 dead: AtomicBool::new(false),
2997 base: runtime::Clock::now(),
2998 last_alive: AtomicU64::new(0),
2999 probe_lock: tokio::sync::Mutex::new(()),
3000 writer_shutdown: Notify::new(),
3001 reader_shutdown: Notify::new(),
3002 counters: Arc::new(TrafficCounters::default()),
3003 });
3004 (conn, rx)
3005}
3006
3007#[cfg(test)]
3008mod session_transition_tests {
3009 use super::*;
3010
3011 type TestRequests = mpsc::Receiver<Vec<u8>>;
3012 const REPLAY: u8 = P9_REBIND_REPLAY;
3013 const OPEN_REPLAY: u8 = REPLAY | P9_REBIND_OPENED;
3014
3015 fn test_conn() -> Arc<Conn> {
3016 test_conn_with_receiver().0
3017 }
3018
3019 fn test_client(conn: Arc<Conn>) -> Arc<NinePClient> {
3020 Arc::new(NinePClient {
3021 targets: Vec::new(),
3022 conn: ArcSwap::new(conn),
3023 live: AtomicBool::new(true),
3024 terminal_errno: AtomicU32::new(0),
3025 live_notify: Notify::new(),
3026 reconnect_notify: Arc::new(Notify::new()),
3027 msize: 8192,
3028 msize_mismatch_warned: Arc::new(AtomicBool::new(false)),
3029 fid_ctr: AtomicU32::new(1),
3030 fid_free: Mutex::new(Vec::new()),
3031 state: Mutex::new(SessionState::default()),
3032 stale_fids: DashSet::new(),
3033 session_transition: tokio::sync::Mutex::new(()),
3034 unsynced: DashMap::new(),
3035 counters: Arc::new(TrafficCounters::default()),
3036 })
3037 }
3038
3039 async fn next_request(requests: &mut TestRequests) -> Option<P9Message> {
3040 requests
3041 .recv()
3042 .await
3043 .map(|frame| P9Message::from_bytes((&frame, 0)).unwrap().1)
3044 }
3045
3046 async fn recv_request(requests: &mut TestRequests, description: &str) -> P9Message {
3047 next_request(requests).await.expect(description)
3048 }
3049
3050 async fn recv_op_request(requests: &mut TestRequests, description: &str) -> P9Message {
3051 let frame = requests.recv().await.expect(description);
3052 P9Message::from_bytes_ctx(&frame, true).unwrap()
3053 }
3054
3055 fn reply(conn: &Conn, tag: u16, body: Message) {
3056 conn.deliver(Bytes::from(P9Message::new(tag, body).to_bytes().unwrap()));
3057 }
3058
3059 fn qid(path: u64) -> Qid {
3060 Qid {
3061 type_: 0,
3062 version: 0,
3063 path,
3064 }
3065 }
3066
3067 fn inode_fid(inode_id: u64, root_inode: u64, n_uname: u32, opened: Option<u32>) -> FidRecord {
3068 FidRecord {
3069 inode_id,
3070 root_inode,
3071 n_uname,
3072 uname: Vec::new(),
3073 opened,
3074 }
3075 }
3076
3077 fn attach_fid(root_inode: u64, n_uname: u32) -> FidRecord {
3078 FidRecord {
3079 inode_id: root_inode,
3080 root_inode,
3081 n_uname,
3082 uname: Vec::new(),
3083 opened: None,
3084 }
3085 }
3086
3087 fn write_lock(fid: u32, start: u64, length: u64) -> LockRecord {
3088 LockRecord {
3089 fid,
3090 lock_type: LockType::WriteLock,
3091 start,
3092 length,
3093 proc_id: 1,
3094 client_id: b"owner".to_vec(),
3095 }
3096 }
3097
3098 #[test]
3099 fn forgetting_a_fid_removes_only_its_locks() {
3100 let mut state = SessionState::default();
3101 state.fids.insert(7, inode_fid(70, 0, 0, None));
3102 state.fids.insert(8, inode_fid(80, 0, 0, None));
3103 state.locks.extend([
3104 write_lock(7, 0, 10),
3105 write_lock(8, 0, 10),
3106 write_lock(7, 20, 10),
3107 ]);
3108
3109 state.forget_fid(7);
3110
3111 assert!(!state.fids.contains_key(&7));
3112 assert!(state.fids.contains_key(&8));
3113 assert_eq!(state.locks.len(), 1);
3114 assert_eq!(state.locks[0].fid, 8);
3115 }
3116
3117 async fn replay_rebind_error(
3118 client: &NinePClient,
3119 conn: &Arc<Conn>,
3120 mut requests: TestRequests,
3121 expected: (u32, u64, u64, u8),
3122 ecode: u32,
3123 ) -> ClientResult<()> {
3124 let responder_conn = Arc::clone(conn);
3125 let responder = tokio::spawn(async move {
3126 let request = recv_request(&mut requests, "rebind request").await;
3127 let Message::Trebind(rebind) = request.body else {
3128 panic!("expected Trebind");
3129 };
3130 let actual = (rebind.fid, rebind.inode_id, rebind.root_inode, rebind.flags);
3131 assert_eq!(actual, expected);
3132 reply(
3133 &responder_conn,
3134 request.tag,
3135 Message::Rlerror(Rlerror { ecode }),
3136 );
3137 });
3138 let result = client.replay(conn).await;
3139 responder.await.unwrap();
3140 result
3141 }
3142
3143 #[tokio::test]
3144 async fn reconnect_winner_rejects_an_old_stateful_response() {
3145 let old = test_conn();
3146 let client = test_client(Arc::clone(&old));
3147 let waiter_client = Arc::clone(&client);
3148 let waiter_old = Arc::clone(&old);
3149
3150 let reconnect = client.session_transition.lock().await;
3151 let waiter = tokio::spawn(async move {
3152 waiter_client
3153 .accept_stateful_response(&waiter_old)
3154 .await
3155 .is_some()
3156 });
3157 tokio::task::yield_now().await;
3158 assert!(
3159 !waiter.is_finished(),
3160 "response settlement must wait for reconnect's transition"
3161 );
3162
3163 old.dead.store(true, Ordering::Release);
3164 client.live.store(false, Ordering::Release);
3165 client.conn.store(test_conn());
3166 client.live.store(true, Ordering::Release);
3167 drop(reconnect);
3168
3169 assert!(
3170 !waiter.await.unwrap(),
3171 "a response from the replaced connection must be resent, not settled"
3172 );
3173 }
3174
3175 #[tokio::test]
3176 async fn settled_state_is_visible_to_the_next_replay_snapshot() {
3177 let conn = test_conn();
3178 let client = test_client(Arc::clone(&conn));
3179 let settlement = client
3180 .accept_stateful_response(&conn)
3181 .await
3182 .expect("current response should settle");
3183
3184 let replay_client = Arc::clone(&client);
3185 let snapshot = tokio::spawn(async move {
3186 let _reconnect = replay_client.session_transition.lock().await;
3187 replay_client.state.lock().unwrap().fids.contains_key(&7)
3188 });
3189 tokio::task::yield_now().await;
3190 assert!(
3191 !snapshot.is_finished(),
3192 "replay must wait until response bookkeeping completes"
3193 );
3194
3195 client
3196 .state
3197 .lock()
3198 .unwrap()
3199 .fids
3200 .insert(7, inode_fid(70, 0, 0, Some(0)));
3201 drop(settlement);
3202
3203 assert!(
3204 snapshot.await.unwrap(),
3205 "the replay snapshot must include the settled fid transition"
3206 );
3207 }
3208
3209 #[tokio::test]
3210 async fn permanent_replay_loss_wakes_waiters_with_estale() {
3211 let client = test_client(test_conn());
3212 client.live.store(false, Ordering::Release);
3213 let waiter_client = Arc::clone(&client);
3214 let waiter = tokio::spawn(async move { waiter_client.wait_until_live().await });
3215 tokio::task::yield_now().await;
3216 assert!(!waiter.is_finished());
3217
3218 client.fail_session(linux::ESTALE);
3219 assert!(matches!(
3220 waiter.await.unwrap(),
3221 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
3222 ));
3223 assert!(matches!(
3224 client.wait_until_live().await,
3225 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
3226 ));
3227 }
3228
3229 #[tokio::test]
3230 async fn discarded_replay_candidate_resets_session_before_shutdown() {
3231 let (conn, mut requests) = test_conn_with_receiver();
3232 let client = test_client(Arc::clone(&conn));
3233 let responder_conn = Arc::clone(&conn);
3234 let responder = tokio::spawn(async move {
3235 let request = recv_request(&mut requests, "candidate reset").await;
3236 assert_eq!(request.tag, NOTAG);
3237 assert!(matches!(request.body, Message::Tversion(_)));
3238 reply(
3239 &responder_conn,
3240 request.tag,
3241 Message::Rversion(Rversion {
3242 msize: 8192,
3243 version: P9String::new(VERSION_9P2000L_ZEROFS.to_vec()),
3244 }),
3245 );
3246 requests
3247 });
3248
3249 client.discard_replay_candidate(&conn).await;
3250 let mut requests = responder.await.unwrap();
3251 assert!(
3252 requests.try_recv().is_err(),
3253 "candidate reset must not issue a throwaway lineage query"
3254 );
3255 assert!(conn.dead.load(Ordering::Acquire));
3256 }
3257
3258 #[tokio::test]
3259 async fn replay_establishes_each_attach_root_before_its_explicit_descendants() {
3260 let (conn, mut requests) = test_conn_with_receiver();
3261 let client = test_client(Arc::clone(&conn));
3262 {
3263 let mut state = client.state.lock().unwrap();
3264 state.fids.insert(1, attach_fid(10, 1000));
3265 state.fids.insert(2, attach_fid(20, 2000));
3266 state.fids.insert(3, inode_fid(11, 10, 1000, None));
3267 state.fids.insert(4, inode_fid(21, 20, 2000, None));
3268 }
3269
3270 let responder_conn = Arc::clone(&conn);
3271 let responder = tokio::spawn(async move {
3272 let mut roots = std::collections::BTreeSet::new();
3273 let mut descendants = std::collections::BTreeSet::new();
3274 for index in 0..4 {
3275 let request = recv_request(&mut requests, "replay request").await;
3276 let Message::Trebind(rebind) = request.body else {
3277 panic!("expected Trebind");
3278 };
3279 assert_eq!(
3280 rebind.flags, P9_REBIND_REPLAY,
3281 "every automatic fid replay is marked independently of open state"
3282 );
3283 if index < 2 {
3284 assert_eq!(rebind.inode_id, rebind.root_inode);
3285 roots.insert(rebind.root_inode);
3286 } else {
3287 assert_ne!(rebind.inode_id, rebind.root_inode);
3288 descendants.insert((rebind.inode_id, rebind.root_inode));
3289 }
3290 reply(
3291 &responder_conn,
3292 request.tag,
3293 Message::Rrebind(Rrebind {
3294 qid: qid(rebind.inode_id),
3295 }),
3296 );
3297 }
3298 assert_eq!(roots, [10, 20].into_iter().collect());
3299 assert_eq!(descendants, [(11, 10), (21, 20)].into_iter().collect());
3300 });
3301
3302 client.replay(&conn).await.unwrap();
3303 responder.await.unwrap();
3304 }
3305
3306 #[tokio::test]
3307 async fn name_derived_credentials_survive_descendant_replay() {
3308 let (old_conn, mut old_requests) = test_conn_with_receiver();
3309 let client = test_client(Arc::clone(&old_conn));
3310 let responder_conn = Arc::clone(&old_conn);
3311 let responder = tokio::spawn(async move {
3312 let request = recv_request(&mut old_requests, "attach request").await;
3313 let Message::Tattach(attach) = request.body else {
3314 panic!("expected Tattach");
3315 };
3316 assert_eq!(attach.uname.as_str().unwrap(), "root");
3317 assert_eq!(attach.n_uname, u32::MAX);
3318 reply(
3319 &responder_conn,
3320 request.tag,
3321 Message::Rattach(Rattach {
3322 qid: Qid {
3323 type_: 0x80,
3324 ..qid(10)
3325 },
3326 }),
3327 );
3328
3329 let request = recv_request(&mut old_requests, "walk request").await;
3330 assert!(matches!(request.body, Message::Twalk(_)));
3331 reply(
3332 &responder_conn,
3333 request.tag,
3334 Message::Rwalk(Rwalk {
3335 nwqid: 1,
3336 wqids: vec![qid(11)],
3337 }),
3338 );
3339 });
3340
3341 client.attach(1, NOFID, "root", "", u32::MAX).await.unwrap();
3342 client.walk(1, 2, &[b"child"]).await.unwrap();
3343 responder.await.unwrap();
3344
3345 client.state.lock().unwrap().fids.remove(&1);
3346
3347 let (replay_conn, mut replay_requests) = test_conn_with_receiver();
3348 let responder_conn = Arc::clone(&replay_conn);
3349 let responder = tokio::spawn(async move {
3350 let request = recv_request(&mut replay_requests, "descendant rebind").await;
3351 let Message::Trebind(rebind) = request.body else {
3352 panic!("expected Trebind");
3353 };
3354 assert_eq!(rebind.fid, 2);
3355 assert_eq!((rebind.inode_id, rebind.root_inode), (11, 10));
3356 assert_eq!(rebind.uname.as_str().unwrap(), "root");
3357 assert_eq!(rebind.n_uname, u32::MAX);
3358 reply(
3359 &responder_conn,
3360 request.tag,
3361 Message::Rrebind(Rrebind { qid: qid(11) }),
3362 );
3363 });
3364
3365 client.replay(&replay_conn).await.unwrap();
3366 responder.await.unwrap();
3367 }
3368
3369 #[tokio::test]
3370 async fn rooted_descendant_replays_after_its_attach_fid_was_clunked() {
3371 let (conn, mut requests) = test_conn_with_receiver();
3372 let client = test_client(Arc::clone(&conn));
3373 client
3374 .state
3375 .lock()
3376 .unwrap()
3377 .fids
3378 .insert(7, inode_fid(70, 10, 1000, None));
3379
3380 let responder_conn = Arc::clone(&conn);
3381 let responder = tokio::spawn(async move {
3382 let request = recv_request(&mut requests, "descendant rebind").await;
3383 let Message::Trebind(rebind) = request.body else {
3384 panic!("expected Trebind");
3385 };
3386 assert_eq!((rebind.inode_id, rebind.root_inode), (70, 10));
3387 reply(
3388 &responder_conn,
3389 request.tag,
3390 Message::Rrebind(Rrebind { qid: qid(70) }),
3391 );
3392 });
3393
3394 client.replay(&conn).await.unwrap();
3395 responder.await.unwrap();
3396 assert!(client.state.lock().unwrap().fids.contains_key(&7));
3397 }
3398
3399 #[tokio::test]
3400 async fn missing_opened_fid_is_quarantined_without_terminating_the_session() {
3401 let (conn, mut requests) = test_conn_with_receiver();
3402 let client = test_client(Arc::clone(&conn));
3403 client
3404 .state
3405 .lock()
3406 .unwrap()
3407 .fids
3408 .insert(7, inode_fid(70, 0, 0, Some(0)));
3409
3410 let responder_conn = Arc::clone(&conn);
3411 let responder = tokio::spawn(async move {
3412 let request = recv_request(&mut requests, "opened fid rebind").await;
3413 assert!(matches!(
3414 request.body,
3415 Message::Trebind(Trebind {
3416 fid: 7,
3417 inode_id: 70,
3418 flags: OPEN_REPLAY,
3419 ..
3420 })
3421 ));
3422 reply(
3423 &responder_conn,
3424 request.tag,
3425 Message::Rlerror(Rlerror {
3426 ecode: linux::ESTALE,
3427 }),
3428 );
3429
3430 let request = recv_request(&mut requests, "unrelated read").await;
3431 assert!(matches!(
3432 request.body,
3433 Message::Tread(Tread {
3434 fid: 8,
3435 count: 1,
3436 ..
3437 })
3438 ));
3439 reply(
3440 &responder_conn,
3441 request.tag,
3442 Message::Rread(Rread {
3443 count: 1,
3444 data: DekuBytes::from(vec![b'x']),
3445 }),
3446 );
3447 });
3448
3449 client.replay(&conn).await.unwrap();
3450 assert!(!client.state.lock().unwrap().fids.contains_key(&7));
3451 assert!(client.stale_fids.contains(&7));
3452 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
3453 assert!(matches!(
3454 client.read(7, 0, 1).await,
3455 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
3456 ));
3457 assert_eq!(client.read(8, 0, 1).await.unwrap(), vec![b'x']);
3458 tokio::time::timeout(Duration::from_secs(1), client.clunk(7))
3459 .await
3460 .expect("stale clunk must not reach the server")
3461 .unwrap();
3462 assert!(!client.stale_fids.contains(&7));
3463 responder.await.unwrap();
3464 }
3465
3466 #[test]
3467 fn freeing_a_fid_clears_its_stale_tombstone() {
3468 let client = test_client(test_conn());
3469 client.stale_fids.insert(7);
3470 client.free_fid(7);
3471 assert!(!client.stale_fids.contains(&7));
3472 }
3473
3474 #[test]
3475 fn stale_tombstones_cover_source_destination_and_clunk_fids() {
3476 let client = test_client(test_conn());
3477 client.stale_fids.insert(7);
3478
3479 for request in [
3480 Message::Tlink(Tlink {
3481 dfid: 8,
3482 fid: 7,
3483 name: P9String::new(b"link".to_vec()),
3484 }),
3485 Message::Twalk(Twalk {
3486 fid: 8,
3487 newfid: 7,
3488 nwname: 0,
3489 wnames: Vec::new(),
3490 }),
3491 Message::Tclunk(Tclunk { fid: 7 }),
3492 ] {
3493 assert!(matches!(
3494 client.validate_fids(request.request_fids()),
3495 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
3496 ));
3497 }
3498 assert!(
3499 client
3500 .validate_fids(
3501 Message::Tread(Tread {
3502 fid: 8,
3503 offset: 0,
3504 count: 1,
3505 })
3506 .request_fids()
3507 )
3508 .is_ok()
3509 );
3510 }
3511
3512 #[tokio::test]
3513 async fn clunk_does_not_mask_a_terminal_session_estale() {
3514 let client = test_client(test_conn());
3515 client.stale_fids.insert(7);
3516 client.fail_session(linux::ESTALE);
3517
3518 assert!(matches!(
3519 client.clunk(7).await,
3520 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
3521 ));
3522 assert!(client.stale_fids.contains(&7));
3523 }
3524
3525 #[tokio::test]
3526 async fn failed_reopen_clunks_the_provisional_rebind() {
3527 let (conn, mut requests) = test_conn_with_receiver();
3528 let client = test_client(Arc::clone(&conn));
3529 client
3530 .state
3531 .lock()
3532 .unwrap()
3533 .fids
3534 .insert(7, inode_fid(70, 0, 0, Some(0)));
3535
3536 let responder_conn = Arc::clone(&conn);
3537 let responder = tokio::spawn(async move {
3538 let request = recv_request(&mut requests, "opened fid rebind").await;
3539 assert!(matches!(
3540 request.body,
3541 Message::Trebind(Trebind { fid: 7, .. })
3542 ));
3543 reply(
3544 &responder_conn,
3545 request.tag,
3546 Message::Rrebind(Rrebind { qid: qid(70) }),
3547 );
3548
3549 let request = recv_request(&mut requests, "fid reopen").await;
3550 assert!(matches!(
3551 request.body,
3552 Message::Tlopen(Tlopen { fid: 7, .. })
3553 ));
3554 reply(
3555 &responder_conn,
3556 request.tag,
3557 Message::Rlerror(Rlerror {
3558 ecode: linux::ESTALE,
3559 }),
3560 );
3561
3562 let request = recv_request(&mut requests, "provisional fid clunk").await;
3563 assert!(matches!(request.body, Message::Tclunk(Tclunk { fid: 7 })));
3564 reply(&responder_conn, request.tag, Message::Rclunk(Rclunk));
3565 });
3566
3567 client.replay(&conn).await.unwrap();
3568 responder.await.unwrap();
3569 let state = client.state.lock().unwrap();
3570 assert!(!state.fids.contains_key(&7));
3571 drop(state);
3572 assert!(client.stale_fids.contains(&7));
3573 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
3574 }
3575
3576 #[tokio::test]
3577 async fn missing_locked_fid_is_a_terminal_replay_error() {
3578 let (conn, requests) = test_conn_with_receiver();
3579 let client = test_client(Arc::clone(&conn));
3580 {
3581 let mut state = client.state.lock().unwrap();
3582 state.fids.insert(7, inode_fid(70, 0, 0, None));
3583 state.locks.push(write_lock(7, 0, 0));
3584 }
3585
3586 assert!(matches!(
3587 replay_rebind_error(&client, &conn, requests, (7, 70, 0, REPLAY), linux::ENOENT)
3588 .await,
3589 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
3590 ));
3591 {
3592 let state = client.state.lock().unwrap();
3593 assert!(state.fids.contains_key(&7));
3594 assert_eq!(state.locks.len(), 1);
3595 }
3596 assert_eq!(client.terminal_errno.load(Ordering::Acquire), linux::ESTALE);
3597 }
3598
3599 #[tokio::test]
3600 async fn missing_unopened_rebind_is_dropped() {
3601 let (conn, requests) = test_conn_with_receiver();
3602 let client = test_client(Arc::clone(&conn));
3603 client
3604 .state
3605 .lock()
3606 .unwrap()
3607 .fids
3608 .insert(7, inode_fid(70, 0, 0, None));
3609
3610 replay_rebind_error(&client, &conn, requests, (7, 70, 0, REPLAY), linux::ENOENT)
3611 .await
3612 .unwrap();
3613 assert!(!client.state.lock().unwrap().fids.contains_key(&7));
3614 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
3615 }
3616
3617 #[tokio::test]
3618 async fn operational_rebind_error_is_transient_and_preserves_fid() {
3619 let (conn, requests) = test_conn_with_receiver();
3620 let client = test_client(Arc::clone(&conn));
3621 client
3622 .state
3623 .lock()
3624 .unwrap()
3625 .fids
3626 .insert(7, inode_fid(70, 0, 0, None));
3627
3628 assert!(matches!(
3629 replay_rebind_error(
3630 &client,
3631 &conn,
3632 requests,
3633 (7, 70, 0, REPLAY),
3634 linux::EIO as u32,
3635 )
3636 .await,
3637 Err(ClientError::Errno(errno)) if errno == linux::EIO as u32
3638 ));
3639 assert!(client.state.lock().unwrap().fids.contains_key(&7));
3640 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
3641 }
3642
3643 #[tokio::test]
3644 async fn missing_unopened_attach_root_is_dropped_while_open_child_replays() {
3645 let (conn, mut requests) = test_conn_with_receiver();
3646 let client = test_client(Arc::clone(&conn));
3647 {
3648 let mut state = client.state.lock().unwrap();
3649 state.fids.insert(1, attach_fid(1, 0));
3650 state.fids.insert(2, inode_fid(70, 1, 0, Some(0)));
3651 }
3652
3653 let responder_conn = Arc::clone(&conn);
3654 let responder = tokio::spawn(async move {
3655 let request = recv_request(&mut requests, "root rebind request").await;
3656 assert!(matches!(
3657 request.body,
3658 Message::Trebind(Trebind {
3659 inode_id: 1,
3660 root_inode: 1,
3661 flags: P9_REBIND_REPLAY,
3662 ..
3663 })
3664 ));
3665 reply(
3666 &responder_conn,
3667 request.tag,
3668 Message::Rlerror(Rlerror {
3669 ecode: linux::ENOENT,
3670 }),
3671 );
3672
3673 let request = recv_request(&mut requests, "opened child rebind").await;
3674 assert!(matches!(
3675 request.body,
3676 Message::Trebind(Trebind {
3677 inode_id: 70,
3678 root_inode: 1,
3679 flags: P9_REBIND_KNOWN_FLAGS,
3680 ..
3681 })
3682 ));
3683 reply(
3684 &responder_conn,
3685 request.tag,
3686 Message::Rrebind(Rrebind { qid: qid(70) }),
3687 );
3688
3689 let request = recv_request(&mut requests, "opened child reopen").await;
3690 assert!(matches!(
3691 request.body,
3692 Message::Tlopen(Tlopen { fid: 2, flags: 0 })
3693 ));
3694 reply(
3695 &responder_conn,
3696 request.tag,
3697 Message::Rlopen(Rlopen {
3698 qid: qid(70),
3699 iounit: 0,
3700 }),
3701 );
3702 });
3703
3704 client.replay(&conn).await.unwrap();
3705 responder.await.unwrap();
3706 let state = client.state.lock().unwrap();
3707 assert!(!state.fids.contains_key(&1));
3708 assert!(state.fids.contains_key(&2));
3709 drop(state);
3710 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
3711 }
3712
3713 #[tokio::test]
3714 async fn operational_attach_error_is_transient() {
3715 let (conn, requests) = test_conn_with_receiver();
3716 let client = test_client(Arc::clone(&conn));
3717 client
3718 .state
3719 .lock()
3720 .unwrap()
3721 .fids
3722 .insert(1, attach_fid(2, 0));
3723
3724 assert!(matches!(
3725 replay_rebind_error(&client, &conn, requests, (1, 2, 2, REPLAY), linux::EIO as u32)
3726 .await,
3727 Err(ClientError::Errno(errno)) if errno == linux::EIO as u32
3728 ));
3729 assert!(client.state.lock().unwrap().fids.contains_key(&1));
3730 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
3731 }
3732
3733 #[tokio::test]
3734 async fn persistent_lock_conflict_remains_retryable() {
3735 let (conn, mut requests) = test_conn_with_receiver();
3736 let client = test_client(Arc::clone(&conn));
3737 client.state.lock().unwrap().locks.push(write_lock(7, 0, 0));
3738
3739 let responder_conn = Arc::clone(&conn);
3740 let responder = tokio::spawn(async move {
3741 while let Some(request) = next_request(&mut requests).await {
3742 assert!(matches!(request.body, Message::Tlock(_)));
3743 reply(
3744 &responder_conn,
3745 request.tag,
3746 Message::Rlock(Rlock {
3747 status: LockStatus::Blocked,
3748 }),
3749 );
3750 }
3751 });
3752
3753 assert!(
3754 runtime::timeout(Duration::from_secs(1), client.replay(&conn))
3755 .await
3756 .is_err(),
3757 "persistent lock conflicts must remain a retryable replay wait"
3758 );
3759 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
3760 responder.abort();
3761 let _ = responder.await;
3762 }
3763
3764 #[tokio::test]
3765 async fn refused_lock_replay_terminates_the_session() {
3766 let (conn, mut requests) = test_conn_with_receiver();
3767 let client = test_client(Arc::clone(&conn));
3768 client.state.lock().unwrap().locks.push(write_lock(7, 0, 0));
3769
3770 let responder_conn = Arc::clone(&conn);
3771 let responder = tokio::spawn(async move {
3772 let request = recv_request(&mut requests, "lock replay").await;
3773 assert!(matches!(request.body, Message::Tlock(_)));
3774 reply(
3775 &responder_conn,
3776 request.tag,
3777 Message::Rlock(Rlock {
3778 status: LockStatus::LockError,
3779 }),
3780 );
3781 });
3782
3783 assert!(matches!(
3784 client.replay(&conn).await,
3785 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
3786 ));
3787 responder.await.unwrap();
3788 assert_eq!(client.terminal_errno.load(Ordering::Acquire), linux::ESTALE);
3789 }
3790
3791 #[tokio::test]
3792 async fn lock_replay_retries_through_old_session_teardown() {
3793 let (conn, mut requests) = test_conn_with_receiver();
3794 let client = test_client(Arc::clone(&conn));
3795 client.state.lock().unwrap().locks.push(write_lock(7, 0, 0));
3796
3797 let responder_conn = Arc::clone(&conn);
3798 let responder = tokio::spawn(async move {
3799 let request = recv_request(&mut requests, "blocked lock acquisition").await;
3800 assert!(matches!(request.body, Message::Tlock(_)));
3801 reply(
3802 &responder_conn,
3803 request.tag,
3804 Message::Rlock(Rlock {
3805 status: LockStatus::Blocked,
3806 }),
3807 );
3808
3809 let request = recv_request(&mut requests, "retried lock acquisition").await;
3810 assert!(matches!(
3811 request.body,
3812 Message::Tlock(Tlock {
3813 lock_type: LockType::WriteLock,
3814 ..
3815 })
3816 ));
3817 reply(
3818 &responder_conn,
3819 request.tag,
3820 Message::Rlock(Rlock {
3821 status: LockStatus::Success,
3822 }),
3823 );
3824 });
3825
3826 client.replay(&conn).await.unwrap();
3827 responder.await.unwrap();
3828 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
3829 }
3830
3831 #[tokio::test]
3832 async fn raced_lock_conflict_rolls_back_the_acquired_prefix() {
3833 let (conn, mut requests) = test_conn_with_receiver();
3834 let client = test_client(Arc::clone(&conn));
3835 client
3836 .state
3837 .lock()
3838 .unwrap()
3839 .locks
3840 .extend([write_lock(7, 0, 10), write_lock(8, 20, 10)]);
3841
3842 let responder_conn = Arc::clone(&conn);
3843 let responder = tokio::spawn(async move {
3844 let request = recv_request(&mut requests, "first lock acquisition").await;
3845 assert!(matches!(
3846 request.body,
3847 Message::Tlock(Tlock {
3848 fid: 7,
3849 lock_type: LockType::WriteLock,
3850 ..
3851 })
3852 ));
3853 reply(
3854 &responder_conn,
3855 request.tag,
3856 Message::Rlock(Rlock {
3857 status: LockStatus::Success,
3858 }),
3859 );
3860
3861 let request = recv_request(&mut requests, "raced lock acquisition").await;
3862 assert!(matches!(
3863 request.body,
3864 Message::Tlock(Tlock {
3865 fid: 8,
3866 lock_type: LockType::WriteLock,
3867 ..
3868 })
3869 ));
3870 reply(
3871 &responder_conn,
3872 request.tag,
3873 Message::Rlerror(Rlerror {
3874 ecode: linux::EAGAIN,
3875 }),
3876 );
3877
3878 let request = recv_request(&mut requests, "prefix rollback").await;
3879 assert!(matches!(
3880 request.body,
3881 Message::Tlock(Tlock {
3882 fid: 7,
3883 lock_type: LockType::Unlock,
3884 ..
3885 })
3886 ));
3887 reply(
3888 &responder_conn,
3889 request.tag,
3890 Message::Rlock(Rlock {
3891 status: LockStatus::Success,
3892 }),
3893 );
3894
3895 for expected_fid in [7, 8] {
3896 let request = recv_request(&mut requests, "retry lock acquisition").await;
3897 let Message::Tlock(lock) = request.body else {
3898 panic!("expected Tlock");
3899 };
3900 assert_eq!(lock.fid, expected_fid);
3901 assert!(matches!(lock.lock_type, LockType::WriteLock));
3902 reply(
3903 &responder_conn,
3904 request.tag,
3905 Message::Rlock(Rlock {
3906 status: LockStatus::Success,
3907 }),
3908 );
3909 }
3910 });
3911
3912 client.replay(&conn).await.unwrap();
3913 responder.await.unwrap();
3914 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
3915 }
3916
3917 #[test]
3918 fn clean_rejection_of_first_frame_restarts_the_retry_horizon() {
3919 let mut attempt = OpAttemptState::default();
3920 let (first_flags, first_origin) = attempt
3921 .dispatch_frame(true, 7, |_, origin| Ok(origin))
3922 .unwrap();
3923 assert_eq!((first_flags, first_origin), (0, 7));
3924 assert!(attempt.started.is_some());
3925 attempt.proven_predispatch(first_flags);
3926 assert!(attempt.started.is_none());
3927 let (next_flags, next_origin) = attempt
3928 .dispatch_frame(true, 8, |_, origin| Ok(origin))
3929 .unwrap();
3930 assert_eq!(
3931 (next_flags, next_origin),
3932 (0, 8),
3933 "a definitive rejection of the sole FIRST may be routed as FIRST again"
3934 );
3935 }
3936
3937 #[test]
3938 fn clean_rejection_of_retry_does_not_erase_older_ambiguity() {
3939 let mut attempt = OpAttemptState::default();
3940 let (first_flags, first_origin) = attempt
3941 .dispatch_frame(true, 7, |_, origin| Ok(origin))
3942 .unwrap();
3943 assert_eq!((first_flags, first_origin), (0, 7));
3944
3945 let (retry_flags, retry_origin) = attempt
3946 .dispatch_frame(true, 8, |_, origin| Ok(origin))
3947 .unwrap();
3948 assert_eq!((retry_flags, retry_origin), (P9_OP_FLAG_RETRY, 7));
3949 assert!(attempt.started.is_some());
3950 attempt.proven_predispatch(retry_flags);
3951 assert!(attempt.started.is_some());
3952 let (next_flags, next_origin) = attempt
3953 .dispatch_frame(true, 9, |_, origin| Ok(origin))
3954 .unwrap();
3955 assert_eq!(
3956 (next_flags, next_origin),
3957 (P9_OP_FLAG_RETRY, 7),
3958 "a rejected RETRY cannot make the older lost FIRST unambiguous"
3959 );
3960 }
3961
3962 async fn assert_notleader_reroute(ecode: u32, expected_flags: u8, expected_epoch: u64) {
3963 let (old_conn, mut old_requests) = test_conn_with_receiver();
3964 old_conn.writer_epoch.store(7, Ordering::Relaxed);
3965 let client = test_client(Arc::clone(&old_conn));
3966
3967 let request_client = Arc::clone(&client);
3968 let request = tokio::spawn(async move { request_client.write(7, 0, b"x").await });
3969 let first_frame = tokio::time::timeout(Duration::from_secs(1), old_requests.recv())
3970 .await
3971 .expect("FIRST request was not queued")
3972 .expect("old request channel closed");
3973 let first = P9Message::from_bytes_ctx(&first_frame, true).unwrap();
3974 let op_id = first.op_id;
3975 assert_ne!(op_id, [0u8; 16]);
3976 assert_eq!(first.op_flags, 0);
3977 assert_eq!(first.op_origin_epoch, 7);
3978 assert!(matches!(first.body, Message::Twrite(_)));
3979
3980 client.live.store(false, Ordering::Release);
3981 reply(&old_conn, first.tag, Message::Rlerror(Rlerror { ecode }));
3982 tokio::time::timeout(Duration::from_secs(1), async {
3983 while !old_conn.dead.load(Ordering::Acquire) {
3984 tokio::task::yield_now().await;
3985 }
3986 })
3987 .await
3988 .expect("not-leader response did not force a re-probe");
3989
3990 let (new_conn, mut new_requests) = test_conn_with_receiver();
3991 new_conn.writer_epoch.store(8, Ordering::Relaxed);
3992 client.conn.store(Arc::clone(&new_conn));
3993 client.live.store(true, Ordering::Release);
3994 client.live_notify.notify_waiters();
3995
3996 let rerouted_frame = tokio::time::timeout(Duration::from_secs(1), new_requests.recv())
3997 .await
3998 .expect("request was not rerouted")
3999 .expect("replacement request channel closed");
4000 let rerouted = P9Message::from_bytes_ctx(&rerouted_frame, true).unwrap();
4001 assert_eq!(rerouted.op_id, op_id);
4002 assert_eq!(
4003 rerouted.op_flags, expected_flags,
4004 "CLEAN must restore FIRST; an ambiguous rejection must retain RETRY"
4005 );
4006 assert_eq!(
4007 rerouted.op_origin_epoch, expected_epoch,
4008 "FIRST adopts the successor epoch; RETRY retains its origin epoch"
4009 );
4010 assert!(matches!(rerouted.body, Message::Twrite(_)));
4011
4012 reply(
4013 &new_conn,
4014 rerouted.tag,
4015 Message::Rwrite(Rwrite { count: 1 }),
4016 );
4017 assert_eq!(request.await.unwrap().unwrap(), 1);
4018 }
4019
4020 #[tokio::test]
4021 async fn clean_notleader_after_first_reroutes_same_op_id_as_first() {
4022 assert_notleader_reroute(P9_ENOTLEADER_CLEAN, 0, 8).await;
4023 }
4024
4025 #[tokio::test]
4026 async fn generic_notleader_after_first_reroutes_same_op_id_as_retry() {
4027 assert_notleader_reroute(P9_ENOTLEADER, P9_OP_FLAG_RETRY, 7).await;
4028 }
4029
4030 #[tokio::test]
4031 async fn write_once_chunks_remaining_data_at_pinned_msize() {
4032 let (conn, mut requests) = test_conn_with_receiver();
4033 let client = test_client(Arc::clone(&conn));
4034 let expected = client.max_write_payload();
4035 let remaining = vec![b'x'; expected as usize + 1];
4036
4037 let request_client = Arc::clone(&client);
4038 let request =
4039 tokio::spawn(async move { request_client.write_once(7, 0, &remaining).await });
4040 let sent = recv_op_request(&mut requests, "write request").await;
4041 let Message::Twrite(write) = sent.body else {
4042 panic!("expected Twrite");
4043 };
4044 assert_eq!(write.count, expected);
4045 assert_eq!(write.data.len(), expected as usize);
4046 reply(&conn, sent.tag, Message::Rwrite(Rwrite { count: expected }));
4047
4048 assert_eq!(request.await.unwrap().unwrap(), (expected, expected));
4049 }
4050
4051 #[tokio::test]
4052 async fn read_once_rejects_a_payload_larger_than_requested() {
4053 let (conn, mut requests) = test_conn_with_receiver();
4054 let client = test_client(Arc::clone(&conn));
4055
4056 let request_client = Arc::clone(&client);
4057 let request = tokio::spawn(async move { request_client.read_once(7, 0, 1).await });
4058 let sent = recv_request(&mut requests, "read request").await;
4059 assert!(matches!(sent.body, Message::Tread(Tread { count: 1, .. })));
4060 reply(
4061 &conn,
4062 sent.tag,
4063 Message::Rread(Rread {
4064 count: 2,
4065 data: DekuBytes::from(vec![b'x', b'y']),
4066 }),
4067 );
4068
4069 assert!(matches!(
4070 request.await.unwrap(),
4071 Err(ClientError::Unexpected("read count"))
4072 ));
4073 }
4074
4075 #[tokio::test]
4076 async fn write_once_rejects_a_count_larger_than_attempted() {
4077 let (conn, mut requests) = test_conn_with_receiver();
4078 let client = test_client(Arc::clone(&conn));
4079
4080 let request_client = Arc::clone(&client);
4081 let request = tokio::spawn(async move { request_client.write_once(7, 0, b"x").await });
4082 let sent = recv_op_request(&mut requests, "write request").await;
4083 assert!(matches!(
4084 sent.body,
4085 Message::Twrite(Twrite { count: 1, .. })
4086 ));
4087 reply(&conn, sent.tag, Message::Rwrite(Rwrite { count: 2 }));
4088
4089 assert!(matches!(
4090 request.await.unwrap(),
4091 Err(ClientError::Unexpected("write count"))
4092 ));
4093 }
4094
4095 #[tokio::test]
4096 async fn ambiguous_reply_loss_marks_the_next_frame_as_retry() {
4097 let (conn, mut requests) = test_conn_with_receiver();
4098 let client = test_client(Arc::clone(&conn));
4099
4100 let responder_conn = Arc::clone(&conn);
4101 let responder = tokio::spawn(async move {
4102 let first = recv_op_request(&mut requests, "initial write request").await;
4103 let op_id = first.op_id;
4104 assert_ne!(op_id, [0u8; 16]);
4105 assert_eq!(first.op_flags, 0);
4106
4107 let (_, pending) = responder_conn
4108 .pending
4109 .remove(&first.tag)
4110 .expect("initial response slot");
4111 drop(pending);
4112
4113 let retry = recv_op_request(&mut requests, "retried write request").await;
4114 assert_eq!(retry.op_id, op_id);
4115 assert_eq!(retry.op_flags, P9_OP_FLAG_RETRY);
4116 reply(
4117 &responder_conn,
4118 retry.tag,
4119 Message::Rlerror(Rlerror {
4120 ecode: P9_EOPIDSTALE,
4121 }),
4122 );
4123 });
4124
4125 assert!(matches!(
4126 client.write(7, 0, b"x").await,
4127 Err(ClientError::Errno(P9_EOPIDSTALE))
4128 ));
4129 responder.await.unwrap();
4130 assert!(conn.pending.is_empty());
4131 }
4132
4133 #[tokio::test]
4134 async fn cancelled_stateful_create_retires_its_session() {
4135 let (old_conn, mut old_requests) = test_conn_with_receiver();
4136 old_conn.writer_epoch.store(7, Ordering::Relaxed);
4137 let client = test_client(Arc::clone(&old_conn));
4138 client
4139 .state
4140 .lock()
4141 .unwrap()
4142 .fids
4143 .insert(7, inode_fid(1, 1, 0, None));
4144 let first_client = Arc::clone(&client);
4145 let first_task = tokio::spawn(async move {
4146 first_client
4147 .lcreate(7, b"child", linux::O_CREAT, 0o644, 0)
4148 .await
4149 });
4150 let first = recv_op_request(&mut old_requests, "FIRST create request").await;
4151 assert_eq!(first.op_flags, 0);
4152 assert_eq!(first.op_origin_epoch, 7);
4153 assert!(matches!(first.body, Message::Tlcreate(_)));
4154
4155 first_task.abort();
4156 let _ = first_task.await;
4157 assert!(
4158 old_conn.dead.load(Ordering::Acquire),
4159 "an unobserved stateful transition must retire its connection"
4160 );
4161 assert_eq!(client.state.lock().unwrap().fids[&7].inode_id, 1);
4162 old_conn.connection_lost(&client.reconnect_notify);
4163 }
4164
4165 #[tokio::test]
4166 async fn cancelled_stateful_request_does_not_retire_an_unseen_successor() {
4167 let (old_conn, mut old_requests) = test_conn_with_receiver();
4168 let client = test_client(Arc::clone(&old_conn));
4169 client
4170 .state
4171 .lock()
4172 .unwrap()
4173 .fids
4174 .insert(7, inode_fid(1, 1, 0, None));
4175
4176 let request_client = Arc::clone(&client);
4177 let request = tokio::spawn(async move {
4178 request_client
4179 .lcreate(7, b"child", linux::O_CREAT, 0o644, 0)
4180 .await
4181 });
4182 let sent = recv_op_request(&mut old_requests, "create request").await;
4183 assert!(matches!(sent.body, Message::Tlcreate(_)));
4184
4185 client.live.store(false, Ordering::Release);
4186 old_conn.connection_lost(&client.reconnect_notify);
4187 let (successor, _successor_requests) = test_conn_with_receiver();
4188 client.conn.store(Arc::clone(&successor));
4189
4190 request.abort();
4191 let _ = request.await;
4192 assert!(old_conn.dead.load(Ordering::Acquire));
4193 assert!(
4194 !successor.dead.load(Ordering::Acquire),
4195 "cancellation must not retire a connection that did not receive the request"
4196 );
4197 }
4198
4199 #[tokio::test]
4200 async fn cancelled_zero_id_lopen_retires_its_session() {
4201 let (conn, mut requests) = test_conn_with_receiver();
4202 let client = test_client(Arc::clone(&conn));
4203 client
4204 .state
4205 .lock()
4206 .unwrap()
4207 .fids
4208 .insert(7, inode_fid(42, 1, 0, None));
4209
4210 let request_client = Arc::clone(&client);
4211 let request = tokio::spawn(async move { request_client.lopen(7, 0).await });
4212 let sent = recv_request(&mut requests, "Tlopen request").await;
4213 assert!(matches!(sent.body, Message::Tlopen(Tlopen { fid: 7, .. })));
4214
4215 request.abort();
4216 let _ = request.await;
4217 assert!(
4218 conn.dead.load(Ordering::Acquire),
4219 "zero-id stateful cancellation must normalize the session"
4220 );
4221 assert_eq!(client.state.lock().unwrap().fids[&7].opened, None);
4222 }
4223
4224 #[tokio::test]
4225 async fn cancelling_stateful_create_before_enqueue_keeps_connection_live() {
4226 let (conn, _requests) = test_conn_with_receiver();
4227 conn.writer_tx
4228 .send(vec![0])
4229 .await
4230 .expect("test writer queue should accept its first frame");
4231 let client = test_client(Arc::clone(&conn));
4232 let request_client = Arc::clone(&client);
4233 let request = tokio::spawn(async move {
4234 request_client
4235 .lcreate(7, b"child", linux::O_CREAT, 0o644, 0)
4236 .await
4237 });
4238 for _ in 0..4 {
4239 tokio::task::yield_now().await;
4240 }
4241 assert!(!request.is_finished());
4242 assert!(conn.pending.is_empty());
4243
4244 request.abort();
4245 let _ = request.await;
4246 assert!(
4247 !conn.dead.load(Ordering::Acquire),
4248 "pre-enqueue cancellation must not churn the live connection"
4249 );
4250 }
4251
4252 #[tokio::test]
4253 async fn cancelling_a_dispatched_request_quarantines_its_tag() {
4254 let (conn, mut requests) = test_conn_with_receiver();
4255 let client = test_client(Arc::clone(&conn));
4256 let request_client = Arc::clone(&client);
4257 let request = tokio::spawn(async move { request_client.write(7, 0, b"x").await });
4258
4259 let first_frame = tokio::time::timeout(Duration::from_secs(1), requests.recv())
4260 .await
4261 .expect("request was not queued")
4262 .expect("request channel closed");
4263 let first = P9Message::from_bytes_ctx(&first_frame, true).unwrap();
4264 assert_eq!(conn.pending.len(), 1);
4265 request.abort();
4266 let _ = request.await;
4267 assert_eq!(
4268 conn.pending.len(),
4269 1,
4270 "a dispatched request's tag must remain quarantined"
4271 );
4272
4273 conn.tag_ctr.store(first.tag, Ordering::Relaxed);
4274 let second_client = Arc::clone(&client);
4275 let second_request = tokio::spawn(async move { second_client.write(7, 0, b"y").await });
4276 let second_frame = tokio::time::timeout(Duration::from_secs(1), requests.recv())
4277 .await
4278 .expect("second request was not queued")
4279 .expect("request channel closed");
4280 let second = P9Message::from_bytes_ctx(&second_frame, true).unwrap();
4281 assert_ne!(second.tag, first.tag);
4282
4283 reply(&conn, first.tag, Message::Rwrite(Rwrite { count: 1 }));
4284 tokio::task::yield_now().await;
4285 assert!(
4286 !second_request.is_finished(),
4287 "the late response must not complete the new request"
4288 );
4289
4290 reply(&conn, second.tag, Message::Rwrite(Rwrite { count: 1 }));
4291 assert_eq!(second_request.await.unwrap().unwrap(), 1);
4292 assert!(conn.pending.is_empty());
4293 }
4294
4295 #[tokio::test]
4296 async fn cancelling_before_writer_capacity_does_not_register_a_tag() {
4297 let (conn, _requests) = test_conn_with_receiver();
4298 conn.writer_tx
4299 .send(vec![0])
4300 .await
4301 .expect("test writer queue should accept its first frame");
4302
4303 let client = test_client(Arc::clone(&conn));
4304 let request_client = Arc::clone(&client);
4305 let request = tokio::spawn(async move { request_client.write(7, 0, b"x").await });
4306 for _ in 0..4 {
4307 tokio::task::yield_now().await;
4308 }
4309 assert!(!request.is_finished());
4310 assert!(
4311 conn.pending.is_empty(),
4312 "waiting for writer capacity must happen before tag registration"
4313 );
4314 request.abort();
4315 let _ = request.await;
4316 assert!(conn.pending.is_empty());
4317 }
4318
4319 #[test]
4320 fn undispatched_guard_releases_its_tag() {
4321 let conn = test_conn();
4322
4323 let (tx, _rx) = oneshot::channel();
4324 let tag = NinePClient::alloc_tag(&conn, tx).expect("tag allocation failed");
4325 let guard = PendingTag {
4326 conn: Arc::clone(&conn),
4327 tag,
4328 dispatched: false,
4329 };
4330 assert!(conn.pending.contains_key(&tag));
4331 drop(guard);
4332 assert!(conn.pending.is_empty());
4333 }
4334
4335 #[test]
4336 fn allocator_checks_every_wire_tag_after_skipping_notag() {
4337 let conn = test_conn();
4338
4339 for tag in 0..(NOTAG - 1) {
4340 let (tx, _rx) = oneshot::channel();
4341 assert!(NinePClient::register_tag(&conn, tag, tx).is_ok());
4342 }
4343 conn.tag_ctr.store(NOTAG, Ordering::Relaxed);
4344
4345 let (tx, _rx) = oneshot::channel();
4346 let tag = NinePClient::alloc_tag(&conn, tx).expect("allocator missed the last usable tag");
4347 assert_eq!(tag, NOTAG - 1);
4348 }
4349}
4350
4351#[cfg(test)]
4352mod liveness_tests {
4353 use super::*;
4354
4355 fn test_conn() -> Arc<Conn> {
4356 test_conn_with_receiver().0
4357 }
4358
4359 #[test]
4360 fn within_is_strict_and_saturates() {
4361 let w = Duration::from_millis(300);
4362 assert!(Conn::within(1000, 800, w), "200ms ago is within 300ms");
4363 assert!(
4364 !Conn::within(1000, 700, w),
4365 "exactly at the window is NOT within (strict <)"
4366 );
4367 assert!(!Conn::within(1000, 500, w), "500ms ago is past 300ms");
4368 assert!(Conn::within(500, 1000, w));
4369 }
4370
4371 #[test]
4372 fn a_just_marked_conn_is_heard() {
4373 let conn = test_conn();
4374 assert!(conn.heard_within(Duration::from_secs(60)));
4375 conn.mark_alive();
4376 assert!(conn.heard_within(Duration::from_secs(60)));
4377 assert!(!conn.heard_within(Duration::from_millis(0)));
4378 }
4379}