nfs3_server/vfs/mod.rs
1//! The basic API to implement to provide an NFS file system
2//!
3//! Opaque FH
4//! ---------
5//! Files are only uniquely identified by a 64-bit file id. (basically an inode number)
6//! We automatically produce internally the opaque filehandle which is comprised of
7//! - A 64-bit generation number derived from the server startup time (i.e. so the opaque file
8//! handle expires when the NFS server restarts)
9//! - The 64-bit file id
10//!
11//! readdir pagination
12//! ------------------
13//! We do not use cookie verifier. We just use the `start_after`. The
14//! implementation should allow startat to start at any position. That is,
15//! the next query to readdir may be the last entry in the previous readdir
16//! response.
17//!
18//! Other requirements
19//! ------------------
20//! getattr needs to be fast. NFS uses that a lot
21//!
22//! The 0 fileid is reserved and should not be used
23
24pub mod adapters;
25pub(crate) mod handle;
26mod iterator;
27
28pub use handle::{FileHandle, FileHandleU64};
29pub use iterator::*;
30
31use crate::nfs3_types::nfs3::{
32 FSF3_CANSETTIME, FSF3_HOMOGENEOUS, FSF3_SYMLINK, FSINFO3resok as fsinfo3, createverf3, fattr3,
33 filename3, nfspath3, nfsstat3, nfstime3, post_op_attr, sattr3,
34};
35use crate::units::{GIBIBYTE, MEBIBYTE};
36use crate::vfs::adapters::ReadDirPlusToReadDir;
37
38/// What capabilities are supported
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum VFSCapabilities {
41 ReadOnly,
42 ReadWrite,
43}
44
45/// Read-only file system interface
46///
47/// This should be enough to implement a read-only NFS server.
48/// If you want to implement a read-write server, you should implement
49/// the [`NfsFileSystem`] trait too.
50pub trait NfsReadFileSystem: Send + Sync {
51 /// Type that can be used to indentify a file or folder in the file system.
52 ///
53 /// For more information, see [`FileHandle`].
54 type Handle: FileHandle;
55
56 /// Returns the ID the of the root directory "/"
57 fn root_dir(&self) -> Self::Handle;
58
59 /// Look up the id of a path in a directory
60 ///
61 /// i.e. given a directory dir/ containing a file `a.txt`
62 /// this may call `lookup(id_of("dir/"), "a.txt")`
63 /// and this should return the id of the file `dir/a.txt`
64 ///
65 /// This method should be fast as it is used very frequently.
66 fn lookup(
67 &self,
68 dirid: &Self::Handle,
69 filename: &filename3<'_>,
70 ) -> impl Future<Output = Result<Self::Handle, nfsstat3>> + Send;
71
72 /// This method is used when the client tries to mount a subdirectory.
73 /// The default implementation walks the directory structure with [`lookup`](Self::lookup).
74 fn lookup_by_path(
75 &self,
76 path: &str,
77 ) -> impl Future<Output = Result<Self::Handle, nfsstat3>> + Send {
78 async move {
79 let splits = path.split('/');
80 let mut fid = self.root_dir();
81 for component in splits {
82 if component.is_empty() {
83 continue;
84 }
85 fid = self.lookup(&fid, &component.as_bytes().into()).await?;
86 }
87 Ok(fid)
88 }
89 }
90
91 /// Returns the attributes of an id.
92 /// This method should be fast as it is used very frequently.
93 fn getattr(&self, id: &Self::Handle) -> impl Future<Output = Result<fattr3, nfsstat3>> + Send;
94
95 /// Reads the contents of a file returning (bytes, EOF)
96 /// Note that offset/count may go past the end of the file and that
97 /// in that case, all bytes till the end of file are returned.
98 /// EOF must be flagged if the end of the file is reached by the read.
99 fn read(
100 &self,
101 id: &Self::Handle,
102 offset: u64,
103 count: u32,
104 ) -> impl Future<Output = Result<(Vec<u8>, bool), nfsstat3>> + Send;
105
106 /// Simple version of readdir. Only need to return filename and id
107 ///
108 /// By default it uses `readdirplus` method to create an iterator
109 fn readdir(
110 &self,
111 dirid: &Self::Handle,
112 cookie: u64,
113 ) -> impl Future<Output = Result<impl ReadDirIterator, nfsstat3>> + Send {
114 async move {
115 self.readdirplus(dirid, cookie)
116 .await
117 .map(ReadDirPlusToReadDir::new)
118 }
119 }
120
121 /// Returns the contents of a directory with pagination.
122 /// Directory listing should be deterministic.
123 /// Up to `max_entries` may be returned, and `start_after` is used
124 /// to determine where to start returning entries from.
125 ///
126 /// For instance if the directory has entry with ids `[1,6,2,11,8,9]`
127 /// and `start_after=6`, readdir should returning 2,11,8,...
128 fn readdirplus(
129 &self,
130 dirid: &Self::Handle,
131 cookie: u64,
132 ) -> impl Future<Output = Result<impl ReadDirPlusIterator<Self::Handle>, nfsstat3>> + Send;
133
134 /// Reads a symlink
135 fn readlink(
136 &self,
137 id: &Self::Handle,
138 ) -> impl Future<Output = Result<nfspath3<'_>, nfsstat3>> + Send;
139
140 /// Get static file system Information
141 fn fsinfo(
142 &self,
143 root_fileid: &Self::Handle,
144 ) -> impl Future<Output = Result<fsinfo3, nfsstat3>> + Send {
145 async move {
146 let dir_attr = self
147 .getattr(root_fileid)
148 .await
149 .map_or(post_op_attr::None, post_op_attr::Some);
150
151 let res = fsinfo3 {
152 obj_attributes: dir_attr,
153 rtmax: MEBIBYTE,
154 rtpref: MEBIBYTE,
155 rtmult: MEBIBYTE,
156 wtmax: MEBIBYTE,
157 wtpref: MEBIBYTE,
158 wtmult: MEBIBYTE,
159 dtpref: MEBIBYTE,
160 maxfilesize: 128u64 * GIBIBYTE,
161 time_delta: nfstime3 {
162 seconds: 0,
163 nseconds: 1_000_000,
164 },
165 properties: FSF3_SYMLINK | FSF3_HOMOGENEOUS | FSF3_CANSETTIME,
166 };
167 Ok(res)
168 }
169 }
170}
171
172/// Write file system interface
173///
174/// This is the interface to implement if you want to provide a writable NFS server.
175pub trait NfsFileSystem: NfsReadFileSystem {
176 /// Returns the set of capabilities supported
177 fn capabilities(&self) -> VFSCapabilities {
178 VFSCapabilities::ReadWrite
179 }
180
181 /// Sets the attributes of an id
182 /// this should return `Err(nfsstat3::NFS3ERR_ROFS)` if readonly
183 fn setattr(
184 &self,
185 id: &Self::Handle,
186 setattr: sattr3,
187 ) -> impl Future<Output = Result<fattr3, nfsstat3>> + Send;
188
189 /// Writes the contents of a file returning (bytes, EOF)
190 /// Note that offset/count may go past the end of the file and that
191 /// in that case, the file is extended.
192 /// If not supported due to readonly file system
193 /// this should return `Err(nfsstat3::NFS3ERR_ROFS)`
194 ///
195 /// # `NFS3ERR_INVAL`:
196 ///
197 /// Some NFS version 2 protocol server implementations
198 /// incorrectly returned `NFSERR_ISDIR` if the file system
199 /// object type was not a regular file. The correct return
200 /// value for the NFS version 3 protocol is `NFS3ERR_INVAL`.
201 fn write(
202 &self,
203 id: &Self::Handle,
204 offset: u64,
205 data: &[u8],
206 ) -> impl Future<Output = Result<fattr3, nfsstat3>> + Send;
207
208 /// Creates a file with the following attributes.
209 /// If not supported due to readonly file system
210 /// this should return `Err(nfsstat3::NFS3ERR_ROFS)`
211 fn create(
212 &self,
213 dirid: &Self::Handle,
214 filename: &filename3<'_>,
215 attr: sattr3,
216 ) -> impl Future<Output = Result<(Self::Handle, fattr3), nfsstat3>> + Send;
217
218 /// Creates a file if it does not already exist.
219 /// If not supported due to readonly file system
220 /// this should return `Err(nfsstat3::NFS3ERR_ROFS)`
221 ///
222 /// # NOTE:
223 /// If the server can not support these exclusive create
224 /// semantics, possibly because of the requirement to commit
225 /// the verifier to stable storage, it should fail the CREATE
226 /// request with the error, `NFS3ERR_NOTSUPP`.
227 fn create_exclusive(
228 &self,
229 dirid: &Self::Handle,
230 filename: &filename3<'_>,
231 createverf: createverf3,
232 ) -> impl Future<Output = Result<Self::Handle, nfsstat3>> + Send;
233
234 /// Makes a directory with the following attributes.
235 /// If not supported dur to readonly file system
236 /// this should return `Err(nfsstat3::NFS3ERR_ROFS)`
237 fn mkdir(
238 &self,
239 dirid: &Self::Handle,
240 dirname: &filename3<'_>,
241 ) -> impl Future<Output = Result<(Self::Handle, fattr3), nfsstat3>> + Send;
242
243 /// Removes a file.
244 /// If not supported due to readonly file system
245 /// this should return `Err(nfsstat3::NFS3ERR_ROFS)`
246 fn remove(
247 &self,
248 dirid: &Self::Handle,
249 filename: &filename3<'_>,
250 ) -> impl Future<Output = Result<(), nfsstat3>> + Send;
251
252 /// Removes a file.
253 /// If not supported due to readonly file system
254 /// this should return `Err(nfsstat3::NFS3ERR_ROFS)`
255 ///
256 /// # NOTE:
257 ///
258 /// If the directory, `to_dirid`, already contains an entry with
259 /// the name, `to_filename`, the source object must be compatible
260 /// with the target: either both are non-directories or both
261 /// are directories and the target must be empty. If
262 /// compatible, the existing target is removed before the
263 /// rename occurs. If they are not compatible or if the target
264 /// is a directory but not empty, the server should return the
265 /// error, `NFS3ERR_EXIST`.
266 fn rename<'a>(
267 &self,
268 from_dirid: &Self::Handle,
269 from_filename: &filename3<'a>,
270 to_dirid: &Self::Handle,
271 to_filename: &filename3<'a>,
272 ) -> impl Future<Output = Result<(), nfsstat3>> + Send;
273
274 /// Makes a symlink with the following attributes.
275 /// If not supported due to readonly file system
276 /// this should return `Err(nfsstat3::NFS3ERR_ROFS)`
277 fn symlink<'a>(
278 &self,
279 dirid: &Self::Handle,
280 linkname: &filename3<'a>,
281 symlink: &nfspath3<'a>,
282 attr: &sattr3,
283 ) -> impl Future<Output = Result<(Self::Handle, fattr3), nfsstat3>> + Send;
284}