Skip to main content

ninep_client/
ops.rs

1//! ZeroFS compound operations shared by the FUSE mount and `zerofs-client`.
2//!
3//! Returned fids belong to the caller. Errors leave no server-side fid.
4
5use crate::{ClientResult, NinePClient};
6use bytes::Bytes;
7use ninep_proto::{
8    DirEntry, DirEntryPlus, Qid, SETATTR_ATIME, SETATTR_ATIME_SET, SETATTR_GID, SETATTR_MODE,
9    SETATTR_MTIME, SETATTR_MTIME_SET, SETATTR_SIZE, SETATTR_UID, Tsetattr,
10};
11use std::collections::VecDeque;
12
13/// A time to set: the server's clock, or an explicit instant.
14#[derive(Clone, Copy, Debug)]
15pub enum SetattrTime {
16    Now,
17    At { sec: u64, nsec: u64 },
18}
19
20/// Builds a `Tsetattr`; `None` fields remain unchanged.
21pub struct SetattrBuilder {
22    ts: Tsetattr,
23}
24
25impl SetattrBuilder {
26    pub fn new(fid: u32) -> Self {
27        Self {
28            ts: Tsetattr {
29                fid,
30                valid: 0,
31                mode: 0,
32                uid: 0,
33                gid: 0,
34                size: 0,
35                atime_sec: 0,
36                atime_nsec: 0,
37                mtime_sec: 0,
38                mtime_nsec: 0,
39            },
40        }
41    }
42
43    /// Mode is passed through verbatim; mask before calling if needed.
44    pub fn mode(mut self, mode: Option<u32>) -> Self {
45        if let Some(mode) = mode {
46            self.ts.valid |= SETATTR_MODE;
47            self.ts.mode = mode;
48        }
49        self
50    }
51
52    pub fn uid(mut self, uid: Option<u32>) -> Self {
53        if let Some(uid) = uid {
54            self.ts.valid |= SETATTR_UID;
55            self.ts.uid = uid;
56        }
57        self
58    }
59
60    pub fn gid(mut self, gid: Option<u32>) -> Self {
61        if let Some(gid) = gid {
62            self.ts.valid |= SETATTR_GID;
63            self.ts.gid = gid;
64        }
65        self
66    }
67
68    pub fn size(mut self, size: Option<u64>) -> Self {
69        if let Some(size) = size {
70            self.ts.valid |= SETATTR_SIZE;
71            self.ts.size = size;
72        }
73        self
74    }
75
76    pub fn atime(mut self, atime: Option<SetattrTime>) -> Self {
77        match atime {
78            Some(SetattrTime::Now) => self.ts.valid |= SETATTR_ATIME,
79            Some(SetattrTime::At { sec, nsec }) => {
80                self.ts.valid |= SETATTR_ATIME | SETATTR_ATIME_SET;
81                self.ts.atime_sec = sec;
82                self.ts.atime_nsec = nsec;
83            }
84            None => {}
85        }
86        self
87    }
88
89    pub fn mtime(mut self, mtime: Option<SetattrTime>) -> Self {
90        match mtime {
91            Some(SetattrTime::Now) => self.ts.valid |= SETATTR_MTIME,
92            Some(SetattrTime::At { sec, nsec }) => {
93                self.ts.valid |= SETATTR_MTIME | SETATTR_MTIME_SET;
94                self.ts.mtime_sec = sec;
95                self.ts.mtime_nsec = nsec;
96            }
97            None => {}
98        }
99        self
100    }
101
102    pub fn build(self) -> Tsetattr {
103        self.ts
104    }
105}
106
107impl NinePClient {
108    /// Opens `from` on `newfid`. On error, `fallback_flags` permits one retry.
109    /// The caller owns `newfid`.
110    pub async fn open_clone(
111        &self,
112        from: u32,
113        newfid: u32,
114        flags: u32,
115        fallback_flags: Option<u32>,
116    ) -> ClientResult<(Qid, u32)> {
117        let mut res = self.lopenat(from, newfid, flags).await;
118        if res.is_err()
119            && let Some(orig) = fallback_flags
120        {
121            res = self.lopenat(from, newfid, orig).await;
122        }
123        res
124    }
125
126    /// Opens `from` on `newfid` and optionally prefetches from offset zero.
127    /// Returns data only when the prefetch reaches EOF. The caller owns `newfid`.
128    pub async fn open_clone_prefetch(
129        &self,
130        from: u32,
131        newfid: u32,
132        flags: u32,
133        prefetch: u32,
134    ) -> ClientResult<(Qid, u32, Option<Bytes>)> {
135        if prefetch > 0 {
136            let (qid, iounit, data, eof) = self.lopenatread(from, newfid, flags, prefetch).await?;
137            let buf = eof.then_some(data);
138            return Ok((qid, iounit, buf));
139        }
140        let (qid, iounit) = self.open_clone(from, newfid, flags, None).await?;
141        Ok((qid, iounit, None))
142    }
143}
144
145/// An entry type with a 9P readdir cookie.
146pub trait DirEntryCookie {
147    fn cookie(&self) -> u64;
148}
149
150impl DirEntryCookie for DirEntry {
151    fn cookie(&self) -> u64 {
152        self.offset
153    }
154}
155
156impl DirEntryCookie for DirEntryPlus {
157    fn cookie(&self) -> u64 {
158        self.offset
159    }
160}
161
162/// Readdir paging state.
163pub struct ReaddirState<E> {
164    /// Entries fetched but not yet delivered.
165    pub buf: VecDeque<E>,
166    /// 9P cookie for the next fetch.
167    pub fetch_cookie: u64,
168    /// The offset a sequential consumer would continue from (the cookie of
169    /// the last delivered entry); used by [`Self::seek`] to detect rewinds.
170    pub resume_offset: u64,
171    /// The server has no more entries past `fetch_cookie`.
172    pub eof: bool,
173}
174
175impl<E: DirEntryCookie> ReaddirState<E> {
176    pub fn starting_at(offset: u64) -> Self {
177        Self {
178            buf: VecDeque::new(),
179            fetch_cookie: offset,
180            resume_offset: offset,
181            eof: false,
182        }
183    }
184
185    /// Resets read-ahead when `offset` differs from the current position.
186    pub fn seek(&mut self, offset: u64) {
187        if offset != self.resume_offset {
188            self.buf.clear();
189            self.fetch_cookie = offset;
190            self.resume_offset = offset;
191            self.eof = false;
192        }
193    }
194
195    /// Adds a fetched batch and advances the next cookie. Empty input marks EOF.
196    pub fn absorb(&mut self, entries: Vec<E>) {
197        match entries.last() {
198            Some(last) => self.fetch_cookie = last.cookie(),
199            None => self.eof = true,
200        }
201        self.buf.extend(entries);
202    }
203}