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}