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