├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── demo.rs └── mirrorfs.rs └── src ├── config.rs ├── context.rs ├── fs_util.rs ├── lib.rs ├── mount.rs ├── mount_handlers.rs ├── nfs.rs ├── nfs_handlers.rs ├── portmap.rs ├── portmap_handlers.rs ├── rpc.rs ├── rpcwire.rs ├── tcp.rs ├── transaction_tracker.rs ├── vfs.rs ├── write_counter.rs └── xdr.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nfsserve" 3 | version = "0.10.2" 4 | edition = "2021" 5 | authors = ["Yucheng Low "] 6 | description = "A Rust NFS Server implementation" 7 | homepage = "https://github.com/xetdata/nfsserve" 8 | repository = "https://github.com/xetdata/nfsserve" 9 | readme = "README.md" 10 | keywords = ["nfs"] 11 | license = "BSD-3-Clause" 12 | categories = ["network-programming", "filesystem"] 13 | publish = true 14 | include = ["src/**/*", "src/*", "Cargo.toml", "LICENSE", "README.md"] 15 | 16 | 17 | [lib] 18 | doctest = false 19 | 20 | [dependencies] 21 | bytestream = "0.4" 22 | byteorder = "1.4" 23 | num-traits = "0.2" 24 | num-derive = "0.3" 25 | tokio = { version="1", features = [ "net", "io-util", "sync", "fs", "rt", "macros" ], default-features = false } 26 | futures = "0.3.21" 27 | tracing = "0.1.31" 28 | tracing-attributes = "0.1" 29 | anyhow = "1" 30 | async-trait = "0.1.9" 31 | smallvec = "1.10.0" 32 | filetime = "0.2" 33 | 34 | # demo 35 | tracing-subscriber = { version = "0.3", features = ["tracing-log"], optional = true } 36 | intaglio = { version = "1.6", optional = true } 37 | 38 | [features] 39 | strict = [] 40 | demo = ["tracing-subscriber", "tokio/rt-multi-thread", "intaglio"] 41 | 42 | 43 | [[example]] 44 | name = "demo" 45 | required-features = ["demo"] 46 | path = "examples/demo.rs" 47 | 48 | [[example]] 49 | name = "mirrorfs" 50 | required-features = ["demo"] 51 | path = "examples/mirrorfs.rs" 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, XetData 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rust NFSv3 Server 2 | ================= 3 | This is an incomplete but very functional implementation of an NFSv3 server 4 | in Rust. 5 | 6 | Why? You may ask. 7 | 8 | I wanted to implement a user-mode file-system mount that is truly cross-platform. 9 | What is a protocol that pretty much every OS supports? NFS. 10 | 11 | Why not FUSE you may ask: 12 | 1. FUSE is annoying to users on Mac and Windows (drivers necessary). 13 | 2. It takes a lot of care to build a FUSE driver for remote filesystems. 14 | NFS clients however have a lot of historical robustification for 15 | slow-responding, or perhaps, never-responding servers. 16 | 3. The OS is pretty good at caching NFS. There are established principles for 17 | cache eviction, for metadata, or for data. With a FUSE driver I have to do 18 | a lot of the work myself. 19 | 20 | So, this is a FUSE-like user-mode filesystem API that basically works by 21 | creating a localhost NFSv3 server you can mount. 22 | 23 | This is used in [pyxet](https://github.com/xetdata/pyxet) and 24 | [xet-core](https://github.com/xetdata/xet-core/) to provide the `xet mount` 25 | functionality that allows you to mount multi-TB [Xethub](https://about.xethub.com) repository 26 | anywhere. 27 | 28 | This is a blogpost explaining our rationale: https://about.xethub.com/blog/nfs-fuse-why-we-built-nfs-server-rust 29 | 30 | Run the Demo 31 | ============ 32 | To run the demofs, this will host an NFS server on localhost:11111 33 | ``` 34 | cargo build --example demo --features demo 35 | ./target/debug/examples/demo 36 | ``` 37 | 38 | To mount. On Linux (sudo may be required): 39 | ``` 40 | mkdir demo 41 | mount.nfs -o user,noacl,nolock,vers=3,tcp,wsize=1048576,rsize=131072,actimeo=120,port=11111,mountport=11111 localhost:/ demo 42 | ``` 43 | 44 | On Mac: 45 | ``` 46 | mkdir demo 47 | mount_nfs -o nolocks,vers=3,tcp,rsize=131072,actimeo=120,port=11111,mountport=11111 localhost:/ demo 48 | ``` 49 | 50 | On Windows (Pro required as Home does not have NFS client): 51 | ``` 52 | mount.exe -o anon,nolock,mtype=soft,fileaccess=6,casesensitive,lang=ansi,rsize=128,wsize=128,timeout=60,retry=2 \\127.0.0.1\\ X: 53 | ``` 54 | 55 | Note that the demo filesystem is *writable*. 56 | 57 | Usage 58 | ===== 59 | 60 | You simply need to implement the vfs::NFSFileSystem 61 | trait. See demofs.rs for an example and bin/main.rs for how to actually start 62 | a service. The interface generally not difficult to implement; demanding mainly 63 | the ability to associate every file system object (directory/file) with a 64-bit 64 | ID. Directory listing can be a bit complicated due to the pagination requirements. 65 | 66 | TODO and Seeking Contributors 67 | ============================= 68 | - Improve documentation 69 | - More things in Mount Protocol and NFS Protocol has to be implemented. 70 | There are a bunch of messages that reply as "Unavailable". For instance, 71 | we implement `READDIR_PLUS` but not `READDIR` which is usually fine, except 72 | that Windows insists on always trying READDIR first. 73 | Link creation is also not supported. 74 | - The RPC message handling in `nfs_handlers.rs` leaves a lot to be desired. 75 | The response serialization is very manual. Some cleanup will be good. 76 | - Windows mount "kinda" works (only on Windows 11 Pro with the NFS server), 77 | but prints a lot of garbage due to various unimplemented APIs. Windows 11 78 | somehow tries to poll with very old NFS protocols constantly. 79 | - Many many perf optimizations. 80 | - Maybe pull in the mount command from [xet-core](https://github.com/xetdata/xet-core/blob/main/rust/gitxetcore/src/xetmnt/mod.rs) 81 | so the user does not need to remember the `-o` incantations above. 82 | - Maybe make an SMB3 implementation so we can work on Windows Home edition 83 | - NFSv4 has some write performance optimizations that would be quite nice. 84 | The protocol is a bit more involving to implement though as it is somewhat 85 | stateful. 86 | 87 | Relevant RFCs 88 | ============= 89 | - XDR is the message format: RFC 1014. https://datatracker.ietf.org/doc/html/rfc1014 90 | - SUN RPC is the RPC wire format: RFC 1057 https://datatracker.ietf.org/doc/html/rfc1057 91 | - NFS is at RFC 1813 https://datatracker.ietf.org/doc/html/rfc1813 92 | - NFS Mount Protocol is at RFC 1813 Appendix I. https://datatracker.ietf.org/doc/html/rfc1813#appendix-I 93 | - PortMapper is at RFC 1057 Appendix A https://datatracker.ietf.org/doc/html/rfc1057#appendix-A 94 | 95 | Basic Source Layout 96 | =================== 97 | - context.rs: A connection context object that is passed around containing 98 | connection information, VFS information, etc. 99 | - xdr.rs: Serialization / Deserialization routines for XDR structures 100 | - tcp.rs: Main TCP handling entry point 101 | - rpcwire.rs: Reads and write RPC messages from a TCP socket and performs outer 102 | most RPC message decoding, redirecting to NFS/Mount/Portmapper 103 | implementations as needed. 104 | - rpc.rs: The structure of a RPC call and reply. All XDR encoded. 105 | - portmap.rs/portmap\_handlers.rs: The XDR structures required by the Portmapper protocol and the Portmapper RPC handlers. 106 | - mount.rs/mount\_handlers.rs: The XDR structures required by the Mount protocol and the Mount RPC handlers. 107 | - nfs.rs/nfs\_handlers.rs: The XDR structures required by the NFS protocol and the NFS RPC handlers. 108 | 109 | 110 | More More Details Than Necessary 111 | ================================ 112 | The basic way a message works is: 113 | 1. We read a collection of fragments off a TCP stream 114 | (a 4 byte length header followed by a bunch of bytes) 115 | 2. We assemble the fragments into a record 116 | 3. The Record is of a SUN RPC message type. 117 | 4. A message tells us 3 pieces of information, 118 | - The RPC Program (just an integer denoting 119 | a protocol "class". For instance NFS protocol is 100003, the Portmapper protocol is 100000). 120 | - The version of the RPC program (ex: 3 = NFSv3, 4 = NFSv4, etc) 121 | - The method invoked (Which NFS method to call) (See for instance nfs.rs top comment for the list) 122 | 5. Continuing to decode the message will give us the arguments of the method 123 | 6. And we take the method response, wrap it around a record and return it. 124 | 125 | Portmapper 126 | ---------- 127 | First, lets get portmapper out of the way. This is a *very* old mechanism which 128 | is rarely used anymore. The portmapper is a daemon which runs on a machine running 129 | on port 111. When NFS, or other RPC services start, they register with the 130 | portmapper service with the port they are listening on (Say NFS on 2049). 131 | Then when another machine wants to connect to NFS, they first ask the port mapper 132 | on 111 to ask about which port NFS is listening on, then connects to the returned 133 | port. 134 | 135 | We do not strictly need to implement this protocol as this is pretty much 136 | unused these days (NFSv4 does not use the portmapper for instance). If `-o port` and `-o mountport` 137 | are specified, Linux and Mac's builtin NFS client do not need it either. 138 | But this was useful for debugging and testing as libnfs seems to require a 139 | portmapper, but it annoyingly hardcodes it to 111. I modified the source to 140 | change it to 12000 for testing and implemented the one `PMAPPROC_GETPORT` 141 | method so I can test with libnfs. 142 | 143 | 144 | NFS Basics 145 | ========== 146 | The way NFS works is that every file system object (dir/file/symlink) has 2 147 | ways in which it can be addressed: 148 | 149 | 1. `fileid3: u64` . A 64-bit integer. Equivalent to an inode number. 150 | 2. `nfs_fh3`: A variable opaque object up to 64 bytes long. 151 | 152 | Basically anytime the client tries to access any information about an object, 153 | it needs an `nfs_fh3`. The purpose of the `nfs_fh3` serves 2 purposes: 154 | 155 | - Allow server to cache additional query information in the handle that may exceed 156 | 64-bit. For instance if the server has multiple exports on different disk volumes, 157 | I may need a few more bits to identify the disk volume. 158 | - Allow client to identify when server has "restarted" and thus client has to 159 | clear all caches. the `nfs_fh3` handle should contain a token that is unique 160 | to when the NFS server first started up which allows the server to check that 161 | the handle is still valid. If the server has restarted, all previous handles 162 | will therefore be "expired" and any usage of them should trigger a handle expiry 163 | error informing the clients to expunge all caches. 164 | 165 | 166 | However, the only way to obtain an `nfs_fh3` for a file is via directory traversal. 167 | i.e. There is a lookup method 168 | `LOOKUP(directory's handle, filename of file/dir in directory)` 169 | which returns the handle for the filename. 170 | 171 | For instance to get the handle of a file "dir/a.txt", I first need the handle 172 | for the directory "dir/", then query `LOOKUP(handle, "a.txt")`. 173 | 174 | The question is then, how do I get my first handle? That is what the MOUNT 175 | protocol addresses. 176 | 177 | Mount 178 | ----- 179 | The MOUNT protocol provides a list of "exports", (in the simplest case. Just "/") 180 | and the client will request to MNT("/") which will return the handle of this 181 | root directory. 182 | 183 | Normally the server can and do maintain a list of mounts which can be queried, 184 | and really the client can UMNT (unmount) as well. But in our case we 185 | only implement MNT and EXPORT which suffices. NFS clients generally 186 | ignore the return message of UMNT as there is really nothing the 187 | client can do on a UMNT failure. As such our Mount protocol implementation 188 | is entirely stateless. 189 | 190 | NFS 191 | --- 192 | The NFS protocol itself is pretty straightforward with most annoyances 193 | due to handling of the XDR messaging format (in paticular with optional, 194 | lists, etc). 195 | 196 | What is nice is that the design of NFS is completely stateless. It is mostly 197 | sit down and implement all the methods that are hit and test them against a 198 | client. 199 | -------------------------------------------------------------------------------- /examples/demo.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | use std::time::SystemTime; 3 | 4 | use async_trait::async_trait; 5 | 6 | use nfsserve::{ 7 | nfs::{ 8 | self, fattr3, fileid3, filename3, ftype3, nfspath3, nfsstat3, nfstime3, sattr3, specdata3, 9 | }, 10 | tcp::*, 11 | vfs::{DirEntry, NFSFileSystem, ReadDirResult, VFSCapabilities}, 12 | }; 13 | 14 | #[derive(Debug, Clone)] 15 | enum FSContents { 16 | File(Vec), 17 | Directory(Vec), 18 | } 19 | #[allow(dead_code)] 20 | #[derive(Debug, Clone)] 21 | struct FSEntry { 22 | id: fileid3, 23 | attr: fattr3, 24 | name: filename3, 25 | parent: fileid3, 26 | contents: FSContents, 27 | } 28 | 29 | fn make_file(name: &str, id: fileid3, parent: fileid3, contents: &[u8]) -> FSEntry { 30 | let attr = fattr3 { 31 | ftype: ftype3::NF3REG, 32 | mode: 0o755, 33 | nlink: 1, 34 | uid: 507, 35 | gid: 507, 36 | size: contents.len() as u64, 37 | used: contents.len() as u64, 38 | rdev: specdata3::default(), 39 | fsid: 0, 40 | fileid: id, 41 | atime: nfstime3::default(), 42 | mtime: nfstime3::default(), 43 | ctime: nfstime3::default(), 44 | }; 45 | FSEntry { 46 | id, 47 | attr, 48 | name: name.as_bytes().into(), 49 | parent, 50 | contents: FSContents::File(contents.to_vec()), 51 | } 52 | } 53 | 54 | fn make_dir(name: &str, id: fileid3, parent: fileid3, contents: Vec) -> FSEntry { 55 | let attr = fattr3 { 56 | ftype: ftype3::NF3DIR, 57 | mode: 0o777, 58 | nlink: 1, 59 | uid: 507, 60 | gid: 507, 61 | size: 0, 62 | used: 0, 63 | rdev: specdata3::default(), 64 | fsid: 0, 65 | fileid: id, 66 | atime: nfstime3::default(), 67 | mtime: nfstime3::default(), 68 | ctime: nfstime3::default(), 69 | }; 70 | FSEntry { 71 | id, 72 | attr, 73 | name: name.as_bytes().into(), 74 | parent, 75 | contents: FSContents::Directory(contents), 76 | } 77 | } 78 | 79 | #[derive(Debug)] 80 | pub struct DemoFS { 81 | fs: Mutex>, 82 | rootdir: fileid3, 83 | } 84 | 85 | impl Default for DemoFS { 86 | fn default() -> DemoFS { 87 | // build the following directory structure 88 | // / 89 | // |-a.txt 90 | // |-b.txt 91 | // |-another_dir 92 | // |-thisworks.txt 93 | // 94 | let entries = vec![ 95 | make_file("", 0, 0, &[]), // fileid 0 is special 96 | make_dir( 97 | "/", 98 | 1, // current id. Must match position in entries 99 | 1, // parent id 100 | vec![2, 3, 4], // children 101 | ), 102 | make_file( 103 | "a.txt", 104 | 2, // current id 105 | 1, // parent id 106 | "hello world\n".as_bytes(), 107 | ), 108 | make_file("b.txt", 3, 1, "Greetings to xet data\n".as_bytes()), 109 | make_dir("another_dir", 4, 1, vec![5]), 110 | make_file("thisworks.txt", 5, 4, "i hope\n".as_bytes()), 111 | ]; 112 | 113 | DemoFS { 114 | fs: Mutex::new(entries), 115 | rootdir: 1, 116 | } 117 | } 118 | } 119 | 120 | // For this demo file system we let the handle just be the file 121 | // there is only 1 file. a.txt. 122 | #[async_trait] 123 | impl NFSFileSystem for DemoFS { 124 | fn root_dir(&self) -> fileid3 { 125 | self.rootdir 126 | } 127 | 128 | fn capabilities(&self) -> VFSCapabilities { 129 | VFSCapabilities::ReadWrite 130 | } 131 | 132 | async fn write(&self, id: fileid3, offset: u64, data: &[u8]) -> Result { 133 | { 134 | let mut fs = self.fs.lock().unwrap(); 135 | let mut fssize = fs[id as usize].attr.size; 136 | if let FSContents::File(bytes) = &mut fs[id as usize].contents { 137 | let offset = offset as usize; 138 | if offset + data.len() > bytes.len() { 139 | bytes.resize(offset + data.len(), 0); 140 | bytes[offset..].copy_from_slice(data); 141 | fssize = bytes.len() as u64; 142 | } 143 | } 144 | fs[id as usize].attr.size = fssize; 145 | fs[id as usize].attr.used = fssize; 146 | } 147 | self.getattr(id).await 148 | } 149 | 150 | async fn create( 151 | &self, 152 | dirid: fileid3, 153 | filename: &filename3, 154 | _attr: sattr3, 155 | ) -> Result<(fileid3, fattr3), nfsstat3> { 156 | let newid: fileid3; 157 | { 158 | let mut fs = self.fs.lock().unwrap(); 159 | newid = fs.len() as fileid3; 160 | fs.push(make_file( 161 | std::str::from_utf8(filename).unwrap(), 162 | newid, 163 | dirid, 164 | "".as_bytes(), 165 | )); 166 | if let FSContents::Directory(dir) = &mut fs[dirid as usize].contents { 167 | dir.push(newid); 168 | } 169 | } 170 | Ok((newid, self.getattr(newid).await.unwrap())) 171 | } 172 | 173 | async fn create_exclusive( 174 | &self, 175 | _dirid: fileid3, 176 | _filename: &filename3, 177 | ) -> Result { 178 | Err(nfsstat3::NFS3ERR_NOTSUPP) 179 | } 180 | 181 | async fn lookup(&self, dirid: fileid3, filename: &filename3) -> Result { 182 | let fs = self.fs.lock().unwrap(); 183 | let entry = fs.get(dirid as usize).ok_or(nfsstat3::NFS3ERR_NOENT)?; 184 | if let FSContents::File(_) = entry.contents { 185 | return Err(nfsstat3::NFS3ERR_NOTDIR); 186 | } else if let FSContents::Directory(dir) = &entry.contents { 187 | // if looking for dir/. its the current directory 188 | if filename[..] == [b'.'] { 189 | return Ok(dirid); 190 | } 191 | // if looking for dir/.. its the parent directory 192 | if filename[..] == [b'.', b'.'] { 193 | return Ok(entry.parent); 194 | } 195 | for i in dir { 196 | if let Some(f) = fs.get(*i as usize) { 197 | if f.name[..] == filename[..] { 198 | return Ok(*i); 199 | } 200 | } 201 | } 202 | } 203 | Err(nfsstat3::NFS3ERR_NOENT) 204 | } 205 | async fn getattr(&self, id: fileid3) -> Result { 206 | let fs = self.fs.lock().unwrap(); 207 | let entry = fs.get(id as usize).ok_or(nfsstat3::NFS3ERR_NOENT)?; 208 | Ok(entry.attr) 209 | } 210 | async fn setattr(&self, id: fileid3, setattr: sattr3) -> Result { 211 | let mut fs = self.fs.lock().unwrap(); 212 | let entry = fs.get_mut(id as usize).ok_or(nfsstat3::NFS3ERR_NOENT)?; 213 | match setattr.atime { 214 | nfs::set_atime::DONT_CHANGE => {} 215 | nfs::set_atime::SET_TO_CLIENT_TIME(c) => { 216 | entry.attr.atime = c; 217 | } 218 | nfs::set_atime::SET_TO_SERVER_TIME => { 219 | let d = SystemTime::now() 220 | .duration_since(SystemTime::UNIX_EPOCH) 221 | .unwrap(); 222 | entry.attr.atime.seconds = d.as_secs() as u32; 223 | entry.attr.atime.nseconds = d.subsec_nanos(); 224 | } 225 | }; 226 | match setattr.mtime { 227 | nfs::set_mtime::DONT_CHANGE => {} 228 | nfs::set_mtime::SET_TO_CLIENT_TIME(c) => { 229 | entry.attr.mtime = c; 230 | } 231 | nfs::set_mtime::SET_TO_SERVER_TIME => { 232 | let d = SystemTime::now() 233 | .duration_since(SystemTime::UNIX_EPOCH) 234 | .unwrap(); 235 | entry.attr.mtime.seconds = d.as_secs() as u32; 236 | entry.attr.mtime.nseconds = d.subsec_nanos(); 237 | } 238 | }; 239 | match setattr.uid { 240 | nfs::set_uid3::uid(u) => { 241 | entry.attr.uid = u; 242 | } 243 | nfs::set_uid3::Void => {} 244 | } 245 | match setattr.gid { 246 | nfs::set_gid3::gid(u) => { 247 | entry.attr.gid = u; 248 | } 249 | nfs::set_gid3::Void => {} 250 | } 251 | match setattr.size { 252 | nfs::set_size3::size(s) => { 253 | entry.attr.size = s; 254 | entry.attr.used = s; 255 | if let FSContents::File(bytes) = &mut entry.contents { 256 | bytes.resize(s as usize, 0); 257 | } 258 | } 259 | nfs::set_size3::Void => {} 260 | } 261 | Ok(entry.attr) 262 | } 263 | 264 | async fn read( 265 | &self, 266 | id: fileid3, 267 | offset: u64, 268 | count: u32, 269 | ) -> Result<(Vec, bool), nfsstat3> { 270 | let fs = self.fs.lock().unwrap(); 271 | let entry = fs.get(id as usize).ok_or(nfsstat3::NFS3ERR_NOENT)?; 272 | if let FSContents::Directory(_) = entry.contents { 273 | return Err(nfsstat3::NFS3ERR_ISDIR); 274 | } else if let FSContents::File(bytes) = &entry.contents { 275 | let mut start = offset as usize; 276 | let mut end = offset as usize + count as usize; 277 | let eof = end >= bytes.len(); 278 | if start >= bytes.len() { 279 | start = bytes.len(); 280 | } 281 | if end > bytes.len() { 282 | end = bytes.len(); 283 | } 284 | return Ok((bytes[start..end].to_vec(), eof)); 285 | } 286 | Err(nfsstat3::NFS3ERR_NOENT) 287 | } 288 | 289 | async fn readdir( 290 | &self, 291 | dirid: fileid3, 292 | start_after: fileid3, 293 | max_entries: usize, 294 | ) -> Result { 295 | let fs = self.fs.lock().unwrap(); 296 | let entry = fs.get(dirid as usize).ok_or(nfsstat3::NFS3ERR_NOENT)?; 297 | if let FSContents::File(_) = entry.contents { 298 | return Err(nfsstat3::NFS3ERR_NOTDIR); 299 | } else if let FSContents::Directory(dir) = &entry.contents { 300 | let mut ret = ReadDirResult { 301 | entries: Vec::new(), 302 | end: false, 303 | }; 304 | let mut start_index = 0; 305 | if start_after > 0 { 306 | if let Some(pos) = dir.iter().position(|&r| r == start_after) { 307 | start_index = pos + 1; 308 | } else { 309 | return Err(nfsstat3::NFS3ERR_BAD_COOKIE); 310 | } 311 | } 312 | let remaining_length = dir.len() - start_index; 313 | 314 | for i in dir[start_index..].iter() { 315 | ret.entries.push(DirEntry { 316 | fileid: *i, 317 | name: fs[(*i) as usize].name.clone(), 318 | attr: fs[(*i) as usize].attr, 319 | }); 320 | if ret.entries.len() >= max_entries { 321 | break; 322 | } 323 | } 324 | if ret.entries.len() == remaining_length { 325 | ret.end = true; 326 | } 327 | return Ok(ret); 328 | } 329 | Err(nfsstat3::NFS3ERR_NOENT) 330 | } 331 | 332 | /// Removes a file. 333 | /// If not supported dur to readonly file system 334 | /// this should return Err(nfsstat3::NFS3ERR_ROFS) 335 | #[allow(unused)] 336 | async fn remove(&self, dirid: fileid3, filename: &filename3) -> Result<(), nfsstat3> { 337 | return Err(nfsstat3::NFS3ERR_NOTSUPP); 338 | } 339 | 340 | /// Removes a file. 341 | /// If not supported dur to readonly file system 342 | /// this should return Err(nfsstat3::NFS3ERR_ROFS) 343 | #[allow(unused)] 344 | async fn rename( 345 | &self, 346 | from_dirid: fileid3, 347 | from_filename: &filename3, 348 | to_dirid: fileid3, 349 | to_filename: &filename3, 350 | ) -> Result<(), nfsstat3> { 351 | return Err(nfsstat3::NFS3ERR_NOTSUPP); 352 | } 353 | 354 | #[allow(unused)] 355 | async fn mkdir( 356 | &self, 357 | _dirid: fileid3, 358 | _dirname: &filename3, 359 | ) -> Result<(fileid3, fattr3), nfsstat3> { 360 | Err(nfsstat3::NFS3ERR_ROFS) 361 | } 362 | 363 | async fn symlink( 364 | &self, 365 | _dirid: fileid3, 366 | _linkname: &filename3, 367 | _symlink: &nfspath3, 368 | _attr: &sattr3, 369 | ) -> Result<(fileid3, fattr3), nfsstat3> { 370 | Err(nfsstat3::NFS3ERR_ROFS) 371 | } 372 | async fn readlink(&self, _id: fileid3) -> Result { 373 | return Err(nfsstat3::NFS3ERR_NOTSUPP); 374 | } 375 | } 376 | 377 | const HOSTPORT: u32 = 11111; 378 | 379 | #[tokio::main] 380 | async fn main() { 381 | tracing_subscriber::fmt() 382 | .with_max_level(tracing::Level::DEBUG) 383 | .with_writer(std::io::stderr) 384 | .init(); 385 | let listener = NFSTcpListener::bind(&format!("127.0.0.1:{HOSTPORT}"), DemoFS::default()) 386 | .await 387 | .unwrap(); 388 | listener.handle_forever().await.unwrap(); 389 | } 390 | // Test with 391 | // mount -t nfs -o nolocks,vers=3,tcp,port=12000,mountport=12000,soft 127.0.0.1:/ mnt/ 392 | -------------------------------------------------------------------------------- /examples/mirrorfs.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeSet, HashMap}; 2 | use std::ffi::{OsStr, OsString}; 3 | use std::fs::Metadata; 4 | use std::io::SeekFrom; 5 | use std::ops::Bound; 6 | use std::os::unix::ffi::OsStrExt; 7 | use std::path::PathBuf; 8 | use std::sync::atomic::{AtomicU64, Ordering}; 9 | 10 | use async_trait::async_trait; 11 | use intaglio::osstr::SymbolTable; 12 | use intaglio::Symbol; 13 | use tokio::fs::{File, OpenOptions}; 14 | use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; 15 | use tracing::debug; 16 | 17 | use nfsserve::fs_util::*; 18 | use nfsserve::nfs::*; 19 | use nfsserve::tcp::{NFSTcp, NFSTcpListener}; 20 | use nfsserve::vfs::{DirEntry, NFSFileSystem, ReadDirResult, VFSCapabilities}; 21 | 22 | #[derive(Debug, Clone)] 23 | struct FSEntry { 24 | name: Vec, 25 | fsmeta: fattr3, 26 | /// metadata when building the children list 27 | children_meta: fattr3, 28 | children: Option>, 29 | } 30 | 31 | #[derive(Debug)] 32 | struct FSMap { 33 | root: PathBuf, 34 | next_fileid: AtomicU64, 35 | intern: SymbolTable, 36 | id_to_path: HashMap, 37 | path_to_id: HashMap, fileid3>, 38 | } 39 | 40 | enum RefreshResult { 41 | /// The fileid was deleted 42 | Delete, 43 | /// The fileid needs to be reloaded. mtime has been updated, caches 44 | /// need to be evicted. 45 | Reload, 46 | /// Nothing has changed 47 | Noop, 48 | } 49 | 50 | impl FSMap { 51 | fn new(root: PathBuf) -> FSMap { 52 | // create root entry 53 | let root_entry = FSEntry { 54 | name: Vec::new(), 55 | fsmeta: metadata_to_fattr3(1, &root.metadata().unwrap()), 56 | children_meta: metadata_to_fattr3(1, &root.metadata().unwrap()), 57 | children: None, 58 | }; 59 | FSMap { 60 | root, 61 | next_fileid: AtomicU64::new(1), 62 | intern: SymbolTable::new(), 63 | id_to_path: HashMap::from([(0, root_entry)]), 64 | path_to_id: HashMap::from([(Vec::new(), 0)]), 65 | } 66 | } 67 | async fn sym_to_path(&self, symlist: &[Symbol]) -> PathBuf { 68 | let mut ret = self.root.clone(); 69 | for i in symlist.iter() { 70 | ret.push(self.intern.get(*i).unwrap()); 71 | } 72 | ret 73 | } 74 | 75 | async fn sym_to_fname(&self, symlist: &[Symbol]) -> OsString { 76 | if let Some(x) = symlist.last() { 77 | self.intern.get(*x).unwrap().into() 78 | } else { 79 | "".into() 80 | } 81 | } 82 | 83 | fn collect_all_children(&self, id: fileid3, ret: &mut Vec) { 84 | ret.push(id); 85 | if let Some(entry) = self.id_to_path.get(&id) { 86 | if let Some(ref ch) = entry.children { 87 | for i in ch.iter() { 88 | self.collect_all_children(*i, ret); 89 | } 90 | } 91 | } 92 | } 93 | 94 | fn delete_entry(&mut self, id: fileid3) { 95 | let mut children = Vec::new(); 96 | self.collect_all_children(id, &mut children); 97 | for i in children.iter() { 98 | if let Some(ent) = self.id_to_path.remove(i) { 99 | self.path_to_id.remove(&ent.name); 100 | } 101 | } 102 | } 103 | 104 | fn find_entry(&self, id: fileid3) -> Result { 105 | Ok(self 106 | .id_to_path 107 | .get(&id) 108 | .ok_or(nfsstat3::NFS3ERR_NOENT)? 109 | .clone()) 110 | } 111 | fn find_entry_mut(&mut self, id: fileid3) -> Result<&mut FSEntry, nfsstat3> { 112 | self.id_to_path.get_mut(&id).ok_or(nfsstat3::NFS3ERR_NOENT) 113 | } 114 | async fn find_child(&self, id: fileid3, filename: &[u8]) -> Result { 115 | let mut name = self 116 | .id_to_path 117 | .get(&id) 118 | .ok_or(nfsstat3::NFS3ERR_NOENT)? 119 | .name 120 | .clone(); 121 | name.push( 122 | self.intern 123 | .check_interned(OsStr::from_bytes(filename)) 124 | .ok_or(nfsstat3::NFS3ERR_NOENT)?, 125 | ); 126 | Ok(*self.path_to_id.get(&name).ok_or(nfsstat3::NFS3ERR_NOENT)?) 127 | } 128 | async fn refresh_entry(&mut self, id: fileid3) -> Result { 129 | let entry = self 130 | .id_to_path 131 | .get(&id) 132 | .ok_or(nfsstat3::NFS3ERR_NOENT)? 133 | .clone(); 134 | let path = self.sym_to_path(&entry.name).await; 135 | // 136 | if !exists_no_traverse(&path) { 137 | self.delete_entry(id); 138 | debug!("Deleting entry A {:?}: {:?}. Ent: {:?}", id, path, entry); 139 | return Ok(RefreshResult::Delete); 140 | } 141 | 142 | let meta = tokio::fs::symlink_metadata(&path) 143 | .await 144 | .map_err(|_| nfsstat3::NFS3ERR_IO)?; 145 | let meta = metadata_to_fattr3(id, &meta); 146 | if !fattr3_differ(&meta, &entry.fsmeta) { 147 | return Ok(RefreshResult::Noop); 148 | } 149 | // If we get here we have modifications 150 | if entry.fsmeta.ftype as u32 != meta.ftype as u32 { 151 | // if the file type changed ex: file->dir or dir->file 152 | // really the entire file has been replaced. 153 | // we expire the entire id 154 | debug!( 155 | "File Type Mismatch FT {:?} : {:?} vs {:?}", 156 | id, entry.fsmeta.ftype, meta.ftype 157 | ); 158 | debug!( 159 | "File Type Mismatch META {:?} : {:?} vs {:?}", 160 | id, entry.fsmeta, meta 161 | ); 162 | self.delete_entry(id); 163 | debug!("Deleting entry B {:?}: {:?}. Ent: {:?}", id, path, entry); 164 | return Ok(RefreshResult::Delete); 165 | } 166 | // inplace modification. 167 | // update metadata 168 | self.id_to_path.get_mut(&id).unwrap().fsmeta = meta; 169 | debug!("Reloading entry {:?}: {:?}. Ent: {:?}", id, path, entry); 170 | Ok(RefreshResult::Reload) 171 | } 172 | async fn refresh_dir_list(&mut self, id: fileid3) -> Result<(), nfsstat3> { 173 | let entry = self 174 | .id_to_path 175 | .get(&id) 176 | .ok_or(nfsstat3::NFS3ERR_NOENT)? 177 | .clone(); 178 | // if there are children and the metadata did not change 179 | if entry.children.is_some() && !fattr3_differ(&entry.children_meta, &entry.fsmeta) { 180 | return Ok(()); 181 | } 182 | if !matches!(entry.fsmeta.ftype, ftype3::NF3DIR) { 183 | return Ok(()); 184 | } 185 | let mut cur_path = entry.name.clone(); 186 | let path = self.sym_to_path(&entry.name).await; 187 | let mut new_children: Vec = Vec::new(); 188 | debug!("Relisting entry {:?}: {:?}. Ent: {:?}", id, path, entry); 189 | if let Ok(mut listing) = tokio::fs::read_dir(&path).await { 190 | while let Some(entry) = listing 191 | .next_entry() 192 | .await 193 | .map_err(|_| nfsstat3::NFS3ERR_IO)? 194 | { 195 | let sym = self.intern.intern(entry.file_name()).unwrap(); 196 | cur_path.push(sym); 197 | let meta = entry.metadata().await.unwrap(); 198 | let next_id = self.create_entry(&cur_path, meta).await; 199 | new_children.push(next_id); 200 | cur_path.pop(); 201 | } 202 | self.id_to_path 203 | .get_mut(&id) 204 | .ok_or(nfsstat3::NFS3ERR_NOENT)? 205 | .children = Some(BTreeSet::from_iter(new_children.into_iter())); 206 | } 207 | 208 | Ok(()) 209 | } 210 | 211 | async fn create_entry(&mut self, fullpath: &Vec, meta: Metadata) -> fileid3 { 212 | let next_id = if let Some(chid) = self.path_to_id.get(fullpath) { 213 | if let Some(chent) = self.id_to_path.get_mut(chid) { 214 | chent.fsmeta = metadata_to_fattr3(*chid, &meta); 215 | } 216 | *chid 217 | } else { 218 | // path does not exist 219 | let next_id = self.next_fileid.fetch_add(1, Ordering::Relaxed); 220 | let metafattr = metadata_to_fattr3(next_id, &meta); 221 | let new_entry = FSEntry { 222 | name: fullpath.clone(), 223 | fsmeta: metafattr, 224 | children_meta: metafattr, 225 | children: None, 226 | }; 227 | debug!("creating new entry {:?}: {:?}", next_id, meta); 228 | self.id_to_path.insert(next_id, new_entry); 229 | self.path_to_id.insert(fullpath.clone(), next_id); 230 | next_id 231 | }; 232 | next_id 233 | } 234 | } 235 | #[derive(Debug)] 236 | pub struct MirrorFS { 237 | fsmap: tokio::sync::Mutex, 238 | } 239 | 240 | /// Enumeration for the create_fs_object method 241 | enum CreateFSObject { 242 | /// Creates a directory 243 | Directory, 244 | /// Creates a file with a set of attributes 245 | File(sattr3), 246 | /// Creates an exclusive file with a set of attributes 247 | Exclusive, 248 | /// Creates a symlink with a set of attributes to a target location 249 | Symlink((sattr3, nfspath3)), 250 | } 251 | impl MirrorFS { 252 | pub fn new(root: PathBuf) -> MirrorFS { 253 | MirrorFS { 254 | fsmap: tokio::sync::Mutex::new(FSMap::new(root)), 255 | } 256 | } 257 | 258 | /// creates a FS object in a given directory and of a given type 259 | /// Updates as much metadata as we can in-place 260 | async fn create_fs_object( 261 | &self, 262 | dirid: fileid3, 263 | objectname: &filename3, 264 | object: &CreateFSObject, 265 | ) -> Result<(fileid3, fattr3), nfsstat3> { 266 | let mut fsmap = self.fsmap.lock().await; 267 | let ent = fsmap.find_entry(dirid)?; 268 | let mut path = fsmap.sym_to_path(&ent.name).await; 269 | let objectname_osstr = OsStr::from_bytes(objectname).to_os_string(); 270 | path.push(&objectname_osstr); 271 | 272 | match object { 273 | CreateFSObject::Directory => { 274 | debug!("mkdir {:?}", path); 275 | if exists_no_traverse(&path) { 276 | return Err(nfsstat3::NFS3ERR_EXIST); 277 | } 278 | tokio::fs::create_dir(&path) 279 | .await 280 | .map_err(|_| nfsstat3::NFS3ERR_IO)?; 281 | } 282 | CreateFSObject::File(setattr) => { 283 | debug!("create {:?}", path); 284 | let file = std::fs::File::create(&path).map_err(|_| nfsstat3::NFS3ERR_IO)?; 285 | let _ = file_setattr(&file, setattr).await; 286 | } 287 | CreateFSObject::Exclusive => { 288 | debug!("create exclusive {:?}", path); 289 | let _ = std::fs::File::options() 290 | .write(true) 291 | .create_new(true) 292 | .open(&path) 293 | .map_err(|_| nfsstat3::NFS3ERR_EXIST)?; 294 | } 295 | CreateFSObject::Symlink((_, target)) => { 296 | debug!("symlink {:?} {:?}", path, target); 297 | if exists_no_traverse(&path) { 298 | return Err(nfsstat3::NFS3ERR_EXIST); 299 | } 300 | tokio::fs::symlink(OsStr::from_bytes(target), &path) 301 | .await 302 | .map_err(|_| nfsstat3::NFS3ERR_IO)?; 303 | // we do not set attributes on symlinks 304 | } 305 | } 306 | 307 | let _ = fsmap.refresh_entry(dirid).await; 308 | 309 | let sym = fsmap.intern.intern(objectname_osstr).unwrap(); 310 | let mut name = ent.name.clone(); 311 | name.push(sym); 312 | let meta = path.symlink_metadata().map_err(|_| nfsstat3::NFS3ERR_IO)?; 313 | let fileid = fsmap.create_entry(&name, meta.clone()).await; 314 | 315 | // update the children list 316 | if let Some(ref mut children) = fsmap 317 | .id_to_path 318 | .get_mut(&dirid) 319 | .ok_or(nfsstat3::NFS3ERR_NOENT)? 320 | .children 321 | { 322 | children.insert(fileid); 323 | } 324 | Ok((fileid, metadata_to_fattr3(fileid, &meta))) 325 | } 326 | } 327 | 328 | #[async_trait] 329 | impl NFSFileSystem for MirrorFS { 330 | fn root_dir(&self) -> fileid3 { 331 | 0 332 | } 333 | fn capabilities(&self) -> VFSCapabilities { 334 | VFSCapabilities::ReadWrite 335 | } 336 | 337 | async fn lookup(&self, dirid: fileid3, filename: &filename3) -> Result { 338 | let mut fsmap = self.fsmap.lock().await; 339 | if let Ok(id) = fsmap.find_child(dirid, filename).await { 340 | if fsmap.id_to_path.contains_key(&id) { 341 | return Ok(id); 342 | } 343 | } 344 | // Optimize for negative lookups. 345 | // See if the file actually exists on the filesystem 346 | let dirent = fsmap.find_entry(dirid)?; 347 | let mut path = fsmap.sym_to_path(&dirent.name).await; 348 | let objectname_osstr = OsStr::from_bytes(filename).to_os_string(); 349 | path.push(&objectname_osstr); 350 | if !exists_no_traverse(&path) { 351 | return Err(nfsstat3::NFS3ERR_NOENT); 352 | } 353 | // ok the file actually exists. 354 | // that means something changed under me probably. 355 | // refresh. 356 | 357 | if let RefreshResult::Delete = fsmap.refresh_entry(dirid).await? { 358 | return Err(nfsstat3::NFS3ERR_NOENT); 359 | } 360 | let _ = fsmap.refresh_dir_list(dirid).await; 361 | 362 | fsmap.find_child(dirid, filename).await 363 | //debug!("lookup({:?}, {:?})", dirid, filename); 364 | 365 | //debug!(" -- lookup result {:?}", res); 366 | } 367 | 368 | async fn getattr(&self, id: fileid3) -> Result { 369 | //debug!("Stat query {:?}", id); 370 | let mut fsmap = self.fsmap.lock().await; 371 | if let RefreshResult::Delete = fsmap.refresh_entry(id).await? { 372 | return Err(nfsstat3::NFS3ERR_NOENT); 373 | } 374 | let ent = fsmap.find_entry(id)?; 375 | let path = fsmap.sym_to_path(&ent.name).await; 376 | debug!("Stat {:?}: {:?}", path, ent); 377 | Ok(ent.fsmeta) 378 | } 379 | 380 | async fn read( 381 | &self, 382 | id: fileid3, 383 | offset: u64, 384 | count: u32, 385 | ) -> Result<(Vec, bool), nfsstat3> { 386 | let fsmap = self.fsmap.lock().await; 387 | let ent = fsmap.find_entry(id)?; 388 | let path = fsmap.sym_to_path(&ent.name).await; 389 | drop(fsmap); 390 | let mut f = File::open(&path).await.or(Err(nfsstat3::NFS3ERR_NOENT))?; 391 | let len = f.metadata().await.or(Err(nfsstat3::NFS3ERR_NOENT))?.len(); 392 | let mut start = offset; 393 | let mut end = offset + count as u64; 394 | let eof = end >= len; 395 | if start >= len { 396 | start = len; 397 | } 398 | if end > len { 399 | end = len; 400 | } 401 | f.seek(SeekFrom::Start(start)) 402 | .await 403 | .or(Err(nfsstat3::NFS3ERR_IO))?; 404 | let mut buf = vec![0; (end - start) as usize]; 405 | f.read_exact(&mut buf).await.or(Err(nfsstat3::NFS3ERR_IO))?; 406 | Ok((buf, eof)) 407 | } 408 | 409 | async fn readdir( 410 | &self, 411 | dirid: fileid3, 412 | start_after: fileid3, 413 | max_entries: usize, 414 | ) -> Result { 415 | let mut fsmap = self.fsmap.lock().await; 416 | fsmap.refresh_entry(dirid).await?; 417 | fsmap.refresh_dir_list(dirid).await?; 418 | 419 | let entry = fsmap.find_entry(dirid)?; 420 | if !matches!(entry.fsmeta.ftype, ftype3::NF3DIR) { 421 | return Err(nfsstat3::NFS3ERR_NOTDIR); 422 | } 423 | debug!("readdir({:?}, {:?})", entry, start_after); 424 | // we must have children here 425 | let children = entry.children.ok_or(nfsstat3::NFS3ERR_IO)?; 426 | 427 | let mut ret = ReadDirResult { 428 | entries: Vec::new(), 429 | end: false, 430 | }; 431 | 432 | let range_start = if start_after > 0 { 433 | Bound::Excluded(start_after) 434 | } else { 435 | Bound::Unbounded 436 | }; 437 | 438 | let remaining_length = children.range((range_start, Bound::Unbounded)).count(); 439 | let path = fsmap.sym_to_path(&entry.name).await; 440 | debug!("path: {:?}", path); 441 | debug!("children len: {:?}", children.len()); 442 | debug!("remaining_len : {:?}", remaining_length); 443 | for i in children.range((range_start, Bound::Unbounded)) { 444 | let fileid = *i; 445 | let fileent = fsmap.find_entry(fileid)?; 446 | let name = fsmap.sym_to_fname(&fileent.name).await; 447 | debug!("\t --- {:?} {:?}", fileid, name); 448 | ret.entries.push(DirEntry { 449 | fileid, 450 | name: name.as_bytes().into(), 451 | attr: fileent.fsmeta, 452 | }); 453 | if ret.entries.len() >= max_entries { 454 | break; 455 | } 456 | } 457 | if ret.entries.len() == remaining_length { 458 | ret.end = true; 459 | } 460 | debug!("readdir_result:{:?}", ret); 461 | 462 | Ok(ret) 463 | } 464 | 465 | async fn setattr(&self, id: fileid3, setattr: sattr3) -> Result { 466 | let mut fsmap = self.fsmap.lock().await; 467 | let entry = fsmap.find_entry(id)?; 468 | let path = fsmap.sym_to_path(&entry.name).await; 469 | path_setattr(&path, &setattr).await?; 470 | 471 | // I have to lookup a second time to update 472 | let metadata = path.symlink_metadata().or(Err(nfsstat3::NFS3ERR_IO))?; 473 | if let Ok(entry) = fsmap.find_entry_mut(id) { 474 | entry.fsmeta = metadata_to_fattr3(id, &metadata); 475 | } 476 | Ok(metadata_to_fattr3(id, &metadata)) 477 | } 478 | async fn write(&self, id: fileid3, offset: u64, data: &[u8]) -> Result { 479 | let fsmap = self.fsmap.lock().await; 480 | let ent = fsmap.find_entry(id)?; 481 | let path = fsmap.sym_to_path(&ent.name).await; 482 | drop(fsmap); 483 | debug!("write to init {:?}", path); 484 | let mut f = OpenOptions::new() 485 | .write(true) 486 | .create(true) 487 | .truncate(false) 488 | .open(&path) 489 | .await 490 | .map_err(|e| { 491 | debug!("Unable to open {:?}", e); 492 | nfsstat3::NFS3ERR_IO 493 | })?; 494 | f.seek(SeekFrom::Start(offset)).await.map_err(|e| { 495 | debug!("Unable to seek {:?}", e); 496 | nfsstat3::NFS3ERR_IO 497 | })?; 498 | f.write_all(data).await.map_err(|e| { 499 | debug!("Unable to write {:?}", e); 500 | nfsstat3::NFS3ERR_IO 501 | })?; 502 | debug!("write to {:?} {:?} {:?}", path, offset, data.len()); 503 | let _ = f.flush().await; 504 | let _ = f.sync_all().await; 505 | let meta = f.metadata().await.or(Err(nfsstat3::NFS3ERR_IO))?; 506 | Ok(metadata_to_fattr3(id, &meta)) 507 | } 508 | 509 | async fn create( 510 | &self, 511 | dirid: fileid3, 512 | filename: &filename3, 513 | setattr: sattr3, 514 | ) -> Result<(fileid3, fattr3), nfsstat3> { 515 | self.create_fs_object(dirid, filename, &CreateFSObject::File(setattr)) 516 | .await 517 | } 518 | 519 | async fn create_exclusive( 520 | &self, 521 | dirid: fileid3, 522 | filename: &filename3, 523 | ) -> Result { 524 | Ok(self 525 | .create_fs_object(dirid, filename, &CreateFSObject::Exclusive) 526 | .await? 527 | .0) 528 | } 529 | 530 | async fn remove(&self, dirid: fileid3, filename: &filename3) -> Result<(), nfsstat3> { 531 | let mut fsmap = self.fsmap.lock().await; 532 | let ent = fsmap.find_entry(dirid)?; 533 | let mut path = fsmap.sym_to_path(&ent.name).await; 534 | path.push(OsStr::from_bytes(filename)); 535 | if let Ok(meta) = path.symlink_metadata() { 536 | if meta.is_dir() { 537 | tokio::fs::remove_dir(&path) 538 | .await 539 | .map_err(|_| nfsstat3::NFS3ERR_IO)?; 540 | } else { 541 | tokio::fs::remove_file(&path) 542 | .await 543 | .map_err(|_| nfsstat3::NFS3ERR_IO)?; 544 | } 545 | 546 | let filesym = fsmap 547 | .intern 548 | .intern(OsStr::from_bytes(filename).to_os_string()) 549 | .unwrap(); 550 | let mut sympath = ent.name.clone(); 551 | sympath.push(filesym); 552 | if let Some(fileid) = fsmap.path_to_id.get(&sympath).copied() { 553 | // update the fileid -> path 554 | // and the path -> fileid mappings for the deleted file 555 | fsmap.id_to_path.remove(&fileid); 556 | fsmap.path_to_id.remove(&sympath); 557 | // we need to update the children listing for the directories 558 | if let Ok(dirent_mut) = fsmap.find_entry_mut(dirid) { 559 | if let Some(ref mut fromch) = dirent_mut.children { 560 | fromch.remove(&fileid); 561 | } 562 | } 563 | } 564 | 565 | let _ = fsmap.refresh_entry(dirid).await; 566 | } else { 567 | return Err(nfsstat3::NFS3ERR_NOENT); 568 | } 569 | 570 | Ok(()) 571 | } 572 | 573 | async fn rename( 574 | &self, 575 | from_dirid: fileid3, 576 | from_filename: &filename3, 577 | to_dirid: fileid3, 578 | to_filename: &filename3, 579 | ) -> Result<(), nfsstat3> { 580 | let mut fsmap = self.fsmap.lock().await; 581 | 582 | let from_dirent = fsmap.find_entry(from_dirid)?; 583 | let mut from_path = fsmap.sym_to_path(&from_dirent.name).await; 584 | from_path.push(OsStr::from_bytes(from_filename)); 585 | 586 | let to_dirent = fsmap.find_entry(to_dirid)?; 587 | let mut to_path = fsmap.sym_to_path(&to_dirent.name).await; 588 | // to folder must exist 589 | if !exists_no_traverse(&to_path) { 590 | return Err(nfsstat3::NFS3ERR_NOENT); 591 | } 592 | to_path.push(OsStr::from_bytes(to_filename)); 593 | 594 | // src path must exist 595 | if !exists_no_traverse(&from_path) { 596 | return Err(nfsstat3::NFS3ERR_NOENT); 597 | } 598 | debug!("Rename {:?} to {:?}", from_path, to_path); 599 | tokio::fs::rename(&from_path, &to_path) 600 | .await 601 | .map_err(|_| nfsstat3::NFS3ERR_IO)?; 602 | 603 | let oldsym = fsmap 604 | .intern 605 | .intern(OsStr::from_bytes(from_filename).to_os_string()) 606 | .unwrap(); 607 | let newsym = fsmap 608 | .intern 609 | .intern(OsStr::from_bytes(to_filename).to_os_string()) 610 | .unwrap(); 611 | 612 | let mut from_sympath = from_dirent.name.clone(); 613 | from_sympath.push(oldsym); 614 | let mut to_sympath = to_dirent.name.clone(); 615 | to_sympath.push(newsym); 616 | if let Some(fileid) = fsmap.path_to_id.get(&from_sympath).copied() { 617 | // update the fileid -> path 618 | // and the path -> fileid mappings for the new file 619 | fsmap.id_to_path.get_mut(&fileid).unwrap().name = to_sympath.clone(); 620 | fsmap.path_to_id.remove(&from_sympath); 621 | fsmap.path_to_id.insert(to_sympath, fileid); 622 | if to_dirid != from_dirid { 623 | // moving across directories. 624 | // we need to update the children listing for the directories 625 | if let Ok(from_dirent_mut) = fsmap.find_entry_mut(from_dirid) { 626 | if let Some(ref mut fromch) = from_dirent_mut.children { 627 | fromch.remove(&fileid); 628 | } 629 | } 630 | if let Ok(to_dirent_mut) = fsmap.find_entry_mut(to_dirid) { 631 | if let Some(ref mut toch) = to_dirent_mut.children { 632 | toch.insert(fileid); 633 | } 634 | } 635 | } 636 | } 637 | let _ = fsmap.refresh_entry(from_dirid).await; 638 | if to_dirid != from_dirid { 639 | let _ = fsmap.refresh_entry(to_dirid).await; 640 | } 641 | 642 | Ok(()) 643 | } 644 | async fn mkdir( 645 | &self, 646 | dirid: fileid3, 647 | dirname: &filename3, 648 | ) -> Result<(fileid3, fattr3), nfsstat3> { 649 | self.create_fs_object(dirid, dirname, &CreateFSObject::Directory) 650 | .await 651 | } 652 | 653 | async fn symlink( 654 | &self, 655 | dirid: fileid3, 656 | linkname: &filename3, 657 | symlink: &nfspath3, 658 | attr: &sattr3, 659 | ) -> Result<(fileid3, fattr3), nfsstat3> { 660 | self.create_fs_object( 661 | dirid, 662 | linkname, 663 | &CreateFSObject::Symlink((*attr, symlink.clone())), 664 | ) 665 | .await 666 | } 667 | async fn readlink(&self, id: fileid3) -> Result { 668 | let fsmap = self.fsmap.lock().await; 669 | let ent = fsmap.find_entry(id)?; 670 | let path = fsmap.sym_to_path(&ent.name).await; 671 | drop(fsmap); 672 | if path.is_symlink() { 673 | if let Ok(target) = path.read_link() { 674 | Ok(target.as_os_str().as_bytes().into()) 675 | } else { 676 | Err(nfsstat3::NFS3ERR_IO) 677 | } 678 | } else { 679 | Err(nfsstat3::NFS3ERR_BADTYPE) 680 | } 681 | } 682 | } 683 | 684 | const HOSTPORT: u32 = 11111; 685 | 686 | #[tokio::main] 687 | async fn main() { 688 | tracing_subscriber::fmt() 689 | .with_max_level(tracing::Level::DEBUG) 690 | .with_writer(std::io::stderr) 691 | .init(); 692 | 693 | let path = std::env::args() 694 | .nth(1) 695 | .expect("must supply directory to mirror"); 696 | let path = PathBuf::from(path); 697 | 698 | let fs = MirrorFS::new(path); 699 | let listener = NFSTcpListener::bind(&format!("127.0.0.1:{HOSTPORT}"), fs) 700 | .await 701 | .unwrap(); 702 | listener.handle_forever().await.unwrap(); 703 | } 704 | // Test with 705 | // mount -t nfs -o nolocks,vers=3,tcp,port=12000,mountport=12000,soft 127.0.0.1:/ mnt/ 706 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::vfs::NFSFileSystem; 2 | use std::fmt; 3 | use std::sync::Arc; 4 | use tokio::sync::mpsc; 5 | use crate::transaction_tracker::TransactionTracker; 6 | 7 | #[derive(Clone)] 8 | pub struct RPCContext { 9 | pub local_port: u16, 10 | pub client_addr: String, 11 | pub auth: crate::rpc::auth_unix, 12 | pub vfs: Arc, 13 | pub mount_signal: Option>, 14 | pub export_name: Arc, 15 | pub transaction_tracker: Arc, 16 | } 17 | 18 | impl fmt::Debug for RPCContext { 19 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 20 | f.debug_struct("RPCContext") 21 | .field("local_port", &self.local_port) 22 | .field("client_addr", &self.client_addr) 23 | .field("auth", &self.auth) 24 | .finish() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/fs_util.rs: -------------------------------------------------------------------------------- 1 | use crate::nfs::*; 2 | use std::fs::Metadata; 3 | use std::fs::Permissions; 4 | 5 | #[cfg(unix)] 6 | use std::os::unix::fs::{MetadataExt, PermissionsExt}; 7 | use std::path::Path; 8 | use tokio::fs::OpenOptions; 9 | use tracing::debug; 10 | 11 | /// Compares if file metadata has changed in a significant way 12 | #[cfg(any(target_os = "linux", target_os = "macos"))] 13 | pub fn metadata_differ(lhs: &Metadata, rhs: &Metadata) -> bool { 14 | lhs.ino() != rhs.ino() 15 | || lhs.mtime() != rhs.mtime() 16 | || lhs.len() != rhs.len() 17 | || lhs.file_type() != rhs.file_type() 18 | } 19 | pub fn fattr3_differ(lhs: &fattr3, rhs: &fattr3) -> bool { 20 | lhs.fileid != rhs.fileid 21 | || lhs.mtime.seconds != rhs.mtime.seconds 22 | || lhs.mtime.nseconds != rhs.mtime.nseconds 23 | || lhs.size != rhs.size 24 | || lhs.ftype as u32 != rhs.ftype as u32 25 | } 26 | 27 | /// path.exists() is terrifyingly unsafe as that 28 | /// traverses symlinks. This can cause deadlocks if we have a 29 | /// recursive symlink. 30 | pub fn exists_no_traverse(path: &Path) -> bool { 31 | path.symlink_metadata().is_ok() 32 | } 33 | 34 | fn mode_unmask(mode: u32) -> u32 { 35 | // it is possible to create a file we cannot write to. 36 | // we force writable always. 37 | let mode = mode | 0x80; 38 | let mode = Permissions::from_mode(mode); 39 | mode.mode() & 0x1FF 40 | } 41 | 42 | /// Converts fs Metadata to NFS fattr3 43 | pub fn metadata_to_fattr3(fid: fileid3, meta: &Metadata) -> fattr3 { 44 | let size = meta.size(); 45 | let file_mode = mode_unmask(meta.mode()); 46 | if meta.is_file() { 47 | fattr3 { 48 | ftype: ftype3::NF3REG, 49 | mode: file_mode, 50 | nlink: 1, 51 | uid: meta.uid(), 52 | gid: meta.gid(), 53 | size, 54 | used: size, 55 | rdev: specdata3::default(), 56 | fsid: 0, 57 | fileid: fid, 58 | atime: nfstime3 { 59 | seconds: meta.atime() as u32, 60 | nseconds: meta.atime_nsec() as u32, 61 | }, 62 | mtime: nfstime3 { 63 | seconds: meta.mtime() as u32, 64 | nseconds: meta.mtime_nsec() as u32, 65 | }, 66 | ctime: nfstime3 { 67 | seconds: meta.ctime() as u32, 68 | nseconds: meta.ctime_nsec() as u32, 69 | }, 70 | } 71 | } else if meta.is_symlink() { 72 | fattr3 { 73 | ftype: ftype3::NF3LNK, 74 | mode: file_mode, 75 | nlink: 1, 76 | uid: meta.uid(), 77 | gid: meta.gid(), 78 | size, 79 | used: size, 80 | rdev: specdata3::default(), 81 | fsid: 0, 82 | fileid: fid, 83 | atime: nfstime3 { 84 | seconds: meta.atime() as u32, 85 | nseconds: meta.atime_nsec() as u32, 86 | }, 87 | mtime: nfstime3 { 88 | seconds: meta.mtime() as u32, 89 | nseconds: meta.mtime_nsec() as u32, 90 | }, 91 | ctime: nfstime3 { 92 | seconds: meta.ctime() as u32, 93 | nseconds: meta.ctime_nsec() as u32, 94 | }, 95 | } 96 | } else { 97 | fattr3 { 98 | ftype: ftype3::NF3DIR, 99 | mode: file_mode, 100 | nlink: 2, 101 | uid: meta.uid(), 102 | gid: meta.gid(), 103 | size, 104 | used: size, 105 | rdev: specdata3::default(), 106 | fsid: 0, 107 | fileid: fid, 108 | atime: nfstime3 { 109 | seconds: meta.atime() as u32, 110 | nseconds: meta.atime_nsec() as u32, 111 | }, 112 | mtime: nfstime3 { 113 | seconds: meta.mtime() as u32, 114 | nseconds: meta.mtime_nsec() as u32, 115 | }, 116 | ctime: nfstime3 { 117 | seconds: meta.ctime() as u32, 118 | nseconds: meta.ctime_nsec() as u32, 119 | }, 120 | } 121 | } 122 | } 123 | 124 | /// Set attributes of a path 125 | pub async fn path_setattr(path: &Path, setattr: &sattr3) -> Result<(), nfsstat3> { 126 | match setattr.atime { 127 | set_atime::SET_TO_SERVER_TIME => { 128 | let _ = filetime::set_file_atime(path, filetime::FileTime::now()); 129 | } 130 | set_atime::SET_TO_CLIENT_TIME(time) => { 131 | let _ = filetime::set_file_atime(path, time.into()); 132 | } 133 | _ => {} 134 | }; 135 | match setattr.mtime { 136 | set_mtime::SET_TO_SERVER_TIME => { 137 | let _ = filetime::set_file_mtime(path, filetime::FileTime::now()); 138 | } 139 | set_mtime::SET_TO_CLIENT_TIME(time) => { 140 | let _ = filetime::set_file_mtime(path, time.into()); 141 | } 142 | _ => {} 143 | }; 144 | if let set_mode3::mode(mode) = setattr.mode { 145 | debug!(" -- set permissions {:?} {:?}", path, mode); 146 | let mode = mode_unmask(mode); 147 | let _ = std::fs::set_permissions(path, Permissions::from_mode(mode)); 148 | }; 149 | if let set_uid3::uid(_) = setattr.uid { 150 | debug!("Set uid not implemented"); 151 | } 152 | if let set_gid3::gid(_) = setattr.gid { 153 | debug!("Set gid not implemented"); 154 | } 155 | if let set_size3::size(size3) = setattr.size { 156 | let file = OpenOptions::new() 157 | .read(true) 158 | .write(true) 159 | .truncate(false) 160 | .open(path) 161 | .await 162 | .or(Err(nfsstat3::NFS3ERR_IO))?; 163 | debug!(" -- set size {:?} {:?}", path, size3); 164 | file.set_len(size3).await.or(Err(nfsstat3::NFS3ERR_IO))?; 165 | } 166 | Ok(()) 167 | } 168 | 169 | /// Set attributes of a file 170 | pub async fn file_setattr(file: &std::fs::File, setattr: &sattr3) -> Result<(), nfsstat3> { 171 | if let set_mode3::mode(mode) = setattr.mode { 172 | debug!(" -- set permissions {:?}", mode); 173 | let mode = mode_unmask(mode); 174 | let _ = file.set_permissions(Permissions::from_mode(mode)); 175 | } 176 | if let set_size3::size(size3) = setattr.size { 177 | debug!(" -- set size {:?}", size3); 178 | file.set_len(size3).or(Err(nfsstat3::NFS3ERR_IO))?; 179 | } 180 | Ok(()) 181 | } 182 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "strict", deny(warnings))] 2 | 3 | mod context; 4 | mod rpc; 5 | mod rpcwire; 6 | mod write_counter; 7 | pub mod xdr; 8 | 9 | mod mount; 10 | mod mount_handlers; 11 | 12 | mod portmap; 13 | mod portmap_handlers; 14 | 15 | pub mod nfs; 16 | mod nfs_handlers; 17 | 18 | #[cfg(not(target_os = "windows"))] 19 | pub mod fs_util; 20 | 21 | pub mod tcp; 22 | pub mod vfs; 23 | mod transaction_tracker; 24 | -------------------------------------------------------------------------------- /src/mount.rs: -------------------------------------------------------------------------------- 1 | // this is just a complete enumeration of everything in the RFC 2 | #![allow(dead_code)] 3 | // And its nice to keep the original RFC names and case 4 | #![allow(non_camel_case_types)] 5 | 6 | use crate::xdr::*; 7 | use byteorder::{ReadBytesExt, WriteBytesExt}; 8 | use num_derive::{FromPrimitive, ToPrimitive}; 9 | use num_traits::cast::FromPrimitive; 10 | use std::io::{Read, Write}; 11 | // Transcribed from RFC 1057 Appendix A 12 | 13 | pub const PROGRAM: u32 = 100005; 14 | pub const VERSION: u32 = 3; 15 | 16 | pub const MNTPATHLEN: u32 = 1024; /* Maximum bytes in a path name */ 17 | pub const MNTNAMLEN: u32 = 255; /* Maximum bytes in a name */ 18 | pub const FHSIZE3: u32 = 64; /* Maximum bytes in a V3 file handle */ 19 | 20 | pub type fhandle3 = Vec; 21 | pub type dirpath = Vec; 22 | pub type name = Vec; 23 | 24 | #[allow(non_camel_case_types)] 25 | #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] 26 | #[repr(u32)] 27 | pub enum mountstat3 { 28 | MNT3_OK = 0, /* no error */ 29 | MNT3ERR_PERM = 1, /* Not owner */ 30 | MNT3ERR_NOENT = 2, /* No such file or directory */ 31 | MNT3ERR_IO = 5, /* I/O error */ 32 | MNT3ERR_ACCES = 13, /* Permission denied */ 33 | MNT3ERR_NOTDIR = 20, /* Not a directory */ 34 | MNT3ERR_INVAL = 22, /* Invalid argument */ 35 | MNT3ERR_NAMETOOLONG = 63, /* Filename too long */ 36 | MNT3ERR_NOTSUPP = 10004, /* Operation not supported */ 37 | MNT3ERR_SERVERFAULT = 10006, /* A failure on the server */ 38 | } 39 | XDREnumSerde!(mountstat3); 40 | -------------------------------------------------------------------------------- /src/mount_handlers.rs: -------------------------------------------------------------------------------- 1 | use crate::context::RPCContext; 2 | use crate::mount::*; 3 | use crate::rpc::*; 4 | use crate::xdr::*; 5 | use num_derive::{FromPrimitive, ToPrimitive}; 6 | use num_traits::cast::{FromPrimitive, ToPrimitive}; 7 | use std::io::{Read, Write}; 8 | use tracing::debug; 9 | 10 | /* 11 | From RFC 1813 Appendix I 12 | program MOUNT_PROGRAM { 13 | version MOUNT_V3 { 14 | void MOUNTPROC3_NULL(void) = 0; 15 | mountres3 MOUNTPROC3_MNT(dirpath) = 1; 16 | mountlist MOUNTPROC3_DUMP(void) = 2; 17 | void MOUNTPROC3_UMNT(dirpath) = 3; 18 | void MOUNTPROC3_UMNTALL(void) = 4; 19 | exports MOUNTPROC3_EXPORT(void) = 5; 20 | } = 3; 21 | } = 100005; 22 | */ 23 | 24 | #[allow(non_camel_case_types)] 25 | #[allow(clippy::upper_case_acronyms)] 26 | #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] 27 | enum MountProgram { 28 | MOUNTPROC3_NULL = 0, 29 | MOUNTPROC3_MNT = 1, 30 | MOUNTPROC3_DUMP = 2, 31 | MOUNTPROC3_UMNT = 3, 32 | MOUNTPROC3_UMNTALL = 4, 33 | MOUNTPROC3_EXPORT = 5, 34 | INVALID, 35 | } 36 | 37 | pub async fn handle_mount( 38 | xid: u32, 39 | call: call_body, 40 | input: &mut impl Read, 41 | output: &mut impl Write, 42 | context: &RPCContext, 43 | ) -> Result<(), anyhow::Error> { 44 | let prog = MountProgram::from_u32(call.proc).unwrap_or(MountProgram::INVALID); 45 | 46 | match prog { 47 | MountProgram::MOUNTPROC3_NULL => mountproc3_null(xid, input, output)?, 48 | MountProgram::MOUNTPROC3_MNT => mountproc3_mnt(xid, input, output, context).await?, 49 | MountProgram::MOUNTPROC3_UMNT => mountproc3_umnt(xid, input, output, context).await?, 50 | MountProgram::MOUNTPROC3_UMNTALL => { 51 | mountproc3_umnt_all(xid, input, output, context).await? 52 | } 53 | MountProgram::MOUNTPROC3_EXPORT => mountproc3_export(xid, input, output, context)?, 54 | _ => { 55 | proc_unavail_reply_message(xid).serialize(output)?; 56 | } 57 | } 58 | Ok(()) 59 | } 60 | 61 | pub fn mountproc3_null( 62 | xid: u32, 63 | _: &mut impl Read, 64 | output: &mut impl Write, 65 | ) -> Result<(), anyhow::Error> { 66 | debug!("mountproc3_null({:?}) ", xid); 67 | // build an RPC reply 68 | let msg = make_success_reply(xid); 69 | debug!("\t{:?} --> {:?}", xid, msg); 70 | msg.serialize(output)?; 71 | Ok(()) 72 | } 73 | 74 | #[allow(non_camel_case_types)] 75 | #[derive(Clone, Debug)] 76 | struct mountres3_ok { 77 | fhandle: fhandle3, // really same thing as nfs::nfs_fh3 78 | auth_flavors: Vec, 79 | } 80 | XDRStruct!(mountres3_ok, fhandle, auth_flavors); 81 | 82 | pub async fn mountproc3_mnt( 83 | xid: u32, 84 | input: &mut impl Read, 85 | output: &mut impl Write, 86 | context: &RPCContext, 87 | ) -> Result<(), anyhow::Error> { 88 | let mut path = dirpath::new(); 89 | path.deserialize(input)?; 90 | let utf8path = std::str::from_utf8(&path).unwrap_or_default(); 91 | debug!("mountproc3_mnt({:?},{:?}) ", xid, utf8path); 92 | let path = if let Some(path) = utf8path.strip_prefix(context.export_name.as_str()) { 93 | let path = path 94 | .trim_start_matches('/') 95 | .trim_end_matches('/') 96 | .trim() 97 | .as_bytes(); 98 | let mut new_path = Vec::with_capacity(path.len() + 1); 99 | new_path.push(b'/'); 100 | new_path.extend_from_slice(path); 101 | new_path 102 | } else { 103 | // invalid export 104 | debug!("{:?} --> no matching export", xid); 105 | make_success_reply(xid).serialize(output)?; 106 | mountstat3::MNT3ERR_NOENT.serialize(output)?; 107 | return Ok(()); 108 | }; 109 | if let Ok(fileid) = context.vfs.path_to_id(&path).await { 110 | let response = mountres3_ok { 111 | fhandle: context.vfs.id_to_fh(fileid).data, 112 | auth_flavors: vec![ 113 | auth_flavor::AUTH_NULL.to_u32().unwrap(), 114 | auth_flavor::AUTH_UNIX.to_u32().unwrap(), 115 | ], 116 | }; 117 | debug!("{:?} --> {:?}", xid, response); 118 | if let Some(ref chan) = context.mount_signal { 119 | let _ = chan.send(true).await; 120 | } 121 | make_success_reply(xid).serialize(output)?; 122 | mountstat3::MNT3_OK.serialize(output)?; 123 | response.serialize(output)?; 124 | } else { 125 | debug!("{:?} --> MNT3ERR_NOENT", xid); 126 | make_success_reply(xid).serialize(output)?; 127 | mountstat3::MNT3ERR_NOENT.serialize(output)?; 128 | } 129 | Ok(()) 130 | } 131 | 132 | /* 133 | exports MOUNTPROC3_EXPORT(void) = 5; 134 | 135 | typedef struct groupnode *groups; 136 | 137 | struct groupnode { 138 | name gr_name; 139 | groups gr_next; 140 | }; 141 | 142 | typedef struct exportnode *exports; 143 | 144 | struct exportnode { 145 | dirpath ex_dir; 146 | groups ex_groups; 147 | exports ex_next; 148 | }; 149 | 150 | DESCRIPTION 151 | 152 | Procedure EXPORT returns a list of all the exported file 153 | systems and which clients are allowed to mount each one. 154 | The names in the group list are implementation-specific 155 | and cannot be directly interpreted by clients. These names 156 | can represent hosts or groups of hosts. 157 | 158 | IMPLEMENTATION 159 | 160 | This procedure generally returns the contents of a list of 161 | shared or exported file systems. These are the file 162 | systems which are made available to NFS version 3 protocol 163 | clients. 164 | */ 165 | 166 | pub fn mountproc3_export( 167 | xid: u32, 168 | _: &mut impl Read, 169 | output: &mut impl Write, 170 | context: &RPCContext, 171 | ) -> Result<(), anyhow::Error> { 172 | debug!("mountproc3_export({:?}) ", xid); 173 | make_success_reply(xid).serialize(output)?; 174 | true.serialize(output)?; 175 | // dirpath 176 | context.export_name.as_bytes().to_vec().serialize(output)?; 177 | // groups 178 | false.serialize(output)?; 179 | // next exports 180 | false.serialize(output)?; 181 | Ok(()) 182 | } 183 | 184 | pub async fn mountproc3_umnt( 185 | xid: u32, 186 | input: &mut impl Read, 187 | output: &mut impl Write, 188 | context: &RPCContext, 189 | ) -> Result<(), anyhow::Error> { 190 | let mut path = dirpath::new(); 191 | path.deserialize(input)?; 192 | let utf8path = std::str::from_utf8(&path).unwrap_or_default(); 193 | debug!("mountproc3_umnt({:?},{:?}) ", xid, utf8path); 194 | if let Some(ref chan) = context.mount_signal { 195 | let _ = chan.send(false).await; 196 | } 197 | make_success_reply(xid).serialize(output)?; 198 | mountstat3::MNT3_OK.serialize(output)?; 199 | Ok(()) 200 | } 201 | 202 | pub async fn mountproc3_umnt_all( 203 | xid: u32, 204 | _input: &mut impl Read, 205 | output: &mut impl Write, 206 | context: &RPCContext, 207 | ) -> Result<(), anyhow::Error> { 208 | debug!("mountproc3_umnt_all({:?}) ", xid); 209 | if let Some(ref chan) = context.mount_signal { 210 | let _ = chan.send(false).await; 211 | } 212 | make_success_reply(xid).serialize(output)?; 213 | mountstat3::MNT3_OK.serialize(output)?; 214 | Ok(()) 215 | } 216 | -------------------------------------------------------------------------------- /src/nfs.rs: -------------------------------------------------------------------------------- 1 | // this is just a complete enumeration of everything in the RFC 2 | #![allow(dead_code)] 3 | // And its nice to keep the original RFC names and case 4 | #![allow(non_camel_case_types)] 5 | 6 | use crate::xdr::*; 7 | use byteorder::{ReadBytesExt, WriteBytesExt}; 8 | use filetime; 9 | use num_derive::{FromPrimitive, ToPrimitive}; 10 | use num_traits::cast::FromPrimitive; 11 | use std::fmt; 12 | use std::io::{Read, Write}; 13 | 14 | // Transcribed from RFC 1813. 15 | 16 | // Section 2.2 Constants 17 | /// These are the RPC constants needed to call the NFS Version 3 18 | /// service. They are given in decimal. 19 | pub const PROGRAM: u32 = 100003; 20 | pub const VERSION: u32 = 3; 21 | 22 | // Section 2.4 Sizes 23 | // 24 | /// The maximum size in bytes of the opaque file handle. 25 | pub const NFS3_FHSIZE: u32 = 64; 26 | 27 | /// The size in bytes of the opaque cookie verifier passed by 28 | /// READDIR and READDIRPLUS. 29 | pub const NFS3_COOKIEVERFSIZE: u32 = 8; 30 | 31 | /// The size in bytes of the opaque verifier used for 32 | /// exclusive CREATE. 33 | pub const NFS3_CREATEVERFSIZE: u32 = 8; 34 | 35 | /// The size in bytes of the opaque verifier used for 36 | /// asynchronous WRITE. 37 | pub const NFS3_WRITEVERFSIZE: u32 = 8; 38 | 39 | // Section 2.5 Basic Data Types 40 | #[allow(non_camel_case_types)] 41 | #[derive(Default, Clone)] 42 | pub struct nfsstring(pub Vec); 43 | impl nfsstring { 44 | pub fn len(&self) -> usize { 45 | self.0.len() 46 | } 47 | pub fn is_empty(&self) -> bool { 48 | self.0.is_empty() 49 | } 50 | } 51 | impl From> for nfsstring { 52 | fn from(value: Vec) -> Self { 53 | Self(value) 54 | } 55 | } 56 | impl From<&[u8]> for nfsstring { 57 | fn from(value: &[u8]) -> Self { 58 | Self(value.into()) 59 | } 60 | } 61 | impl AsRef<[u8]> for nfsstring { 62 | fn as_ref(&self) -> &[u8] { 63 | &self.0 64 | } 65 | } 66 | 67 | impl std::ops::Deref for nfsstring { 68 | type Target = Vec; 69 | 70 | fn deref(&self) -> &Self::Target { 71 | &self.0 72 | } 73 | } 74 | impl fmt::Debug for nfsstring { 75 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 76 | write!(f, "{:?}", String::from_utf8_lossy(&self.0)) 77 | } 78 | } 79 | impl fmt::Display for nfsstring { 80 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 81 | write!(f, "{:?}", String::from_utf8_lossy(&self.0)) 82 | } 83 | } 84 | pub type opaque = u8; 85 | pub type filename3 = nfsstring; 86 | pub type nfspath3 = nfsstring; 87 | pub type fileid3 = u64; 88 | pub type cookie3 = u64; 89 | pub type cookieverf3 = [opaque; NFS3_COOKIEVERFSIZE as usize]; 90 | pub type createverf3 = [opaque; NFS3_CREATEVERFSIZE as usize]; 91 | pub type writeverf3 = [opaque; NFS3_WRITEVERFSIZE as usize]; 92 | pub type uid3 = u32; 93 | pub type gid3 = u32; 94 | pub type size3 = u64; 95 | pub type offset3 = u64; 96 | pub type mode3 = u32; 97 | pub type count3 = u32; 98 | 99 | #[allow(non_camel_case_types)] 100 | #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] 101 | #[repr(u32)] 102 | pub enum nfsstat3 { 103 | /// Indicates the call completed successfully. 104 | NFS3_OK = 0, 105 | /// Not owner. The operation was not allowed because the 106 | /// caller is either not a privileged user (root) or not the 107 | /// owner of the target of the operation. 108 | NFS3ERR_PERM = 1, 109 | /// No such file or directory. The file or directory name 110 | /// specified does not exist. 111 | NFS3ERR_NOENT = 2, 112 | /// I/O error. A hard error (for example, a disk error) 113 | /// occurred while processing the requested operation. 114 | NFS3ERR_IO = 5, 115 | /// I/O error. No such device or address. 116 | NFS3ERR_NXIO = 6, 117 | /// Permission denied. The caller does not have the correct 118 | /// permission to perform the requested operation. Contrast 119 | /// this with NFS3ERR_PERM, which restricts itself to owner 120 | /// or privileged user permission failures. 121 | NFS3ERR_ACCES = 13, 122 | /// File exists. The file specified already exists. 123 | NFS3ERR_EXIST = 17, 124 | /// Attempt to do a cross-device hard link. 125 | NFS3ERR_XDEV = 18, 126 | /// No such device. 127 | NFS3ERR_NODEV = 19, 128 | /// Not a directory. The caller specified a non-directory in 129 | /// a directory operation. 130 | NFS3ERR_NOTDIR = 20, 131 | /// Is a directory. The caller specified a directory in a 132 | /// non-directory operation. 133 | NFS3ERR_ISDIR = 21, 134 | /// Invalid argument or unsupported argument for an 135 | /// operation. Two examples are attempting a READLINK on an 136 | /// object other than a symbolic link or attempting to 137 | /// SETATTR a time field on a server that does not support 138 | /// this operation. 139 | NFS3ERR_INVAL = 22, 140 | /// File too large. The operation would have caused a file to 141 | /// grow beyond the server's limit. 142 | NFS3ERR_FBIG = 27, 143 | /// No space left on device. The operation would have caused 144 | /// the server's file system to exceed its limit. 145 | NFS3ERR_NOSPC = 28, 146 | /// Read-only file system. A modifying operation was 147 | /// attempted on a read-only file system. 148 | NFS3ERR_ROFS = 30, 149 | /// Too many hard links. 150 | NFS3ERR_MLINK = 31, 151 | /// The filename in an operation was too long. 152 | NFS3ERR_NAMETOOLONG = 63, 153 | /// An attempt was made to remove a directory that was not empty. 154 | NFS3ERR_NOTEMPTY = 66, 155 | /// Resource (quota) hard limit exceeded. The user's resource 156 | /// limit on the server has been exceeded. 157 | NFS3ERR_DQUOT = 69, 158 | /// Invalid file handle. The file handle given in the 159 | /// arguments was invalid. The file referred to by that file 160 | /// handle no longer exists or access to it has been 161 | /// revoked. 162 | NFS3ERR_STALE = 70, 163 | /// Too many levels of remote in path. The file handle given 164 | /// in the arguments referred to a file on a non-local file 165 | /// system on the server. 166 | NFS3ERR_REMOTE = 71, 167 | /// Illegal NFS file handle. The file handle failed internal 168 | /// consistency checks. 169 | NFS3ERR_BADHANDLE = 10001, 170 | /// Update synchronization mismatch was detected during a 171 | /// SETATTR operation. 172 | NFS3ERR_NOT_SYNC = 10002, 173 | /// READDIR or READDIRPLUS cookie is stale 174 | NFS3ERR_BAD_COOKIE = 10003, 175 | /// Operation is not supported. 176 | NFS3ERR_NOTSUPP = 10004, 177 | /// Buffer or request is too small. 178 | NFS3ERR_TOOSMALL = 10005, 179 | /// An error occurred on the server which does not map to any 180 | /// of the legal NFS version 3 protocol error values. The 181 | /// client should translate this into an appropriate error. 182 | /// UNIX clients may choose to translate this to EIO. 183 | NFS3ERR_SERVERFAULT = 10006, 184 | /// An attempt was made to create an object of a type not 185 | /// supported by the server. 186 | NFS3ERR_BADTYPE = 10007, 187 | /// The server initiated the request, but was not able to 188 | /// complete it in a timely fashion. The client should wait 189 | /// and then try the request with a new RPC transaction ID. 190 | /// For example, this error should be returned from a server 191 | /// that supports hierarchical storage and receives a request 192 | /// to process a file that has been migrated. In this case, 193 | /// the server should start the immigration process and 194 | /// respond to client with this error. 195 | NFS3ERR_JUKEBOX = 10008, 196 | } 197 | 198 | XDREnumSerde!(nfsstat3); 199 | 200 | /// File Type 201 | #[allow(non_camel_case_types)] 202 | #[derive(Copy, Clone, Debug, Default, FromPrimitive, ToPrimitive)] 203 | #[repr(u32)] 204 | pub enum ftype3 { 205 | /// Regular File 206 | #[default] 207 | NF3REG = 1, 208 | /// Directory 209 | NF3DIR = 2, 210 | /// Block Special Device 211 | NF3BLK = 3, 212 | /// Character Special Device 213 | NF3CHR = 4, 214 | /// Symbolic Link 215 | NF3LNK = 5, 216 | /// Socket 217 | NF3SOCK = 6, 218 | /// Named Pipe 219 | NF3FIFO = 7, 220 | } 221 | XDREnumSerde!(ftype3); 222 | /// Device Number information. Ex: Major / Minor device 223 | #[allow(non_camel_case_types)] 224 | #[derive(Copy, Clone, Debug, Default)] 225 | #[repr(C)] 226 | pub struct specdata3 { 227 | pub specdata1: u32, 228 | pub specdata2: u32, 229 | } 230 | XDRStruct!(specdata3, specdata1, specdata2); 231 | 232 | /// File Handle information 233 | #[allow(non_camel_case_types)] 234 | #[derive(Clone, Debug)] 235 | pub struct nfs_fh3 { 236 | pub data: Vec, 237 | } 238 | XDRStruct!(nfs_fh3, data); 239 | #[allow(clippy::derivable_impls)] 240 | impl Default for nfs_fh3 { 241 | fn default() -> nfs_fh3 { 242 | nfs_fh3 { data: Vec::new() } 243 | } 244 | } 245 | 246 | #[allow(non_camel_case_types)] 247 | #[derive(Copy, Clone, Debug, Default)] 248 | #[repr(C)] 249 | pub struct nfstime3 { 250 | pub seconds: u32, 251 | pub nseconds: u32, 252 | } 253 | XDRStruct!(nfstime3, seconds, nseconds); 254 | 255 | impl From for filetime::FileTime { 256 | fn from(time: nfstime3) -> Self { 257 | filetime::FileTime::from_unix_time(time.seconds as i64, time.nseconds) 258 | } 259 | } 260 | 261 | #[allow(non_camel_case_types)] 262 | #[derive(Copy, Clone, Debug, Default)] 263 | pub struct fattr3 { 264 | pub ftype: ftype3, 265 | pub mode: mode3, 266 | pub nlink: u32, 267 | pub uid: uid3, 268 | pub gid: gid3, 269 | pub size: size3, 270 | pub used: size3, 271 | pub rdev: specdata3, 272 | pub fsid: u64, 273 | pub fileid: fileid3, 274 | pub atime: nfstime3, 275 | pub mtime: nfstime3, 276 | pub ctime: nfstime3, 277 | } 278 | XDRStruct!( 279 | fattr3, ftype, mode, nlink, uid, gid, size, used, rdev, fsid, fileid, atime, mtime, ctime 280 | ); 281 | 282 | // Section 3.3.19. Procedure 19: FSINFO - Get static file system Information 283 | // The following constants are used in fsinfo to construct the bitmask 'properties', 284 | // which represents the file system properties. 285 | 286 | /// If this bit is 1 (TRUE), the file system supports hard links. 287 | pub const FSF_LINK: u32 = 0x0001; 288 | 289 | /// If this bit is 1 (TRUE), the file system supports symbolic links. 290 | pub const FSF_SYMLINK: u32 = 0x0002; 291 | 292 | /// If this bit is 1 (TRUE), the information returned by 293 | /// PATHCONF is identical for every file and directory 294 | /// in the file system. If it is 0 (FALSE), the client 295 | /// should retrieve PATHCONF information for each file 296 | /// and directory as required. 297 | pub const FSF_HOMOGENEOUS: u32 = 0x0008; 298 | 299 | /// If this bit is 1 (TRUE), the server will set the 300 | /// times for a file via SETATTR if requested (to the 301 | /// accuracy indicated by time_delta). If it is 0 302 | /// (FALSE), the server cannot set times as requested. 303 | pub const FSF_CANSETTIME: u32 = 0x0010; 304 | 305 | #[allow(non_camel_case_types)] 306 | #[derive(Debug, Default)] 307 | pub struct fsinfo3 { 308 | pub obj_attributes: post_op_attr, 309 | pub rtmax: u32, 310 | pub rtpref: u32, 311 | pub rtmult: u32, 312 | pub wtmax: u32, 313 | pub wtpref: u32, 314 | pub wtmult: u32, 315 | pub dtpref: u32, 316 | pub maxfilesize: size3, 317 | pub time_delta: nfstime3, 318 | pub properties: u32, 319 | } 320 | XDRStruct!( 321 | fsinfo3, 322 | obj_attributes, 323 | rtmax, 324 | rtpref, 325 | rtmult, 326 | wtmax, 327 | wtpref, 328 | wtmult, 329 | dtpref, 330 | maxfilesize, 331 | time_delta, 332 | properties 333 | ); 334 | 335 | #[allow(non_camel_case_types)] 336 | #[derive(Copy, Clone, Debug, Default)] 337 | pub struct wcc_attr { 338 | pub size: size3, 339 | pub mtime: nfstime3, 340 | pub ctime: nfstime3, 341 | } 342 | XDRStruct!(wcc_attr, size, mtime, ctime); 343 | 344 | #[allow(non_camel_case_types)] 345 | #[derive(Copy, Clone, Debug, Default)] 346 | #[repr(u32)] 347 | pub enum pre_op_attr { 348 | #[default] 349 | Void, 350 | attributes(wcc_attr), 351 | } 352 | XDRBoolUnion!(pre_op_attr, attributes, wcc_attr); 353 | 354 | #[allow(non_camel_case_types)] 355 | #[derive(Copy, Clone, Debug, Default)] 356 | #[repr(u32)] 357 | pub enum post_op_attr { 358 | #[default] 359 | Void, 360 | attributes(fattr3), 361 | } 362 | XDRBoolUnion!(post_op_attr, attributes, fattr3); 363 | 364 | #[allow(non_camel_case_types)] 365 | #[derive(Copy, Clone, Debug, Default)] 366 | pub struct wcc_data { 367 | pub before: pre_op_attr, 368 | pub after: post_op_attr, 369 | } 370 | XDRStruct!(wcc_data, before, after); 371 | 372 | #[allow(non_camel_case_types)] 373 | #[derive(Clone, Debug, Default)] 374 | #[repr(u32)] 375 | pub enum post_op_fh3 { 376 | #[default] 377 | Void, 378 | handle(nfs_fh3), 379 | } 380 | XDRBoolUnion!(post_op_fh3, handle, nfs_fh3); 381 | 382 | #[allow(non_camel_case_types)] 383 | #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] 384 | #[repr(u32)] 385 | /// This enum is only used as a discriminant for set_atime / set_mtime 386 | /// and should not be used directly. 387 | pub enum _time_how { 388 | DONT_CHANGE = 0, 389 | SET_TO_SERVER_TIME = 1, 390 | SET_TO_CLIENT_TIME = 2, 391 | } 392 | XDREnumSerde!(_time_how); 393 | 394 | #[allow(non_camel_case_types)] 395 | #[derive(Copy, Clone, Debug)] 396 | #[repr(u32)] 397 | pub enum set_mode3 { 398 | Void, 399 | mode(mode3), 400 | } 401 | XDRBoolUnion!(set_mode3, mode, mode3); 402 | 403 | #[allow(non_camel_case_types)] 404 | #[derive(Copy, Clone, Debug)] 405 | #[repr(u32)] 406 | pub enum set_uid3 { 407 | Void, 408 | uid(uid3), 409 | } 410 | XDRBoolUnion!(set_uid3, uid, uid3); 411 | 412 | #[allow(non_camel_case_types)] 413 | #[derive(Copy, Clone, Debug)] 414 | #[repr(u32)] 415 | pub enum set_gid3 { 416 | Void, 417 | gid(gid3), 418 | } 419 | XDRBoolUnion!(set_gid3, gid, gid3); 420 | 421 | #[allow(non_camel_case_types)] 422 | #[derive(Copy, Clone, Debug)] 423 | #[repr(u32)] 424 | pub enum set_size3 { 425 | Void, 426 | size(size3), 427 | } 428 | XDRBoolUnion!(set_size3, size, size3); 429 | 430 | #[allow(non_camel_case_types)] 431 | #[derive(Copy, Clone, Debug)] 432 | #[repr(u32)] 433 | /// discriminant is time_how 434 | pub enum set_atime { 435 | DONT_CHANGE, 436 | SET_TO_SERVER_TIME, 437 | SET_TO_CLIENT_TIME(nfstime3), 438 | } 439 | impl XDR for set_atime { 440 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 441 | match self { 442 | set_atime::DONT_CHANGE => { 443 | 0_u32.serialize(dest)?; 444 | } 445 | set_atime::SET_TO_SERVER_TIME => { 446 | 1_u32.serialize(dest)?; 447 | } 448 | set_atime::SET_TO_CLIENT_TIME(v) => { 449 | 2_u32.serialize(dest)?; 450 | v.serialize(dest)?; 451 | } 452 | } 453 | Ok(()) 454 | } 455 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 456 | let mut c: u32 = 0; 457 | c.deserialize(src)?; 458 | if c == 0 { 459 | *self = set_atime::DONT_CHANGE; 460 | } else if c == 1 { 461 | *self = set_atime::SET_TO_SERVER_TIME; 462 | } else if c == 2 { 463 | let mut r = nfstime3::default(); 464 | r.deserialize(src)?; 465 | *self = set_atime::SET_TO_CLIENT_TIME(r); 466 | } else { 467 | return Err(std::io::Error::new( 468 | std::io::ErrorKind::InvalidData, 469 | "Invalid value for set_atime", 470 | )); 471 | } 472 | Ok(()) 473 | } 474 | } 475 | 476 | #[allow(non_camel_case_types)] 477 | #[derive(Copy, Clone, Debug)] 478 | #[repr(u32)] 479 | /// discriminant is time_how 480 | pub enum set_mtime { 481 | DONT_CHANGE, 482 | SET_TO_SERVER_TIME, 483 | SET_TO_CLIENT_TIME(nfstime3), 484 | } 485 | 486 | impl XDR for set_mtime { 487 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 488 | match self { 489 | set_mtime::DONT_CHANGE => { 490 | 0_u32.serialize(dest)?; 491 | } 492 | set_mtime::SET_TO_SERVER_TIME => { 493 | 1_u32.serialize(dest)?; 494 | } 495 | set_mtime::SET_TO_CLIENT_TIME(v) => { 496 | 2_u32.serialize(dest)?; 497 | v.serialize(dest)?; 498 | } 499 | } 500 | Ok(()) 501 | } 502 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 503 | let mut c: u32 = 0; 504 | c.deserialize(src)?; 505 | if c == 0 { 506 | *self = set_mtime::DONT_CHANGE; 507 | } else if c == 1 { 508 | *self = set_mtime::SET_TO_SERVER_TIME; 509 | } else if c == 2 { 510 | let mut r = nfstime3::default(); 511 | r.deserialize(src)?; 512 | *self = set_mtime::SET_TO_CLIENT_TIME(r); 513 | } else { 514 | return Err(std::io::Error::new( 515 | std::io::ErrorKind::InvalidData, 516 | "Invalid value for set_mtime", 517 | )); 518 | } 519 | Ok(()) 520 | } 521 | } 522 | 523 | #[allow(non_camel_case_types)] 524 | #[derive(Copy, Clone, Debug)] 525 | pub struct sattr3 { 526 | pub mode: set_mode3, 527 | pub uid: set_uid3, 528 | pub gid: set_gid3, 529 | pub size: set_size3, 530 | pub atime: set_atime, 531 | pub mtime: set_mtime, 532 | } 533 | XDRStruct!(sattr3, mode, uid, gid, size, atime, mtime); 534 | impl Default for sattr3 { 535 | fn default() -> sattr3 { 536 | sattr3 { 537 | mode: set_mode3::Void, 538 | uid: set_uid3::Void, 539 | gid: set_gid3::Void, 540 | size: set_size3::Void, 541 | atime: set_atime::DONT_CHANGE, 542 | mtime: set_mtime::DONT_CHANGE, 543 | } 544 | } 545 | } 546 | 547 | #[allow(non_camel_case_types)] 548 | #[derive(Clone, Debug, Default)] 549 | pub struct diropargs3 { 550 | pub dir: nfs_fh3, 551 | pub name: filename3, 552 | } 553 | XDRStruct!(diropargs3, dir, name); 554 | 555 | #[allow(non_camel_case_types)] 556 | #[derive(Debug, Default)] 557 | pub struct symlinkdata3 { 558 | pub symlink_attributes: sattr3, 559 | pub symlink_data: nfspath3, 560 | } 561 | XDRStruct!(symlinkdata3, symlink_attributes, symlink_data); 562 | 563 | /// We define the root handle here 564 | pub fn get_root_mount_handle() -> Vec { 565 | vec![0] 566 | } 567 | -------------------------------------------------------------------------------- /src/nfs_handlers.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::upper_case_acronyms)] 2 | #![allow(dead_code)] 3 | use crate::context::RPCContext; 4 | use crate::nfs; 5 | use crate::rpc::*; 6 | use crate::vfs::VFSCapabilities; 7 | use crate::xdr::*; 8 | use byteorder::{ReadBytesExt, WriteBytesExt}; 9 | use num_derive::{FromPrimitive, ToPrimitive}; 10 | use num_traits::cast::FromPrimitive; 11 | use std::io::{Read, Write}; 12 | use tracing::{debug, error, trace, warn}; 13 | /* 14 | program NFS_PROGRAM { 15 | version NFS_V3 { 16 | 17 | void 18 | NFSPROC3_NULL(void) = 0; 19 | 20 | GETATTR3res 21 | NFSPROC3_GETATTR(GETATTR3args) = 1; 22 | 23 | SETATTR3res 24 | NFSPROC3_SETATTR(SETATTR3args) = 2; 25 | 26 | LOOKUP3res 27 | NFSPROC3_LOOKUP(LOOKUP3args) = 3; 28 | 29 | ACCESS3res 30 | NFSPROC3_ACCESS(ACCESS3args) = 4; 31 | 32 | READLINK3res 33 | NFSPROC3_READLINK(READLINK3args) = 5; 34 | 35 | READ3res 36 | NFSPROC3_READ(READ3args) = 6; 37 | 38 | WRITE3res 39 | NFSPROC3_WRITE(WRITE3args) = 7; 40 | 41 | CREATE3res 42 | NFSPROC3_CREATE(CREATE3args) = 8; 43 | 44 | MKDIR3res 45 | NFSPROC3_MKDIR(MKDIR3args) = 9; 46 | 47 | SYMLINK3res 48 | NFSPROC3_SYMLINK(SYMLINK3args) = 10; 49 | 50 | MKNOD3res 51 | NFSPROC3_MKNOD(MKNOD3args) = 11; 52 | 53 | REMOVE3res 54 | NFSPROC3_REMOVE(REMOVE3args) = 12; 55 | 56 | RMDIR3res 57 | NFSPROC3_RMDIR(RMDIR3args) = 13; 58 | 59 | RENAME3res 60 | NFSPROC3_RENAME(RENAME3args) = 14; 61 | 62 | LINK3res 63 | NFSPROC3_LINK(LINK3args) = 15; 64 | 65 | READDIR3res 66 | NFSPROC3_READDIR(READDIR3args) = 16; 67 | 68 | READDIRPLUS3res 69 | NFSPROC3_READDIRPLUS(READDIRPLUS3args) = 17; 70 | 71 | FSSTAT3res 72 | NFSPROC3_FSSTAT(FSSTAT3args) = 18; 73 | 74 | FSINFO3res 75 | NFSPROC3_FSINFO(FSINFO3args) = 19; 76 | 77 | PATHCONF3res 78 | NFSPROC3_PATHCONF(PATHCONF3args) = 20; 79 | 80 | COMMIT3res 81 | NFSPROC3_COMMIT(COMMIT3args) = 21; 82 | 83 | } = 3; 84 | } = 100003; 85 | */ 86 | 87 | #[allow(non_camel_case_types)] 88 | #[allow(clippy::upper_case_acronyms)] 89 | #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] 90 | enum NFSProgram { 91 | NFSPROC3_NULL = 0, 92 | NFSPROC3_GETATTR = 1, 93 | NFSPROC3_SETATTR = 2, 94 | NFSPROC3_LOOKUP = 3, 95 | NFSPROC3_ACCESS = 4, 96 | NFSPROC3_READLINK = 5, 97 | NFSPROC3_READ = 6, 98 | NFSPROC3_WRITE = 7, 99 | NFSPROC3_CREATE = 8, 100 | NFSPROC3_MKDIR = 9, 101 | NFSPROC3_SYMLINK = 10, 102 | NFSPROC3_MKNOD = 11, 103 | NFSPROC3_REMOVE = 12, 104 | NFSPROC3_RMDIR = 13, 105 | NFSPROC3_RENAME = 14, 106 | NFSPROC3_LINK = 15, 107 | NFSPROC3_READDIR = 16, 108 | NFSPROC3_READDIRPLUS = 17, 109 | NFSPROC3_FSSTAT = 18, 110 | NFSPROC3_FSINFO = 19, 111 | NFSPROC3_PATHCONF = 20, 112 | NFSPROC3_COMMIT = 21, 113 | INVALID = 22, 114 | } 115 | 116 | pub async fn handle_nfs( 117 | xid: u32, 118 | call: call_body, 119 | input: &mut impl Read, 120 | output: &mut impl Write, 121 | context: &RPCContext, 122 | ) -> Result<(), anyhow::Error> { 123 | if call.vers != nfs::VERSION { 124 | warn!( 125 | "Invalid NFS Version number {} != {}", 126 | call.vers, 127 | nfs::VERSION 128 | ); 129 | prog_mismatch_reply_message(xid, nfs::VERSION).serialize(output)?; 130 | return Ok(()); 131 | } 132 | let prog = NFSProgram::from_u32(call.proc).unwrap_or(NFSProgram::INVALID); 133 | 134 | match prog { 135 | NFSProgram::NFSPROC3_NULL => nfsproc3_null(xid, input, output)?, 136 | NFSProgram::NFSPROC3_GETATTR => nfsproc3_getattr(xid, input, output, context).await?, 137 | NFSProgram::NFSPROC3_LOOKUP => nfsproc3_lookup(xid, input, output, context).await?, 138 | NFSProgram::NFSPROC3_READ => nfsproc3_read(xid, input, output, context).await?, 139 | NFSProgram::NFSPROC3_FSINFO => nfsproc3_fsinfo(xid, input, output, context).await?, 140 | NFSProgram::NFSPROC3_ACCESS => nfsproc3_access(xid, input, output, context).await?, 141 | NFSProgram::NFSPROC3_PATHCONF => nfsproc3_pathconf(xid, input, output, context).await?, 142 | NFSProgram::NFSPROC3_FSSTAT => nfsproc3_fsstat(xid, input, output, context).await?, 143 | NFSProgram::NFSPROC3_READDIR => nfsproc3_readdir(xid, input, output, context).await?, 144 | NFSProgram::NFSPROC3_READDIRPLUS => { 145 | nfsproc3_readdirplus(xid, input, output, context).await? 146 | } 147 | NFSProgram::NFSPROC3_WRITE => nfsproc3_write(xid, input, output, context).await?, 148 | NFSProgram::NFSPROC3_CREATE => nfsproc3_create(xid, input, output, context).await?, 149 | NFSProgram::NFSPROC3_SETATTR => nfsproc3_setattr(xid, input, output, context).await?, 150 | NFSProgram::NFSPROC3_REMOVE => nfsproc3_remove(xid, input, output, context).await?, 151 | NFSProgram::NFSPROC3_RMDIR => nfsproc3_remove(xid, input, output, context).await?, 152 | NFSProgram::NFSPROC3_RENAME => nfsproc3_rename(xid, input, output, context).await?, 153 | NFSProgram::NFSPROC3_MKDIR => nfsproc3_mkdir(xid, input, output, context).await?, 154 | NFSProgram::NFSPROC3_SYMLINK => nfsproc3_symlink(xid, input, output, context).await?, 155 | NFSProgram::NFSPROC3_READLINK => nfsproc3_readlink(xid, input, output, context).await?, 156 | _ => { 157 | warn!("Unimplemented message {:?}", prog); 158 | proc_unavail_reply_message(xid).serialize(output)?; 159 | } /* 160 | NFSPROC3_MKNOD, 161 | NFSPROC3_LINK, 162 | NFSPROC3_COMMIT, 163 | INVALID*/ 164 | } 165 | Ok(()) 166 | } 167 | 168 | pub fn nfsproc3_null( 169 | xid: u32, 170 | _: &mut impl Read, 171 | output: &mut impl Write, 172 | ) -> Result<(), anyhow::Error> { 173 | debug!("nfsproc3_null({:?}) ", xid); 174 | let msg = make_success_reply(xid); 175 | debug!("\t{:?} --> {:?}", xid, msg); 176 | msg.serialize(output)?; 177 | Ok(()) 178 | } 179 | /* 180 | GETATTR3res NFSPROC3_GETATTR(GETATTR3args) = 1; 181 | struct GETATTR3args { 182 | nfs_fh3 object; 183 | }; 184 | 185 | struct GETATTR3resok { 186 | fattr3 obj_attributes; 187 | }; 188 | 189 | union GETATTR3res switch (nfsstat3 status) { 190 | case NFS3_OK: 191 | GETATTR3resok resok; 192 | default: 193 | void; 194 | }; 195 | */ 196 | pub async fn nfsproc3_getattr( 197 | xid: u32, 198 | input: &mut impl Read, 199 | output: &mut impl Write, 200 | context: &RPCContext, 201 | ) -> Result<(), anyhow::Error> { 202 | let mut handle = nfs::nfs_fh3::default(); 203 | handle.deserialize(input)?; 204 | debug!("nfsproc3_getattr({:?},{:?}) ", xid, handle); 205 | 206 | let id = context.vfs.fh_to_id(&handle); 207 | // fail if unable to convert file handle 208 | if let Err(stat) = id { 209 | make_success_reply(xid).serialize(output)?; 210 | stat.serialize(output)?; 211 | return Ok(()); 212 | } 213 | let id = id.unwrap(); 214 | match context.vfs.getattr(id).await { 215 | Ok(fh) => { 216 | debug!(" {:?} --> {:?}", xid, fh); 217 | make_success_reply(xid).serialize(output)?; 218 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 219 | fh.serialize(output)?; 220 | } 221 | Err(stat) => { 222 | error!("getattr error {:?} --> {:?}", xid, stat); 223 | make_success_reply(xid).serialize(output)?; 224 | stat.serialize(output)?; 225 | } 226 | } 227 | Ok(()) 228 | } 229 | 230 | /* 231 | LOOKUP3res NFSPROC3_LOOKUP(LOOKUP3args) = 3; 232 | 233 | struct LOOKUP3args { 234 | diropargs3 what; 235 | }; 236 | 237 | struct LOOKUP3resok { 238 | nfs_fh3 object; 239 | post_op_attr obj_attributes; 240 | post_op_attr dir_attributes; 241 | }; 242 | 243 | struct LOOKUP3resfail { 244 | post_op_attr dir_attributes; 245 | }; 246 | 247 | union LOOKUP3res switch (nfsstat3 status) { 248 | case NFS3_OK: 249 | LOOKUP3resok resok; 250 | default: 251 | LOOKUP3resfail resfail; 252 | }; 253 | * 254 | */ 255 | pub async fn nfsproc3_lookup( 256 | xid: u32, 257 | input: &mut impl Read, 258 | output: &mut impl Write, 259 | context: &RPCContext, 260 | ) -> Result<(), anyhow::Error> { 261 | let mut dirops = nfs::diropargs3::default(); 262 | dirops.deserialize(input)?; 263 | debug!("nfsproc3_lookup({:?},{:?}) ", xid, dirops); 264 | 265 | let dirid = context.vfs.fh_to_id(&dirops.dir); 266 | // fail if unable to convert file handle 267 | if let Err(stat) = dirid { 268 | make_success_reply(xid).serialize(output)?; 269 | stat.serialize(output)?; 270 | nfs::post_op_attr::Void.serialize(output)?; 271 | return Ok(()); 272 | } 273 | let dirid = dirid.unwrap(); 274 | 275 | let dir_attr = match context.vfs.getattr(dirid).await { 276 | Ok(v) => nfs::post_op_attr::attributes(v), 277 | Err(_) => nfs::post_op_attr::Void, 278 | }; 279 | match context.vfs.lookup(dirid, &dirops.name).await { 280 | Ok(fid) => { 281 | let obj_attr = match context.vfs.getattr(fid).await { 282 | Ok(v) => nfs::post_op_attr::attributes(v), 283 | Err(_) => nfs::post_op_attr::Void, 284 | }; 285 | 286 | debug!("lookup success {:?} --> {:?}", xid, obj_attr); 287 | make_success_reply(xid).serialize(output)?; 288 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 289 | context.vfs.id_to_fh(fid).serialize(output)?; 290 | obj_attr.serialize(output)?; 291 | dir_attr.serialize(output)?; 292 | } 293 | Err(stat) => { 294 | debug!("lookup error {:?}({:?}) --> {:?}", xid, dirops.name, stat); 295 | make_success_reply(xid).serialize(output)?; 296 | stat.serialize(output)?; 297 | dir_attr.serialize(output)?; 298 | } 299 | } 300 | Ok(()) 301 | } 302 | 303 | #[allow(non_camel_case_types)] 304 | #[derive(Debug, Default)] 305 | struct READ3args { 306 | file: nfs::nfs_fh3, 307 | offset: nfs::offset3, 308 | count: nfs::count3, 309 | } 310 | XDRStruct!(READ3args, file, offset, count); 311 | 312 | #[allow(non_camel_case_types)] 313 | #[derive(Debug, Default)] 314 | struct READ3resok { 315 | file_attributes: nfs::post_op_attr, 316 | count: nfs::count3, 317 | eof: bool, 318 | data: Vec, 319 | } 320 | XDRStruct!(READ3resok, file_attributes, count, eof, data); 321 | /* 322 | READ3res NFSPROC3_READ(READ3args) = 6; 323 | 324 | struct READ3args { 325 | nfs_fh3 file; 326 | offset3 offset; 327 | count3 count; 328 | }; 329 | 330 | struct READ3resok { 331 | post_op_attr file_attributes; 332 | count3 count; 333 | bool eof; 334 | opaque data<>; 335 | }; 336 | 337 | struct READ3resfail { 338 | post_op_attr file_attributes; 339 | }; 340 | 341 | union READ3res switch (nfsstat3 status) { 342 | case NFS3_OK: 343 | READ3resok resok; 344 | default: 345 | READ3resfail resfail; 346 | }; 347 | */ 348 | pub async fn nfsproc3_read( 349 | xid: u32, 350 | input: &mut impl Read, 351 | output: &mut impl Write, 352 | context: &RPCContext, 353 | ) -> Result<(), anyhow::Error> { 354 | let mut args = READ3args::default(); 355 | args.deserialize(input)?; 356 | debug!("nfsproc3_read({:?},{:?}) ", xid, args); 357 | 358 | let id = context.vfs.fh_to_id(&args.file); 359 | if let Err(stat) = id { 360 | make_success_reply(xid).serialize(output)?; 361 | stat.serialize(output)?; 362 | nfs::post_op_attr::Void.serialize(output)?; 363 | return Ok(()); 364 | } 365 | let id = id.unwrap(); 366 | 367 | let obj_attr = match context.vfs.getattr(id).await { 368 | Ok(v) => nfs::post_op_attr::attributes(v), 369 | Err(_) => nfs::post_op_attr::Void, 370 | }; 371 | match context.vfs.read(id, args.offset, args.count).await { 372 | Ok((bytes, eof)) => { 373 | let res = READ3resok { 374 | file_attributes: obj_attr, 375 | count: bytes.len() as u32, 376 | eof, 377 | data: bytes, 378 | }; 379 | make_success_reply(xid).serialize(output)?; 380 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 381 | res.serialize(output)?; 382 | } 383 | Err(stat) => { 384 | error!("read error {:?} --> {:?}", xid, stat); 385 | make_success_reply(xid).serialize(output)?; 386 | stat.serialize(output)?; 387 | obj_attr.serialize(output)?; 388 | } 389 | } 390 | Ok(()) 391 | } 392 | 393 | /* 394 | 395 | FSINFO3res NFSPROC3_FSINFO(FSINFO3args) = 19; 396 | 397 | const FSF3_LINK = 0x0001; 398 | const FSF3_SYMLINK = 0x0002; 399 | const FSF3_HOMOGENEOUS = 0x0008; 400 | const FSF3_CANSETTIME = 0x0010; 401 | 402 | struct FSINFOargs { 403 | nfs_fh3 fsroot; 404 | }; 405 | 406 | struct FSINFO3resok { 407 | post_op_attr obj_attributes; 408 | uint32 rtmax; 409 | uint32 rtpref; 410 | uint32 rtmult; 411 | uint32 wtmax; 412 | uint32 wtpref; 413 | uint32 wtmult; 414 | uint32 dtpref; 415 | size3 maxfilesize; 416 | nfstime3 time_delta; 417 | uint32 properties; 418 | }; 419 | 420 | struct FSINFO3resfail { 421 | post_op_attr obj_attributes; 422 | }; 423 | 424 | union FSINFO3res switch (nfsstat3 status) { 425 | case NFS3_OK: 426 | FSINFO3resok resok; 427 | default: 428 | FSINFO3resfail resfail; 429 | }; 430 | */ 431 | 432 | pub async fn nfsproc3_fsinfo( 433 | xid: u32, 434 | input: &mut impl Read, 435 | output: &mut impl Write, 436 | context: &RPCContext, 437 | ) -> Result<(), anyhow::Error> { 438 | let mut handle = nfs::nfs_fh3::default(); 439 | handle.deserialize(input)?; 440 | debug!("nfsproc3_fsinfo({:?},{:?}) ", xid, handle); 441 | 442 | let id = context.vfs.fh_to_id(&handle); 443 | // fail if unable to convert file handle 444 | if let Err(stat) = id { 445 | make_success_reply(xid).serialize(output)?; 446 | stat.serialize(output)?; 447 | nfs::post_op_attr::Void.serialize(output)?; 448 | return Ok(()); 449 | } 450 | let id = id.unwrap(); 451 | 452 | match context.vfs.fsinfo(id).await { 453 | Ok(fsinfo) => { 454 | debug!(" {:?} --> {:?}", xid, fsinfo); 455 | make_success_reply(xid).serialize(output)?; 456 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 457 | fsinfo.serialize(output)?; 458 | } 459 | Err(stat) => { 460 | error!("fsinfo error {:?} --> {:?}", xid, stat); 461 | make_success_reply(xid).serialize(output)?; 462 | stat.serialize(output)?; 463 | } 464 | } 465 | Ok(()) 466 | } 467 | 468 | const ACCESS3_READ: u32 = 0x0001; 469 | const ACCESS3_LOOKUP: u32 = 0x0002; 470 | const ACCESS3_MODIFY: u32 = 0x0004; 471 | const ACCESS3_EXTEND: u32 = 0x0008; 472 | const ACCESS3_DELETE: u32 = 0x0010; 473 | const ACCESS3_EXECUTE: u32 = 0x0020; 474 | /* 475 | 476 | ACCESS3res NFSPROC3_ACCESS(ACCESS3args) = 4; 477 | 478 | 479 | struct ACCESS3args { 480 | nfs_fh3 object; 481 | uint32 access; 482 | }; 483 | 484 | struct ACCESS3resok { 485 | post_op_attr obj_attributes; 486 | uint32 access; 487 | }; 488 | 489 | struct ACCESS3resfail { 490 | post_op_attr obj_attributes; 491 | }; 492 | 493 | union ACCESS3res switch (nfsstat3 status) { 494 | case NFS3_OK: 495 | ACCESS3resok resok; 496 | default: 497 | ACCESS3resfail resfail; 498 | }; 499 | */ 500 | 501 | pub async fn nfsproc3_access( 502 | xid: u32, 503 | input: &mut impl Read, 504 | output: &mut impl Write, 505 | context: &RPCContext, 506 | ) -> Result<(), anyhow::Error> { 507 | let mut handle = nfs::nfs_fh3::default(); 508 | handle.deserialize(input)?; 509 | let mut access: u32 = 0; 510 | access.deserialize(input)?; 511 | debug!("nfsproc3_access({:?},{:?},{:?})", xid, handle, access); 512 | 513 | let id = context.vfs.fh_to_id(&handle); 514 | // fail if unable to convert file handle 515 | if let Err(stat) = id { 516 | make_success_reply(xid).serialize(output)?; 517 | stat.serialize(output)?; 518 | nfs::post_op_attr::Void.serialize(output)?; 519 | return Ok(()); 520 | } 521 | let id = id.unwrap(); 522 | 523 | let obj_attr = match context.vfs.getattr(id).await { 524 | Ok(v) => nfs::post_op_attr::attributes(v), 525 | Err(_) => nfs::post_op_attr::Void, 526 | }; 527 | // TODO better checks here 528 | if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { 529 | access &= ACCESS3_READ | ACCESS3_LOOKUP; 530 | } 531 | debug!(" {:?} ---> {:?}", xid, access); 532 | make_success_reply(xid).serialize(output)?; 533 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 534 | obj_attr.serialize(output)?; 535 | access.serialize(output)?; 536 | Ok(()) 537 | } 538 | 539 | #[allow(non_camel_case_types)] 540 | #[derive(Debug, Default)] 541 | struct PATHCONF3resok { 542 | obj_attributes: nfs::post_op_attr, 543 | linkmax: u32, 544 | name_max: u32, 545 | no_trunc: bool, 546 | chown_restricted: bool, 547 | case_insensitive: bool, 548 | case_preserving: bool, 549 | } 550 | XDRStruct!( 551 | PATHCONF3resok, 552 | obj_attributes, 553 | linkmax, 554 | name_max, 555 | no_trunc, 556 | chown_restricted, 557 | case_insensitive, 558 | case_preserving 559 | ); 560 | /* 561 | 562 | PATHCONF3res NFSPROC3_PATHCONF(PATHCONF3args) = 20; 563 | 564 | struct PATHCONF3args { 565 | nfs_fh3 object; 566 | }; 567 | 568 | struct PATHCONF3resok { 569 | post_op_attr obj_attributes; 570 | uint32 linkmax; 571 | uint32 name_max; 572 | bool no_trunc; 573 | bool chown_restricted; 574 | bool case_insensitive; 575 | bool case_preserving; 576 | }; 577 | 578 | struct PATHCONF3resfail { 579 | post_op_attr obj_attributes; 580 | }; 581 | 582 | union PATHCONF3res switch (nfsstat3 status) { 583 | case NFS3_OK: 584 | PATHCONF3resok resok; 585 | default: 586 | PATHCONF3resfail resfail; 587 | }; 588 | */ 589 | pub async fn nfsproc3_pathconf( 590 | xid: u32, 591 | input: &mut impl Read, 592 | output: &mut impl Write, 593 | context: &RPCContext, 594 | ) -> Result<(), anyhow::Error> { 595 | let mut handle = nfs::nfs_fh3::default(); 596 | handle.deserialize(input)?; 597 | debug!("nfsproc3_pathconf({:?},{:?})", xid, handle); 598 | 599 | let id = context.vfs.fh_to_id(&handle); 600 | // fail if unable to convert file handle 601 | if let Err(stat) = id { 602 | make_success_reply(xid).serialize(output)?; 603 | stat.serialize(output)?; 604 | nfs::post_op_attr::Void.serialize(output)?; 605 | return Ok(()); 606 | } 607 | let id = id.unwrap(); 608 | 609 | let obj_attr = match context.vfs.getattr(id).await { 610 | Ok(v) => nfs::post_op_attr::attributes(v), 611 | Err(_) => nfs::post_op_attr::Void, 612 | }; 613 | let res = PATHCONF3resok { 614 | obj_attributes: obj_attr, 615 | linkmax: 0, 616 | name_max: 32768, 617 | no_trunc: true, 618 | chown_restricted: true, 619 | case_insensitive: false, 620 | case_preserving: true, 621 | }; 622 | debug!(" {:?} ---> {:?}", xid, res); 623 | make_success_reply(xid).serialize(output)?; 624 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 625 | res.serialize(output)?; 626 | Ok(()) 627 | } 628 | 629 | #[allow(non_camel_case_types)] 630 | #[derive(Debug, Default)] 631 | struct FSSTAT3resok { 632 | obj_attributes: nfs::post_op_attr, 633 | tbytes: nfs::size3, 634 | fbytes: nfs::size3, 635 | abytes: nfs::size3, 636 | tfiles: nfs::size3, 637 | ffiles: nfs::size3, 638 | afiles: nfs::size3, 639 | invarsec: u32, 640 | } 641 | XDRStruct!( 642 | FSSTAT3resok, 643 | obj_attributes, 644 | tbytes, 645 | fbytes, 646 | abytes, 647 | tfiles, 648 | ffiles, 649 | afiles, 650 | invarsec 651 | ); 652 | 653 | /* 654 | FSSTAT3res NFSPROC3_FSSTAT(FSSTAT3args) = 18; 655 | 656 | struct FSSTAT3args { 657 | nfs_fh3 fsroot; 658 | }; 659 | 660 | struct FSSTAT3resok { 661 | post_op_attr obj_attributes; 662 | size3 tbytes; 663 | size3 fbytes; 664 | size3 abytes; 665 | size3 tfiles; 666 | size3 ffiles; 667 | size3 afiles; 668 | uint32 invarsec; 669 | }; 670 | 671 | struct FSSTAT3resfail { 672 | post_op_attr obj_attributes; 673 | }; 674 | 675 | union FSSTAT3res switch (nfsstat3 status) { 676 | case NFS3_OK: 677 | FSSTAT3resok resok; 678 | default: 679 | FSSTAT3resfail resfail; 680 | }; 681 | 682 | */ 683 | 684 | pub async fn nfsproc3_fsstat( 685 | xid: u32, 686 | input: &mut impl Read, 687 | output: &mut impl Write, 688 | context: &RPCContext, 689 | ) -> Result<(), anyhow::Error> { 690 | let mut handle = nfs::nfs_fh3::default(); 691 | handle.deserialize(input)?; 692 | debug!("nfsproc3_fsstat({:?},{:?}) ", xid, handle); 693 | let id = context.vfs.fh_to_id(&handle); 694 | // fail if unable to convert file handle 695 | if let Err(stat) = id { 696 | make_success_reply(xid).serialize(output)?; 697 | stat.serialize(output)?; 698 | nfs::post_op_attr::Void.serialize(output)?; 699 | return Ok(()); 700 | } 701 | let id = id.unwrap(); 702 | 703 | let obj_attr = match context.vfs.getattr(id).await { 704 | Ok(v) => nfs::post_op_attr::attributes(v), 705 | Err(_) => nfs::post_op_attr::Void, 706 | }; 707 | let res = FSSTAT3resok { 708 | obj_attributes: obj_attr, 709 | tbytes: 1024 * 1024 * 1024 * 1024, 710 | fbytes: 1024 * 1024 * 1024 * 1024, 711 | abytes: 1024 * 1024 * 1024 * 1024, 712 | tfiles: 1024 * 1024 * 1024, 713 | ffiles: 1024 * 1024 * 1024, 714 | afiles: 1024 * 1024 * 1024, 715 | invarsec: u32::MAX, 716 | }; 717 | make_success_reply(xid).serialize(output)?; 718 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 719 | debug!(" {:?} ---> {:?}", xid, res); 720 | res.serialize(output)?; 721 | Ok(()) 722 | } 723 | 724 | #[allow(non_camel_case_types)] 725 | #[derive(Debug, Default)] 726 | struct READDIRPLUS3args { 727 | dir: nfs::nfs_fh3, 728 | cookie: nfs::cookie3, 729 | cookieverf: nfs::cookieverf3, 730 | dircount: nfs::count3, 731 | maxcount: nfs::count3, 732 | } 733 | XDRStruct!( 734 | READDIRPLUS3args, 735 | dir, 736 | cookie, 737 | cookieverf, 738 | dircount, 739 | maxcount 740 | ); 741 | 742 | #[allow(non_camel_case_types)] 743 | #[derive(Debug, Default)] 744 | struct entry3 { 745 | fileid: nfs::fileid3, 746 | name: nfs::filename3, 747 | cookie: nfs::cookie3, 748 | } 749 | XDRStruct!(entry3, fileid, name, cookie); 750 | 751 | #[allow(non_camel_case_types)] 752 | #[derive(Debug, Default)] 753 | struct READDIR3args { 754 | dir: nfs::nfs_fh3, 755 | cookie: nfs::cookie3, 756 | cookieverf: nfs::cookieverf3, 757 | dircount: nfs::count3, 758 | } 759 | XDRStruct!(READDIR3args, dir, cookie, cookieverf, dircount); 760 | 761 | #[allow(non_camel_case_types)] 762 | #[derive(Debug, Default)] 763 | struct entryplus3 { 764 | fileid: nfs::fileid3, 765 | name: nfs::filename3, 766 | cookie: nfs::cookie3, 767 | name_attributes: nfs::post_op_attr, 768 | name_handle: nfs::post_op_fh3, 769 | } 770 | XDRStruct!( 771 | entryplus3, 772 | fileid, 773 | name, 774 | cookie, 775 | name_attributes, 776 | name_handle 777 | ); 778 | /* 779 | 780 | READDIRPLUS3res NFSPROC3_READDIRPLUS(READDIRPLUS3args) = 17; 781 | 782 | struct READDIRPLUS3args { 783 | nfs_fh3 dir; 784 | cookie3 cookie; 785 | cookieverf3 cookieverf; 786 | count3 dircount; 787 | count3 maxcount; 788 | }; 789 | 790 | 791 | struct dirlistplus3 { 792 | entryplus3 *entries; 793 | bool eof; 794 | }; 795 | 796 | struct READDIRPLUS3resok { 797 | post_op_attr dir_attributes; 798 | cookieverf3 cookieverf; 799 | dirlistplus3 reply; 800 | }; 801 | struct READDIRPLUS3resfail { 802 | post_op_attr dir_attributes; 803 | }; 804 | */ 805 | pub async fn nfsproc3_readdirplus( 806 | xid: u32, 807 | input: &mut impl Read, 808 | output: &mut impl Write, 809 | context: &RPCContext, 810 | ) -> Result<(), anyhow::Error> { 811 | let mut args = READDIRPLUS3args::default(); 812 | args.deserialize(input)?; 813 | debug!("nfsproc3_readdirplus({:?},{:?}) ", xid, args); 814 | 815 | let dirid = context.vfs.fh_to_id(&args.dir); 816 | // fail if unable to convert file handle 817 | if let Err(stat) = dirid { 818 | make_success_reply(xid).serialize(output)?; 819 | stat.serialize(output)?; 820 | nfs::post_op_attr::Void.serialize(output)?; 821 | return Ok(()); 822 | } 823 | let dirid = dirid.unwrap(); 824 | let dir_attr_maybe = context.vfs.getattr(dirid).await; 825 | 826 | let dir_attr = match dir_attr_maybe { 827 | Ok(v) => nfs::post_op_attr::attributes(v), 828 | Err(_) => nfs::post_op_attr::Void, 829 | }; 830 | 831 | let dirversion = if let Ok(ref dir_attr) = dir_attr_maybe { 832 | let cvf_version = (dir_attr.mtime.seconds as u64) << 32 | (dir_attr.mtime.nseconds as u64); 833 | cvf_version.to_be_bytes() 834 | } else { 835 | nfs::cookieverf3::default() 836 | }; 837 | debug!(" -- Dir attr {:?}", dir_attr); 838 | debug!(" -- Dir version {:?}", dirversion); 839 | let has_version = args.cookieverf != nfs::cookieverf3::default(); 840 | // initial call should hve empty cookie verf 841 | // subsequent calls should have cvf_version as defined above 842 | // which is based off the mtime. 843 | // 844 | // TODO: This is *far* too aggressive. and unnecessary. 845 | // The client should maintain this correctly typically. 846 | // 847 | // The way cookieverf is handled is quite interesting... 848 | // 849 | // There are 2 notes in the RFC of interest: 850 | // 1. If the 851 | // server detects that the cookie is no longer valid, the 852 | // server will reject the READDIR request with the status, 853 | // NFS3ERR_BAD_COOKIE. The client should be careful to 854 | // avoid holding directory entry cookies across operations 855 | // that modify the directory contents, such as REMOVE and 856 | // CREATE. 857 | // 858 | // 2. One implementation of the cookie-verifier mechanism might 859 | // be for the server to use the modification time of the 860 | // directory. This might be overly restrictive, however. A 861 | // better approach would be to record the time of the last 862 | // directory modification that changed the directory 863 | // organization in a way that would make it impossible to 864 | // reliably interpret a cookie. Servers in which directory 865 | // cookies are always valid are free to use zero as the 866 | // verifier always. 867 | // 868 | // Basically, as long as the cookie is "kinda" intepretable, 869 | // we should keep accepting it. 870 | // On testing, the Mac NFS client pretty much expects that 871 | // especially on highly concurrent modifications to the directory. 872 | // 873 | // 1. If part way through a directory enumeration we fail with BAD_COOKIE 874 | // if the directory contents change, the client listing may fail resulting 875 | // in a "no such file or directory" error. 876 | // 2. if we cache readdir results. i.e. we think of a readdir as two parts 877 | // a. enumerating everything first 878 | // b. the cookie is then used to paginate the enumeration 879 | // we can run into file time synchronization issues. i.e. while one 880 | // listing occurs and another file is touched, the listing may report 881 | // an outdated file status. 882 | // 883 | // This cache also appears to have to be *quite* long lasting 884 | // as the client may hold on to a directory enumerator 885 | // with unbounded time. 886 | // 887 | // Basically, if we think about how linux directory listing works 888 | // is that you just get an enumerator. There is no mechanic available for 889 | // "restarting" a pagination and this enumerator is assumed to be valid 890 | // even across directory modifications and should reflect changes 891 | // immediately. 892 | // 893 | // The best solution is simply to really completely avoid sending 894 | // BAD_COOKIE all together and to ignore the cookie mechanism. 895 | // 896 | /*if args.cookieverf != nfs::cookieverf3::default() && args.cookieverf != dirversion { 897 | info!(" -- Dir version mismatch. Received {:?}", args.cookieverf); 898 | make_success_reply(xid).serialize(output)?; 899 | nfs::nfsstat3::NFS3ERR_BAD_COOKIE.serialize(output)?; 900 | dir_attr.serialize(output)?; 901 | return Ok(()); 902 | }*/ 903 | // subtract off the final entryplus* field (which must be false) and the eof 904 | let max_bytes_allowed = args.maxcount as usize - 128; 905 | // args.dircount is bytes of just fileid, name, cookie. 906 | // This is hard to ballpark, so we just divide it by 16 907 | let estimated_max_results = args.dircount / 16; 908 | let max_dircount_bytes = args.dircount as usize; 909 | let mut ctr = 0; 910 | match context 911 | .vfs 912 | .readdir(dirid, args.cookie, estimated_max_results as usize) 913 | .await 914 | { 915 | Ok(result) => { 916 | // we count dir_count seperately as it is just a subset of fields 917 | let mut accumulated_dircount: usize = 0; 918 | let mut all_entries_written = true; 919 | 920 | // this is a wrapper around a writer that also just counts the number of bytes 921 | // written 922 | let mut counting_output = crate::write_counter::WriteCounter::new(output); 923 | 924 | make_success_reply(xid).serialize(&mut counting_output)?; 925 | nfs::nfsstat3::NFS3_OK.serialize(&mut counting_output)?; 926 | dir_attr.serialize(&mut counting_output)?; 927 | dirversion.serialize(&mut counting_output)?; 928 | for entry in result.entries { 929 | let obj_attr = entry.attr; 930 | let handle = nfs::post_op_fh3::handle(context.vfs.id_to_fh(entry.fileid)); 931 | 932 | let entry = entryplus3 { 933 | fileid: entry.fileid, 934 | name: entry.name, 935 | cookie: entry.fileid, 936 | name_attributes: nfs::post_op_attr::attributes(obj_attr), 937 | name_handle: handle, 938 | }; 939 | // write the entry into a buffer first 940 | let mut write_buf: Vec = Vec::new(); 941 | let mut write_cursor = std::io::Cursor::new(&mut write_buf); 942 | // true flag for the entryplus3* to mark that this contains an entry 943 | true.serialize(&mut write_cursor)?; 944 | entry.serialize(&mut write_cursor)?; 945 | write_cursor.flush()?; 946 | let added_dircount = std::mem::size_of::() // fileid 947 | + std::mem::size_of::() + entry.name.len() // name 948 | + std::mem::size_of::(); // cookie 949 | let added_output_bytes = write_buf.len(); 950 | // check if we can write without hitting the limits 951 | if added_output_bytes + counting_output.bytes_written() < max_bytes_allowed 952 | && added_dircount + accumulated_dircount < max_dircount_bytes 953 | { 954 | trace!(" -- dirent {:?}", entry); 955 | // commit the entry 956 | ctr += 1; 957 | counting_output.write_all(&write_buf)?; 958 | accumulated_dircount += added_dircount; 959 | trace!( 960 | " -- lengths: {:?} / {:?} {:?} / {:?}", 961 | accumulated_dircount, 962 | max_dircount_bytes, 963 | counting_output.bytes_written(), 964 | max_bytes_allowed 965 | ); 966 | } else { 967 | trace!(" -- insufficient space. truncating"); 968 | all_entries_written = false; 969 | break; 970 | } 971 | } 972 | // false flag for the final entryplus* linked list 973 | false.serialize(&mut counting_output)?; 974 | // eof flag is only valid here if we wrote everything 975 | if all_entries_written { 976 | debug!(" -- readdir eof {:?}", result.end); 977 | result.end.serialize(&mut counting_output)?; 978 | } else { 979 | debug!(" -- readdir eof {:?}", false); 980 | false.serialize(&mut counting_output)?; 981 | } 982 | debug!( 983 | "readir {}, has_version {}, start at {}, flushing {} entries, complete {}", 984 | dirid, has_version, args.cookie, ctr, all_entries_written 985 | ); 986 | } 987 | Err(stat) => { 988 | error!("readdir error {:?} --> {:?} ", xid, stat); 989 | make_success_reply(xid).serialize(output)?; 990 | stat.serialize(output)?; 991 | dir_attr.serialize(output)?; 992 | } 993 | }; 994 | Ok(()) 995 | } 996 | 997 | pub async fn nfsproc3_readdir( 998 | xid: u32, 999 | input: &mut impl Read, 1000 | output: &mut impl Write, 1001 | context: &RPCContext, 1002 | ) -> Result<(), anyhow::Error> { 1003 | let mut args = READDIR3args::default(); 1004 | args.deserialize(input)?; 1005 | debug!("nfsproc3_readdirplus({:?},{:?}) ", xid, args); 1006 | 1007 | let dirid = context.vfs.fh_to_id(&args.dir); 1008 | // fail if unable to convert file handle 1009 | if let Err(stat) = dirid { 1010 | make_success_reply(xid).serialize(output)?; 1011 | stat.serialize(output)?; 1012 | nfs::post_op_attr::Void.serialize(output)?; 1013 | return Ok(()); 1014 | } 1015 | let dirid = dirid.unwrap(); 1016 | let dir_attr_maybe = context.vfs.getattr(dirid).await; 1017 | 1018 | let dir_attr = match dir_attr_maybe { 1019 | Ok(v) => nfs::post_op_attr::attributes(v), 1020 | Err(_) => nfs::post_op_attr::Void, 1021 | }; 1022 | 1023 | let dirversion = if let Ok(ref dir_attr) = dir_attr_maybe { 1024 | let cvf_version = (dir_attr.mtime.seconds as u64) << 32 | (dir_attr.mtime.nseconds as u64); 1025 | cvf_version.to_be_bytes() 1026 | } else { 1027 | nfs::cookieverf3::default() 1028 | }; 1029 | debug!(" -- Dir attr {:?}", dir_attr); 1030 | debug!(" -- Dir version {:?}", dirversion); 1031 | let has_version = args.cookieverf != nfs::cookieverf3::default(); 1032 | // subtract off the final entryplus* field (which must be false) and the eof 1033 | let max_bytes_allowed = args.dircount as usize - 128; 1034 | // args.dircount is bytes of just fileid, name, cookie. 1035 | // This is hard to ballpark, so we just divide it by 16 1036 | let estimated_max_results = args.dircount / 16; 1037 | let mut ctr = 0; 1038 | match context 1039 | .vfs 1040 | .readdir_simple(dirid, estimated_max_results as usize) 1041 | .await 1042 | { 1043 | Ok(result) => { 1044 | // we count dir_count seperately as it is just a subset of fields 1045 | let mut accumulated_dircount: usize = 0; 1046 | let mut all_entries_written = true; 1047 | 1048 | // this is a wrapper around a writer that also just counts the number of bytes 1049 | // written 1050 | let mut counting_output = crate::write_counter::WriteCounter::new(output); 1051 | 1052 | make_success_reply(xid).serialize(&mut counting_output)?; 1053 | nfs::nfsstat3::NFS3_OK.serialize(&mut counting_output)?; 1054 | dir_attr.serialize(&mut counting_output)?; 1055 | dirversion.serialize(&mut counting_output)?; 1056 | for entry in result.entries { 1057 | let entry = entry3 { 1058 | fileid: entry.fileid, 1059 | name: entry.name, 1060 | cookie: entry.fileid, 1061 | }; 1062 | // write the entry into a buffer first 1063 | let mut write_buf: Vec = Vec::new(); 1064 | let mut write_cursor = std::io::Cursor::new(&mut write_buf); 1065 | // true flag for the entryplus3* to mark that this contains an entry 1066 | true.serialize(&mut write_cursor)?; 1067 | entry.serialize(&mut write_cursor)?; 1068 | write_cursor.flush()?; 1069 | let added_dircount = std::mem::size_of::() // fileid 1070 | + std::mem::size_of::() + entry.name.len() // name 1071 | + std::mem::size_of::(); // cookie 1072 | let added_output_bytes = write_buf.len(); 1073 | // check if we can write without hitting the limits 1074 | if added_output_bytes + counting_output.bytes_written() < max_bytes_allowed { 1075 | trace!(" -- dirent {:?}", entry); 1076 | // commit the entry 1077 | ctr += 1; 1078 | counting_output.write_all(&write_buf)?; 1079 | accumulated_dircount += added_dircount; 1080 | trace!( 1081 | " -- lengths: {:?} / {:?} / {:?}", 1082 | accumulated_dircount, 1083 | counting_output.bytes_written(), 1084 | max_bytes_allowed 1085 | ); 1086 | } else { 1087 | trace!(" -- insufficient space. truncating"); 1088 | all_entries_written = false; 1089 | break; 1090 | } 1091 | } 1092 | // false flag for the final entryplus* linked list 1093 | false.serialize(&mut counting_output)?; 1094 | // eof flag is only valid here if we wrote everything 1095 | if all_entries_written { 1096 | debug!(" -- readdir eof {:?}", result.end); 1097 | result.end.serialize(&mut counting_output)?; 1098 | } else { 1099 | debug!(" -- readdir eof {:?}", false); 1100 | false.serialize(&mut counting_output)?; 1101 | } 1102 | debug!( 1103 | "readir {}, has_version {}, start at {}, flushing {} entries, complete {}", 1104 | dirid, has_version, args.cookie, ctr, all_entries_written 1105 | ); 1106 | } 1107 | Err(stat) => { 1108 | error!("readdir error {:?} --> {:?} ", xid, stat); 1109 | make_success_reply(xid).serialize(output)?; 1110 | stat.serialize(output)?; 1111 | dir_attr.serialize(output)?; 1112 | } 1113 | }; 1114 | Ok(()) 1115 | } 1116 | 1117 | #[allow(non_camel_case_types)] 1118 | #[derive(Copy, Clone, Debug, Default, FromPrimitive, ToPrimitive)] 1119 | #[repr(u32)] 1120 | pub enum stable_how { 1121 | #[default] 1122 | UNSTABLE = 0, 1123 | DATA_SYNC = 1, 1124 | FILE_SYNC = 2, 1125 | } 1126 | XDREnumSerde!(stable_how); 1127 | 1128 | #[allow(non_camel_case_types)] 1129 | #[derive(Debug, Default)] 1130 | struct WRITE3args { 1131 | file: nfs::nfs_fh3, 1132 | offset: nfs::offset3, 1133 | count: nfs::count3, 1134 | stable: u32, 1135 | data: Vec, 1136 | } 1137 | XDRStruct!(WRITE3args, file, offset, count, stable, data); 1138 | 1139 | #[allow(non_camel_case_types)] 1140 | #[derive(Debug, Default)] 1141 | struct WRITE3resok { 1142 | file_wcc: nfs::wcc_data, 1143 | count: nfs::count3, 1144 | committed: stable_how, 1145 | verf: nfs::writeverf3, 1146 | } 1147 | XDRStruct!(WRITE3resok, file_wcc, count, committed, verf); 1148 | /* 1149 | enum stable_how { 1150 | UNSTABLE = 0, 1151 | DATA_SYNC = 1, 1152 | FILE_SYNC = 2 1153 | }; 1154 | 1155 | 1156 | struct WRITE3args { 1157 | nfs_fh3 file; 1158 | offset3 offset; 1159 | count3 count; 1160 | stable_how stable; 1161 | opaque data<>; 1162 | }; 1163 | 1164 | struct WRITE3resok { 1165 | wcc_data file_wcc; 1166 | count3 count; 1167 | stable_how committed; 1168 | writeverf3 verf; 1169 | }; 1170 | 1171 | 1172 | struct WRITE3resfail { 1173 | wcc_data file_wcc; 1174 | }; 1175 | 1176 | 1177 | union WRITE3res switch (nfsstat3 status) { 1178 | case NFS3_OK: 1179 | WRITE3resok resok; 1180 | default: 1181 | WRITE3resfail resfail; 1182 | }; 1183 | 1184 | */ 1185 | pub async fn nfsproc3_write( 1186 | xid: u32, 1187 | input: &mut impl Read, 1188 | output: &mut impl Write, 1189 | context: &RPCContext, 1190 | ) -> Result<(), anyhow::Error> { 1191 | // if we do not have write capabilities 1192 | if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { 1193 | warn!("No write capabilities."); 1194 | make_success_reply(xid).serialize(output)?; 1195 | nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; 1196 | nfs::wcc_data::default().serialize(output)?; 1197 | return Ok(()); 1198 | } 1199 | 1200 | let mut args = WRITE3args::default(); 1201 | args.deserialize(input)?; 1202 | debug!("nfsproc3_write({:?},...) ", xid); 1203 | // sanity check the length 1204 | if args.data.len() != args.count as usize { 1205 | garbage_args_reply_message(xid).serialize(output)?; 1206 | return Ok(()); 1207 | } 1208 | 1209 | let id = context.vfs.fh_to_id(&args.file); 1210 | if let Err(stat) = id { 1211 | make_success_reply(xid).serialize(output)?; 1212 | stat.serialize(output)?; 1213 | nfs::wcc_data::default().serialize(output)?; 1214 | return Ok(()); 1215 | } 1216 | let id = id.unwrap(); 1217 | 1218 | // get the object attributes before the write 1219 | let pre_obj_attr = match context.vfs.getattr(id).await { 1220 | Ok(v) => { 1221 | let wccattr = nfs::wcc_attr { 1222 | size: v.size, 1223 | mtime: v.mtime, 1224 | ctime: v.ctime, 1225 | }; 1226 | nfs::pre_op_attr::attributes(wccattr) 1227 | } 1228 | Err(_) => nfs::pre_op_attr::Void, 1229 | }; 1230 | 1231 | match context.vfs.write(id, args.offset, &args.data).await { 1232 | Ok(fattr) => { 1233 | debug!("write success {:?} --> {:?}", xid, fattr); 1234 | let res = WRITE3resok { 1235 | file_wcc: nfs::wcc_data { 1236 | before: pre_obj_attr, 1237 | after: nfs::post_op_attr::attributes(fattr), 1238 | }, 1239 | count: args.count, 1240 | committed: stable_how::FILE_SYNC, 1241 | verf: context.vfs.serverid(), 1242 | }; 1243 | make_success_reply(xid).serialize(output)?; 1244 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 1245 | res.serialize(output)?; 1246 | } 1247 | Err(stat) => { 1248 | error!("write error {:?} --> {:?}", xid, stat); 1249 | make_success_reply(xid).serialize(output)?; 1250 | stat.serialize(output)?; 1251 | nfs::wcc_data::default().serialize(output)?; 1252 | } 1253 | } 1254 | Ok(()) 1255 | } 1256 | 1257 | #[allow(non_camel_case_types)] 1258 | #[derive(Copy, Clone, Debug, Default, FromPrimitive, ToPrimitive)] 1259 | #[repr(u32)] 1260 | pub enum createmode3 { 1261 | #[default] 1262 | UNCHECKED = 0, 1263 | GUARDED = 1, 1264 | EXCLUSIVE = 2, 1265 | } 1266 | XDREnumSerde!(createmode3); 1267 | /* 1268 | CREATE3res NFSPROC3_CREATE(CREATE3args) = 8; 1269 | 1270 | enum createmode3 { 1271 | UNCHECKED = 0, 1272 | GUARDED = 1, 1273 | EXCLUSIVE = 2 1274 | }; 1275 | 1276 | union createhow3 switch (createmode3 mode) { 1277 | case UNCHECKED: 1278 | case GUARDED: 1279 | sattr3 obj_attributes; 1280 | case EXCLUSIVE: 1281 | createverf3 verf; 1282 | }; 1283 | 1284 | struct CREATE3args { 1285 | diropargs3 where; 1286 | createhow3 how; 1287 | }; 1288 | 1289 | struct CREATE3resok { 1290 | post_op_fh3 obj; 1291 | post_op_attr obj_attributes; 1292 | wcc_data dir_wcc; 1293 | }; 1294 | 1295 | struct CREATE3resfail { 1296 | wcc_data dir_wcc; 1297 | }; 1298 | 1299 | union CREATE3res switch (nfsstat3 status) { 1300 | case NFS3_OK: 1301 | CREATE3resok resok; 1302 | default: 1303 | CREATE3resfail resfail; 1304 | }; 1305 | */ 1306 | 1307 | pub async fn nfsproc3_create( 1308 | xid: u32, 1309 | input: &mut impl Read, 1310 | output: &mut impl Write, 1311 | context: &RPCContext, 1312 | ) -> Result<(), anyhow::Error> { 1313 | // if we do not have write capabilities 1314 | if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { 1315 | warn!("No write capabilities."); 1316 | make_success_reply(xid).serialize(output)?; 1317 | nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; 1318 | nfs::wcc_data::default().serialize(output)?; 1319 | return Ok(()); 1320 | } 1321 | 1322 | let mut dirops = nfs::diropargs3::default(); 1323 | dirops.deserialize(input)?; 1324 | let mut createhow = createmode3::default(); 1325 | createhow.deserialize(input)?; 1326 | 1327 | debug!("nfsproc3_create({:?}, {:?}, {:?}) ", xid, dirops, createhow); 1328 | 1329 | // find the directory we are supposed to create the 1330 | // new file in 1331 | let dirid = context.vfs.fh_to_id(&dirops.dir); 1332 | if let Err(stat) = dirid { 1333 | // directory does not exist 1334 | make_success_reply(xid).serialize(output)?; 1335 | stat.serialize(output)?; 1336 | nfs::wcc_data::default().serialize(output)?; 1337 | error!("Directory does not exist"); 1338 | return Ok(()); 1339 | } 1340 | // found the directory, get the attributes 1341 | let dirid = dirid.unwrap(); 1342 | 1343 | // get the object attributes before the write 1344 | let pre_dir_attr = match context.vfs.getattr(dirid).await { 1345 | Ok(v) => { 1346 | let wccattr = nfs::wcc_attr { 1347 | size: v.size, 1348 | mtime: v.mtime, 1349 | ctime: v.ctime, 1350 | }; 1351 | nfs::pre_op_attr::attributes(wccattr) 1352 | } 1353 | Err(stat) => { 1354 | error!("Cannot stat directory"); 1355 | make_success_reply(xid).serialize(output)?; 1356 | stat.serialize(output)?; 1357 | nfs::wcc_data::default().serialize(output)?; 1358 | return Ok(()); 1359 | } 1360 | }; 1361 | let mut target_attributes = nfs::sattr3::default(); 1362 | 1363 | match createhow { 1364 | createmode3::UNCHECKED => { 1365 | target_attributes.deserialize(input)?; 1366 | debug!("create unchecked {:?}", target_attributes); 1367 | } 1368 | createmode3::GUARDED => { 1369 | target_attributes.deserialize(input)?; 1370 | debug!("create guarded {:?}", target_attributes); 1371 | if context.vfs.lookup(dirid, &dirops.name).await.is_ok() { 1372 | // file exists. Fail with NFS3ERR_EXIST. 1373 | // Re-read dir attributes 1374 | // for post op attr 1375 | let post_dir_attr = match context.vfs.getattr(dirid).await { 1376 | Ok(v) => nfs::post_op_attr::attributes(v), 1377 | Err(_) => nfs::post_op_attr::Void, 1378 | }; 1379 | 1380 | make_success_reply(xid).serialize(output)?; 1381 | nfs::nfsstat3::NFS3ERR_EXIST.serialize(output)?; 1382 | nfs::wcc_data { 1383 | before: pre_dir_attr, 1384 | after: post_dir_attr, 1385 | } 1386 | .serialize(output)?; 1387 | return Ok(()); 1388 | } 1389 | } 1390 | createmode3::EXCLUSIVE => { 1391 | debug!("create exclusive"); 1392 | } 1393 | } 1394 | 1395 | let fid: Result; 1396 | let postopattr: nfs::post_op_attr; 1397 | // fill in the fid and post op attr here 1398 | if matches!(createhow, createmode3::EXCLUSIVE) { 1399 | // the API for exclusive is very slightly different 1400 | // We are not returning a post op attribute 1401 | fid = context.vfs.create_exclusive(dirid, &dirops.name).await; 1402 | postopattr = nfs::post_op_attr::Void; 1403 | } else { 1404 | // create! 1405 | let res = context 1406 | .vfs 1407 | .create(dirid, &dirops.name, target_attributes) 1408 | .await; 1409 | fid = res.map(|x| x.0); 1410 | postopattr = if let Ok((_, fattr)) = res { 1411 | nfs::post_op_attr::attributes(fattr) 1412 | } else { 1413 | nfs::post_op_attr::Void 1414 | }; 1415 | } 1416 | 1417 | // Re-read dir attributes for post op attr 1418 | let post_dir_attr = match context.vfs.getattr(dirid).await { 1419 | Ok(v) => nfs::post_op_attr::attributes(v), 1420 | Err(_) => nfs::post_op_attr::Void, 1421 | }; 1422 | let wcc_res = nfs::wcc_data { 1423 | before: pre_dir_attr, 1424 | after: post_dir_attr, 1425 | }; 1426 | 1427 | match fid { 1428 | Ok(fid) => { 1429 | debug!("create success --> {:?}, {:?}", fid, postopattr); 1430 | make_success_reply(xid).serialize(output)?; 1431 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 1432 | // serialize CREATE3resok 1433 | let fh = context.vfs.id_to_fh(fid); 1434 | nfs::post_op_fh3::handle(fh).serialize(output)?; 1435 | postopattr.serialize(output)?; 1436 | wcc_res.serialize(output)?; 1437 | } 1438 | Err(e) => { 1439 | error!("create error --> {:?}", e); 1440 | // serialize CREATE3resfail 1441 | make_success_reply(xid).serialize(output)?; 1442 | e.serialize(output)?; 1443 | wcc_res.serialize(output)?; 1444 | } 1445 | } 1446 | 1447 | Ok(()) 1448 | } 1449 | 1450 | #[allow(non_camel_case_types)] 1451 | #[derive(Copy, Clone, Debug, Default)] 1452 | #[repr(u32)] 1453 | pub enum sattrguard3 { 1454 | #[default] 1455 | Void, 1456 | obj_ctime(nfs::nfstime3), 1457 | } 1458 | XDRBoolUnion!(sattrguard3, obj_ctime, nfs::nfstime3); 1459 | 1460 | #[allow(non_camel_case_types)] 1461 | #[derive(Clone, Debug, Default)] 1462 | struct SETATTR3args { 1463 | object: nfs::nfs_fh3, 1464 | new_attribute: nfs::sattr3, 1465 | guard: sattrguard3, 1466 | } 1467 | XDRStruct!(SETATTR3args, object, new_attribute, guard); 1468 | 1469 | /* 1470 | SETATTR3res NFSPROC3_SETATTR(SETATTR3args) = 2; 1471 | 1472 | union sattrguard3 switch (bool check) { 1473 | case TRUE: 1474 | nfstime3 obj_ctime; 1475 | case FALSE: 1476 | void; 1477 | }; 1478 | 1479 | struct SETATTR3args { 1480 | nfs_fh3 object; 1481 | sattr3 new_attributes; 1482 | sattrguard3 guard; 1483 | }; 1484 | 1485 | struct SETATTR3resok { 1486 | wcc_data obj_wcc; 1487 | }; 1488 | 1489 | struct SETATTR3resfail { 1490 | wcc_data obj_wcc; 1491 | }; 1492 | union SETATTR3res switch (nfsstat3 status) { 1493 | case NFS3_OK: 1494 | SETATTR3resok resok; 1495 | default: 1496 | SETATTR3resfail resfail; 1497 | }; 1498 | */ 1499 | 1500 | pub async fn nfsproc3_setattr( 1501 | xid: u32, 1502 | input: &mut impl Read, 1503 | output: &mut impl Write, 1504 | context: &RPCContext, 1505 | ) -> Result<(), anyhow::Error> { 1506 | if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { 1507 | warn!("No write capabilities."); 1508 | make_success_reply(xid).serialize(output)?; 1509 | nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; 1510 | nfs::wcc_data::default().serialize(output)?; 1511 | return Ok(()); 1512 | } 1513 | let mut args = SETATTR3args::default(); 1514 | args.deserialize(input)?; 1515 | debug!("nfsproc3_setattr({:?},{:?}) ", xid, args); 1516 | 1517 | let id = context.vfs.fh_to_id(&args.object); 1518 | // fail if unable to convert file handle 1519 | if let Err(stat) = id { 1520 | make_success_reply(xid).serialize(output)?; 1521 | stat.serialize(output)?; 1522 | return Ok(()); 1523 | } 1524 | let id = id.unwrap(); 1525 | 1526 | let ctime; 1527 | 1528 | let pre_op_attr = match context.vfs.getattr(id).await { 1529 | Ok(v) => { 1530 | let wccattr = nfs::wcc_attr { 1531 | size: v.size, 1532 | mtime: v.mtime, 1533 | ctime: v.ctime, 1534 | }; 1535 | ctime = v.ctime; 1536 | nfs::pre_op_attr::attributes(wccattr) 1537 | } 1538 | Err(stat) => { 1539 | make_success_reply(xid).serialize(output)?; 1540 | stat.serialize(output)?; 1541 | nfs::wcc_data::default().serialize(output)?; 1542 | return Ok(()); 1543 | } 1544 | }; 1545 | // handle the guard 1546 | match args.guard { 1547 | sattrguard3::Void => {} 1548 | sattrguard3::obj_ctime(c) => { 1549 | if c.seconds != ctime.seconds || c.nseconds != ctime.nseconds { 1550 | make_success_reply(xid).serialize(output)?; 1551 | nfs::nfsstat3::NFS3ERR_NOT_SYNC.serialize(output)?; 1552 | nfs::wcc_data::default().serialize(output)?; 1553 | } 1554 | } 1555 | } 1556 | 1557 | match context.vfs.setattr(id, args.new_attribute).await { 1558 | Ok(post_op_attr) => { 1559 | debug!(" setattr success {:?} --> {:?}", xid, post_op_attr); 1560 | let wcc_res = nfs::wcc_data { 1561 | before: pre_op_attr, 1562 | after: nfs::post_op_attr::attributes(post_op_attr), 1563 | }; 1564 | make_success_reply(xid).serialize(output)?; 1565 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 1566 | wcc_res.serialize(output)?; 1567 | } 1568 | Err(stat) => { 1569 | error!("setattr error {:?} --> {:?}", xid, stat); 1570 | make_success_reply(xid).serialize(output)?; 1571 | stat.serialize(output)?; 1572 | nfs::wcc_data::default().serialize(output)?; 1573 | } 1574 | } 1575 | Ok(()) 1576 | } 1577 | 1578 | /* 1579 | REMOVE3res NFSPROC3_REMOVE(REMOVE3args) = 12; 1580 | 1581 | struct REMOVE3args { 1582 | diropargs3 object; 1583 | }; 1584 | 1585 | struct REMOVE3resok { 1586 | wcc_data dir_wcc; 1587 | }; 1588 | 1589 | struct REMOVE3resfail { 1590 | wcc_data dir_wcc; 1591 | }; 1592 | 1593 | union REMOVE3res switch (nfsstat3 status) { 1594 | case NFS3_OK: 1595 | REMOVE3resok resok; 1596 | default: 1597 | REMOVE3resfail resfail; 1598 | }; 1599 | 1600 | RMDIR is basically identically structured 1601 | */ 1602 | 1603 | pub async fn nfsproc3_remove( 1604 | xid: u32, 1605 | input: &mut impl Read, 1606 | output: &mut impl Write, 1607 | context: &RPCContext, 1608 | ) -> Result<(), anyhow::Error> { 1609 | // if we do not have write capabilities 1610 | if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { 1611 | warn!("No write capabilities."); 1612 | make_success_reply(xid).serialize(output)?; 1613 | nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; 1614 | nfs::wcc_data::default().serialize(output)?; 1615 | return Ok(()); 1616 | } 1617 | 1618 | let mut dirops = nfs::diropargs3::default(); 1619 | dirops.deserialize(input)?; 1620 | 1621 | debug!("nfsproc3_remove({:?}, {:?}) ", xid, dirops); 1622 | 1623 | // find the directory with the file 1624 | let dirid = context.vfs.fh_to_id(&dirops.dir); 1625 | if let Err(stat) = dirid { 1626 | // directory does not exist 1627 | make_success_reply(xid).serialize(output)?; 1628 | stat.serialize(output)?; 1629 | nfs::wcc_data::default().serialize(output)?; 1630 | error!("Directory does not exist"); 1631 | return Ok(()); 1632 | } 1633 | let dirid = dirid.unwrap(); 1634 | 1635 | // get the object attributes before the write 1636 | let pre_dir_attr = match context.vfs.getattr(dirid).await { 1637 | Ok(v) => { 1638 | let wccattr = nfs::wcc_attr { 1639 | size: v.size, 1640 | mtime: v.mtime, 1641 | ctime: v.ctime, 1642 | }; 1643 | nfs::pre_op_attr::attributes(wccattr) 1644 | } 1645 | Err(stat) => { 1646 | error!("Cannot stat directory"); 1647 | make_success_reply(xid).serialize(output)?; 1648 | stat.serialize(output)?; 1649 | nfs::wcc_data::default().serialize(output)?; 1650 | return Ok(()); 1651 | } 1652 | }; 1653 | 1654 | // delete! 1655 | let res = context.vfs.remove(dirid, &dirops.name).await; 1656 | 1657 | // Re-read dir attributes for post op attr 1658 | let post_dir_attr = match context.vfs.getattr(dirid).await { 1659 | Ok(v) => nfs::post_op_attr::attributes(v), 1660 | Err(_) => nfs::post_op_attr::Void, 1661 | }; 1662 | let wcc_res = nfs::wcc_data { 1663 | before: pre_dir_attr, 1664 | after: post_dir_attr, 1665 | }; 1666 | 1667 | match res { 1668 | Ok(()) => { 1669 | debug!("remove success"); 1670 | make_success_reply(xid).serialize(output)?; 1671 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 1672 | wcc_res.serialize(output)?; 1673 | } 1674 | Err(e) => { 1675 | error!("remove error {:?} --> {:?}", xid, e); 1676 | // serialize CREATE3resfail 1677 | make_success_reply(xid).serialize(output)?; 1678 | e.serialize(output)?; 1679 | wcc_res.serialize(output)?; 1680 | } 1681 | } 1682 | 1683 | Ok(()) 1684 | } 1685 | 1686 | /* 1687 | RENAME3res NFSPROC3_RENAME(RENAME3args) = 14; 1688 | 1689 | struct RENAME3args { 1690 | diropargs3 from; 1691 | diropargs3 to; 1692 | }; 1693 | 1694 | struct RENAME3resok { 1695 | wcc_data fromdir_wcc; 1696 | wcc_data todir_wcc; 1697 | }; 1698 | 1699 | struct RENAME3resfail { 1700 | wcc_data fromdir_wcc; 1701 | wcc_data todir_wcc; 1702 | }; 1703 | 1704 | union RENAME3res switch (nfsstat3 status) { 1705 | case NFS3_OK: 1706 | RENAME3resok resok; 1707 | default: 1708 | RENAME3resfail resfail; 1709 | }; 1710 | */ 1711 | 1712 | pub async fn nfsproc3_rename( 1713 | xid: u32, 1714 | input: &mut impl Read, 1715 | output: &mut impl Write, 1716 | context: &RPCContext, 1717 | ) -> Result<(), anyhow::Error> { 1718 | // if we do not have write capabilities 1719 | if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { 1720 | warn!("No write capabilities."); 1721 | make_success_reply(xid).serialize(output)?; 1722 | nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; 1723 | nfs::wcc_data::default().serialize(output)?; 1724 | return Ok(()); 1725 | } 1726 | 1727 | let mut fromdirops = nfs::diropargs3::default(); 1728 | let mut todirops = nfs::diropargs3::default(); 1729 | fromdirops.deserialize(input)?; 1730 | todirops.deserialize(input)?; 1731 | 1732 | debug!( 1733 | "nfsproc3_rename({:?}, {:?}, {:?}) ", 1734 | xid, fromdirops, todirops 1735 | ); 1736 | 1737 | // find the from directory 1738 | let from_dirid = context.vfs.fh_to_id(&fromdirops.dir); 1739 | if let Err(stat) = from_dirid { 1740 | // directory does not exist 1741 | make_success_reply(xid).serialize(output)?; 1742 | stat.serialize(output)?; 1743 | nfs::wcc_data::default().serialize(output)?; 1744 | error!("Directory does not exist"); 1745 | return Ok(()); 1746 | } 1747 | 1748 | // find the to directory 1749 | let to_dirid = context.vfs.fh_to_id(&todirops.dir); 1750 | if let Err(stat) = to_dirid { 1751 | // directory does not exist 1752 | make_success_reply(xid).serialize(output)?; 1753 | stat.serialize(output)?; 1754 | nfs::wcc_data::default().serialize(output)?; 1755 | error!("Directory does not exist"); 1756 | return Ok(()); 1757 | } 1758 | 1759 | // found the directory, get the attributes 1760 | let from_dirid = from_dirid.unwrap(); 1761 | let to_dirid = to_dirid.unwrap(); 1762 | 1763 | // get the object attributes before the write 1764 | let pre_from_dir_attr = match context.vfs.getattr(from_dirid).await { 1765 | Ok(v) => { 1766 | let wccattr = nfs::wcc_attr { 1767 | size: v.size, 1768 | mtime: v.mtime, 1769 | ctime: v.ctime, 1770 | }; 1771 | nfs::pre_op_attr::attributes(wccattr) 1772 | } 1773 | Err(stat) => { 1774 | error!("Cannot stat directory"); 1775 | make_success_reply(xid).serialize(output)?; 1776 | stat.serialize(output)?; 1777 | nfs::wcc_data::default().serialize(output)?; 1778 | return Ok(()); 1779 | } 1780 | }; 1781 | 1782 | // get the object attributes before the write 1783 | let pre_to_dir_attr = match context.vfs.getattr(to_dirid).await { 1784 | Ok(v) => { 1785 | let wccattr = nfs::wcc_attr { 1786 | size: v.size, 1787 | mtime: v.mtime, 1788 | ctime: v.ctime, 1789 | }; 1790 | nfs::pre_op_attr::attributes(wccattr) 1791 | } 1792 | Err(stat) => { 1793 | error!("Cannot stat directory"); 1794 | make_success_reply(xid).serialize(output)?; 1795 | stat.serialize(output)?; 1796 | nfs::wcc_data::default().serialize(output)?; 1797 | return Ok(()); 1798 | } 1799 | }; 1800 | 1801 | // rename! 1802 | let res = context 1803 | .vfs 1804 | .rename(from_dirid, &fromdirops.name, to_dirid, &todirops.name) 1805 | .await; 1806 | 1807 | // Re-read dir attributes for post op attr 1808 | let post_from_dir_attr = match context.vfs.getattr(from_dirid).await { 1809 | Ok(v) => nfs::post_op_attr::attributes(v), 1810 | Err(_) => nfs::post_op_attr::Void, 1811 | }; 1812 | let post_to_dir_attr = match context.vfs.getattr(to_dirid).await { 1813 | Ok(v) => nfs::post_op_attr::attributes(v), 1814 | Err(_) => nfs::post_op_attr::Void, 1815 | }; 1816 | let from_wcc_res = nfs::wcc_data { 1817 | before: pre_from_dir_attr, 1818 | after: post_from_dir_attr, 1819 | }; 1820 | 1821 | let to_wcc_res = nfs::wcc_data { 1822 | before: pre_to_dir_attr, 1823 | after: post_to_dir_attr, 1824 | }; 1825 | 1826 | match res { 1827 | Ok(()) => { 1828 | debug!("rename success"); 1829 | make_success_reply(xid).serialize(output)?; 1830 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 1831 | from_wcc_res.serialize(output)?; 1832 | to_wcc_res.serialize(output)?; 1833 | } 1834 | Err(e) => { 1835 | error!("rename error {:?} --> {:?}", xid, e); 1836 | // serialize CREATE3resfail 1837 | make_success_reply(xid).serialize(output)?; 1838 | e.serialize(output)?; 1839 | from_wcc_res.serialize(output)?; 1840 | to_wcc_res.serialize(output)?; 1841 | } 1842 | } 1843 | 1844 | Ok(()) 1845 | } 1846 | 1847 | /* 1848 | MKDIR3res NFSPROC3_MKDIR(MKDIR3args) = 9; 1849 | 1850 | struct MKDIR3args { 1851 | diropargs3 where; 1852 | sattr3 attributes; 1853 | }; 1854 | 1855 | struct MKDIR3resok { 1856 | post_op_fh3 obj; 1857 | post_op_attr obj_attributes; 1858 | wcc_data dir_wcc; 1859 | }; 1860 | 1861 | struct MKDIR3resfail { 1862 | wcc_data dir_wcc; 1863 | }; 1864 | 1865 | union MKDIR3res switch (nfsstat3 status) { 1866 | case NFS3_OK: 1867 | MKDIR3resok resok; 1868 | default: 1869 | MKDIR3resfail resfail; 1870 | }; 1871 | 1872 | */ 1873 | 1874 | #[allow(non_camel_case_types)] 1875 | #[derive(Debug, Default)] 1876 | struct MKDIR3args { 1877 | dirops: nfs::diropargs3, 1878 | attributes: nfs::sattr3, 1879 | } 1880 | XDRStruct!(MKDIR3args, dirops, attributes); 1881 | 1882 | pub async fn nfsproc3_mkdir( 1883 | xid: u32, 1884 | input: &mut impl Read, 1885 | output: &mut impl Write, 1886 | context: &RPCContext, 1887 | ) -> Result<(), anyhow::Error> { 1888 | // if we do not have write capabilities 1889 | if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { 1890 | warn!("No write capabilities."); 1891 | make_success_reply(xid).serialize(output)?; 1892 | nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; 1893 | nfs::wcc_data::default().serialize(output)?; 1894 | return Ok(()); 1895 | } 1896 | let mut args = MKDIR3args::default(); 1897 | args.deserialize(input)?; 1898 | 1899 | debug!("nfsproc3_mkdir({:?}, {:?}) ", xid, args); 1900 | 1901 | // find the directory we are supposed to create the 1902 | // new file in 1903 | let dirid = context.vfs.fh_to_id(&args.dirops.dir); 1904 | if let Err(stat) = dirid { 1905 | // directory does not exist 1906 | make_success_reply(xid).serialize(output)?; 1907 | stat.serialize(output)?; 1908 | nfs::wcc_data::default().serialize(output)?; 1909 | error!("Directory does not exist"); 1910 | return Ok(()); 1911 | } 1912 | // found the directory, get the attributes 1913 | let dirid = dirid.unwrap(); 1914 | 1915 | // get the object attributes before the write 1916 | let pre_dir_attr = match context.vfs.getattr(dirid).await { 1917 | Ok(v) => { 1918 | let wccattr = nfs::wcc_attr { 1919 | size: v.size, 1920 | mtime: v.mtime, 1921 | ctime: v.ctime, 1922 | }; 1923 | nfs::pre_op_attr::attributes(wccattr) 1924 | } 1925 | Err(stat) => { 1926 | error!("Cannot stat directory"); 1927 | make_success_reply(xid).serialize(output)?; 1928 | stat.serialize(output)?; 1929 | nfs::wcc_data::default().serialize(output)?; 1930 | return Ok(()); 1931 | } 1932 | }; 1933 | 1934 | let res = context.vfs.mkdir(dirid, &args.dirops.name).await; 1935 | 1936 | // Re-read dir attributes for post op attr 1937 | let post_dir_attr = match context.vfs.getattr(dirid).await { 1938 | Ok(v) => nfs::post_op_attr::attributes(v), 1939 | Err(_) => nfs::post_op_attr::Void, 1940 | }; 1941 | let wcc_res = nfs::wcc_data { 1942 | before: pre_dir_attr, 1943 | after: post_dir_attr, 1944 | }; 1945 | 1946 | match res { 1947 | Ok((fid, fattr)) => { 1948 | debug!("mkdir success --> {:?}, {:?}", fid, fattr); 1949 | make_success_reply(xid).serialize(output)?; 1950 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 1951 | // serialize CREATE3resok 1952 | let fh = context.vfs.id_to_fh(fid); 1953 | nfs::post_op_fh3::handle(fh).serialize(output)?; 1954 | nfs::post_op_attr::attributes(fattr).serialize(output)?; 1955 | wcc_res.serialize(output)?; 1956 | } 1957 | Err(e) => { 1958 | debug!("mkdir error {:?} --> {:?}", xid, e); 1959 | // serialize CREATE3resfail 1960 | make_success_reply(xid).serialize(output)?; 1961 | e.serialize(output)?; 1962 | wcc_res.serialize(output)?; 1963 | } 1964 | } 1965 | 1966 | Ok(()) 1967 | } 1968 | 1969 | /* 1970 | SYMLINK3res NFSPROC3_SYMLINK(SYMLINK3args) = 10; 1971 | 1972 | struct symlinkdata3 { 1973 | sattr3 symlink_attributes; 1974 | nfspath3 symlink_data; 1975 | }; 1976 | 1977 | struct SYMLINK3args { 1978 | diropargs3 where; 1979 | symlinkdata3 symlink; 1980 | }; 1981 | 1982 | struct SYMLINK3resok { 1983 | post_op_fh3 obj; 1984 | post_op_attr obj_attributes; 1985 | wcc_data dir_wcc; 1986 | }; 1987 | 1988 | struct SYMLINK3resfail { 1989 | wcc_data dir_wcc; 1990 | }; 1991 | 1992 | union SYMLINK3res switch (nfsstat3 status) { 1993 | case NFS3_OK: 1994 | SYMLINK3resok resok; 1995 | default: 1996 | SYMLINK3resfail resfail; 1997 | }; 1998 | */ 1999 | 2000 | #[allow(non_camel_case_types)] 2001 | #[derive(Debug, Default)] 2002 | struct SYMLINK3args { 2003 | dirops: nfs::diropargs3, 2004 | symlink: nfs::symlinkdata3, 2005 | } 2006 | XDRStruct!(SYMLINK3args, dirops, symlink); 2007 | 2008 | pub async fn nfsproc3_symlink( 2009 | xid: u32, 2010 | input: &mut impl Read, 2011 | output: &mut impl Write, 2012 | context: &RPCContext, 2013 | ) -> Result<(), anyhow::Error> { 2014 | // if we do not have write capabilities 2015 | if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { 2016 | warn!("No write capabilities."); 2017 | make_success_reply(xid).serialize(output)?; 2018 | nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; 2019 | nfs::wcc_data::default().serialize(output)?; 2020 | return Ok(()); 2021 | } 2022 | let mut args = SYMLINK3args::default(); 2023 | args.deserialize(input)?; 2024 | 2025 | debug!("nfsproc3_symlink({:?}, {:?}) ", xid, args); 2026 | 2027 | // find the directory we are supposed to create the 2028 | // new file in 2029 | let dirid = context.vfs.fh_to_id(&args.dirops.dir); 2030 | if let Err(stat) = dirid { 2031 | // directory does not exist 2032 | make_success_reply(xid).serialize(output)?; 2033 | stat.serialize(output)?; 2034 | nfs::wcc_data::default().serialize(output)?; 2035 | error!("Directory does not exist"); 2036 | return Ok(()); 2037 | } 2038 | // found the directory, get the attributes 2039 | let dirid = dirid.unwrap(); 2040 | 2041 | // get the object attributes before the write 2042 | let pre_dir_attr = match context.vfs.getattr(dirid).await { 2043 | Ok(v) => { 2044 | let wccattr = nfs::wcc_attr { 2045 | size: v.size, 2046 | mtime: v.mtime, 2047 | ctime: v.ctime, 2048 | }; 2049 | nfs::pre_op_attr::attributes(wccattr) 2050 | } 2051 | Err(stat) => { 2052 | error!("Cannot stat directory"); 2053 | make_success_reply(xid).serialize(output)?; 2054 | stat.serialize(output)?; 2055 | nfs::wcc_data::default().serialize(output)?; 2056 | return Ok(()); 2057 | } 2058 | }; 2059 | 2060 | let res = context 2061 | .vfs 2062 | .symlink( 2063 | dirid, 2064 | &args.dirops.name, 2065 | &args.symlink.symlink_data, 2066 | &args.symlink.symlink_attributes, 2067 | ) 2068 | .await; 2069 | 2070 | // Re-read dir attributes for post op attr 2071 | let post_dir_attr = match context.vfs.getattr(dirid).await { 2072 | Ok(v) => nfs::post_op_attr::attributes(v), 2073 | Err(_) => nfs::post_op_attr::Void, 2074 | }; 2075 | let wcc_res = nfs::wcc_data { 2076 | before: pre_dir_attr, 2077 | after: post_dir_attr, 2078 | }; 2079 | 2080 | match res { 2081 | Ok((fid, fattr)) => { 2082 | debug!("symlink success --> {:?}, {:?}", fid, fattr); 2083 | make_success_reply(xid).serialize(output)?; 2084 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 2085 | // serialize CREATE3resok 2086 | let fh = context.vfs.id_to_fh(fid); 2087 | nfs::post_op_fh3::handle(fh).serialize(output)?; 2088 | nfs::post_op_attr::attributes(fattr).serialize(output)?; 2089 | wcc_res.serialize(output)?; 2090 | } 2091 | Err(e) => { 2092 | debug!("symlink error --> {:?}", e); 2093 | // serialize CREATE3resfail 2094 | make_success_reply(xid).serialize(output)?; 2095 | e.serialize(output)?; 2096 | wcc_res.serialize(output)?; 2097 | } 2098 | } 2099 | 2100 | Ok(()) 2101 | } 2102 | 2103 | /* 2104 | 2105 | READLINK3res NFSPROC3_READLINK(READLINK3args) = 5; 2106 | 2107 | struct READLINK3args { 2108 | nfs_fh3 symlink; 2109 | }; 2110 | 2111 | struct READLINK3resok { 2112 | post_op_attr symlink_attributes; 2113 | nfspath3 data; 2114 | }; 2115 | 2116 | struct READLINK3resfail { 2117 | post_op_attr symlink_attributes; 2118 | }; 2119 | 2120 | union READLINK3res switch (nfsstat3 status) { 2121 | case NFS3_OK: 2122 | READLINK3resok resok; 2123 | default: 2124 | READLINK3resfail resfail; 2125 | }; 2126 | */ 2127 | pub async fn nfsproc3_readlink( 2128 | xid: u32, 2129 | input: &mut impl Read, 2130 | output: &mut impl Write, 2131 | context: &RPCContext, 2132 | ) -> Result<(), anyhow::Error> { 2133 | let mut handle = nfs::nfs_fh3::default(); 2134 | handle.deserialize(input)?; 2135 | debug!("nfsproc3_readlink({:?},{:?}) ", xid, handle); 2136 | 2137 | let id = context.vfs.fh_to_id(&handle); 2138 | // fail if unable to convert file handle 2139 | if let Err(stat) = id { 2140 | make_success_reply(xid).serialize(output)?; 2141 | stat.serialize(output)?; 2142 | return Ok(()); 2143 | } 2144 | let id = id.unwrap(); 2145 | // if the id does not exist, we fail 2146 | let symlink_attr = match context.vfs.getattr(id).await { 2147 | Ok(v) => nfs::post_op_attr::attributes(v), 2148 | Err(stat) => { 2149 | make_success_reply(xid).serialize(output)?; 2150 | stat.serialize(output)?; 2151 | nfs::post_op_attr::Void.serialize(output)?; 2152 | return Ok(()); 2153 | } 2154 | }; 2155 | match context.vfs.readlink(id).await { 2156 | Ok(path) => { 2157 | debug!(" {:?} --> {:?}", xid, path); 2158 | make_success_reply(xid).serialize(output)?; 2159 | nfs::nfsstat3::NFS3_OK.serialize(output)?; 2160 | symlink_attr.serialize(output)?; 2161 | path.serialize(output)?; 2162 | } 2163 | Err(stat) => { 2164 | // failed to read link 2165 | // retry with failure and the post_op_attr 2166 | make_success_reply(xid).serialize(output)?; 2167 | stat.serialize(output)?; 2168 | symlink_attr.serialize(output)?; 2169 | } 2170 | } 2171 | Ok(()) 2172 | } 2173 | -------------------------------------------------------------------------------- /src/portmap.rs: -------------------------------------------------------------------------------- 1 | // this is just a complete enumeration of everything in the RFC 2 | #![allow(dead_code)] 3 | // And its nice to keep the original RFC names and case 4 | #![allow(non_camel_case_types)] 5 | 6 | use crate::xdr::*; 7 | use std::io::{Read, Write}; 8 | // Transcribed from RFC 1057 Appendix A 9 | 10 | /// Device Number information. Ex: Major / Minor device 11 | #[allow(non_camel_case_types)] 12 | #[derive(Copy, Clone, Debug, Default)] 13 | #[repr(C)] 14 | pub struct mapping { 15 | pub prog: u32, 16 | pub vers: u32, 17 | pub prot: u32, 18 | pub port: u32, 19 | } 20 | XDRStruct!(mapping, prog, vers, prot, port); 21 | pub const IPPROTO_TCP: u32 = 6; /* protocol number for TCP/IP */ 22 | pub const IPPROTO_UDP: u32 = 17; /* protocol number for UDP/IP */ 23 | pub const PROGRAM: u32 = 100000; 24 | pub const VERSION: u32 = 2; 25 | -------------------------------------------------------------------------------- /src/portmap_handlers.rs: -------------------------------------------------------------------------------- 1 | use crate::context::RPCContext; 2 | use crate::portmap; 3 | use crate::rpc::*; 4 | use crate::xdr::*; 5 | use num_derive::{FromPrimitive, ToPrimitive}; 6 | use num_traits::cast::FromPrimitive; 7 | use std::io::{Read, Write}; 8 | use tracing::{debug, error}; 9 | 10 | /* 11 | From RFC 1057 Appendix A 12 | 13 | program PMAP_PROG { 14 | version PMAP_VERS { 15 | void PMAPPROC_NULL(void) = 0; 16 | bool PMAPPROC_SET(mapping) = 1; 17 | bool PMAPPROC_UNSET(mapping) = 2; 18 | unsigned int PMAPPROC_GETPORT(mapping) = 3; 19 | pmaplist PMAPPROC_DUMP(void) = 4; 20 | call_result PMAPPROC_CALLIT(call_args) = 5; 21 | } = 2; 22 | } = 100000; 23 | */ 24 | 25 | #[allow(non_camel_case_types)] 26 | #[allow(clippy::upper_case_acronyms)] 27 | #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] 28 | enum PortmapProgram { 29 | PMAPPROC_NULL = 0, 30 | PMAPPROC_SET = 1, 31 | PMAPPROC_UNSET = 2, 32 | PMAPPROC_GETPORT = 3, 33 | PMAPPROC_DUMP = 4, 34 | PMAPPROC_CALLIT = 5, 35 | INVALID, 36 | } 37 | 38 | pub fn handle_portmap( 39 | xid: u32, 40 | call: call_body, 41 | input: &mut impl Read, 42 | output: &mut impl Write, 43 | context: &RPCContext, 44 | ) -> Result<(), anyhow::Error> { 45 | if call.vers != portmap::VERSION { 46 | error!( 47 | "Invalid Portmap Version number {} != {}", 48 | call.vers, 49 | portmap::VERSION 50 | ); 51 | prog_mismatch_reply_message(xid, portmap::VERSION).serialize(output)?; 52 | return Ok(()); 53 | } 54 | let prog = PortmapProgram::from_u32(call.proc).unwrap_or(PortmapProgram::INVALID); 55 | 56 | match prog { 57 | PortmapProgram::PMAPPROC_NULL => pmapproc_null(xid, input, output)?, 58 | PortmapProgram::PMAPPROC_GETPORT => pmapproc_getport(xid, input, output, context)?, 59 | _ => { 60 | proc_unavail_reply_message(xid).serialize(output)?; 61 | } 62 | } 63 | Ok(()) 64 | } 65 | 66 | pub fn pmapproc_null( 67 | xid: u32, 68 | _: &mut impl Read, 69 | output: &mut impl Write, 70 | ) -> Result<(), anyhow::Error> { 71 | debug!("pmapproc_null({:?}) ", xid); 72 | // build an RPC reply 73 | let msg = make_success_reply(xid); 74 | debug!("\t{:?} --> {:?}", xid, msg); 75 | msg.serialize(output)?; 76 | Ok(()) 77 | } 78 | 79 | /* 80 | * We fake a portmapper here. And always direct back to the same host port 81 | */ 82 | pub fn pmapproc_getport( 83 | xid: u32, 84 | read: &mut impl Read, 85 | output: &mut impl Write, 86 | context: &RPCContext, 87 | ) -> Result<(), anyhow::Error> { 88 | let mut mapping = portmap::mapping::default(); 89 | mapping.deserialize(read)?; 90 | debug!("pmapproc_getport({:?}, {:?}) ", xid, mapping); 91 | make_success_reply(xid).serialize(output)?; 92 | let port = context.local_port as u32; 93 | debug!("\t{:?} --> {:?}", xid, port); 94 | port.serialize(output)?; 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /src/rpc.rs: -------------------------------------------------------------------------------- 1 | // this is just a complete enumeration of everything in the RFC 2 | #![allow(dead_code)] 3 | // And its nice to keep the original RFC names and case 4 | #![allow(non_camel_case_types)] 5 | 6 | use crate::xdr::*; 7 | use byteorder::{ReadBytesExt, WriteBytesExt}; 8 | use num_derive::{FromPrimitive, ToPrimitive}; 9 | use num_traits::cast::FromPrimitive; 10 | use std::io::{Read, Write}; 11 | // Transcribed from RFC 1057 12 | 13 | #[allow(non_camel_case_types)] 14 | #[allow(clippy::upper_case_acronyms)] 15 | #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] 16 | #[repr(u32)] 17 | /// This is only defined as the discriminant for rpc_body and should not 18 | /// be used directly 19 | pub enum _msg_type { 20 | CALL = 0, 21 | REPLY = 1, 22 | } 23 | XDREnumSerde!(_msg_type); 24 | 25 | #[allow(non_camel_case_types)] 26 | #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] 27 | #[repr(u32)] 28 | /// This is only defined as the discriminant for reply_body and should not 29 | /// be used directly 30 | pub enum _reply_stat { 31 | MSG_ACCEPTED = 0, 32 | MSG_DENIED = 1, 33 | } 34 | XDREnumSerde!(_reply_stat); 35 | 36 | #[allow(non_camel_case_types)] 37 | #[allow(clippy::upper_case_acronyms)] 38 | #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] 39 | #[repr(u32)] 40 | /// This is only defined as the discriminant for accept_body and should not 41 | /// be used directly 42 | pub enum _accept_stat { 43 | /// RPC executed successfully 44 | SUCCESS = 0, 45 | /// remote hasn't exported program 46 | PROG_UNAVAIL = 1, 47 | /// remote can't support version # 48 | PROG_MISMATCH = 2, 49 | /// program can't support procedure 50 | PROC_UNAVAIL = 3, 51 | /// procedure can't decode params 52 | GARBAGE_ARGS = 4, 53 | } 54 | XDREnumSerde!(_accept_stat); 55 | 56 | #[allow(non_camel_case_types)] 57 | #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] 58 | #[repr(u32)] 59 | /// This is only defined as the discriminant for reject_body and should not 60 | /// be used directly 61 | pub enum _reject_stat { 62 | /// RPC version number != 2 63 | RPC_MISMATCH = 0, 64 | /// remote can't authenticate caller 65 | AUTH_ERROR = 1, 66 | } 67 | XDREnumSerde!(_reject_stat); 68 | 69 | #[allow(non_camel_case_types)] 70 | #[derive(Copy, Clone, Debug, Default, FromPrimitive, ToPrimitive)] 71 | #[repr(u32)] 72 | /// Why authentication failed 73 | pub enum auth_stat { 74 | /// bad credentials (seal broken) 75 | #[default] 76 | AUTH_BADCRED = 1, 77 | /// client must begin new session 78 | AUTH_REJECTEDCRED = 2, 79 | /// bad verifier (seal broken) 80 | AUTH_BADVERF = 3, 81 | /// verifier expired or replayed 82 | AUTH_REJECTEDVERF = 4, 83 | /// rejected for security reasons 84 | AUTH_TOOWEAK = 5, 85 | } 86 | XDREnumSerde!(auth_stat); 87 | 88 | #[allow(non_camel_case_types)] 89 | #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] 90 | #[repr(u32)] 91 | #[non_exhaustive] 92 | pub enum auth_flavor { 93 | AUTH_NULL = 0, 94 | AUTH_UNIX = 1, 95 | AUTH_SHORT = 2, 96 | AUTH_DES = 3, /* and more to be defined */ 97 | } 98 | XDREnumSerde!(auth_flavor); 99 | 100 | #[allow(non_camel_case_types)] 101 | #[derive(Clone, Debug, Default)] 102 | pub struct auth_unix { 103 | stamp: u32, 104 | machinename: Vec, 105 | uid: u32, 106 | gid: u32, 107 | gids: Vec, 108 | } 109 | XDRStruct!(auth_unix, stamp, machinename, uid, gid, gids); 110 | 111 | ///Provisions for authentication of caller to service and vice-versa are 112 | ///provided as a part of the RPC protocol. The call message has two 113 | ///authentication fields, the credentials and verifier. The reply 114 | ///message has one authentication field, the response verifier. The RPC 115 | ///protocol specification defines all three fields to be the following 116 | ///opaque type (in the eXternal Data Representation (XDR) language [9]): 117 | /// 118 | /// In other words, any "opaque_auth" structure is an "auth_flavor" 119 | ///enumeration followed by bytes which are opaque to (uninterpreted by) 120 | ///the RPC protocol implementation. 121 | /// 122 | ///The interpretation and semantics of the data contained within the 123 | ///authentication fields is specified by individual, independent 124 | ///authentication protocol specifications. (Section 9 defines the 125 | ///various authentication protocols.) 126 | /// 127 | ///If authentication parameters were rejected, the reply message 128 | ///contains information stating why they were rejected. 129 | #[allow(non_camel_case_types)] 130 | #[derive(Clone, Debug)] 131 | pub struct opaque_auth { 132 | pub flavor: auth_flavor, 133 | pub body: Vec, 134 | } 135 | XDRStruct!(opaque_auth, flavor, body); 136 | impl Default for opaque_auth { 137 | fn default() -> opaque_auth { 138 | opaque_auth { 139 | flavor: auth_flavor::AUTH_NULL, 140 | body: Vec::new(), 141 | } 142 | } 143 | } 144 | 145 | #[allow(non_camel_case_types)] 146 | #[derive(Clone, Debug, Default)] 147 | ///All messages start with a transaction identifier, xid, followed by a 148 | ///two-armed discriminated union. The union's discriminant is a 149 | ///msg_type which switches to one of the two types of the message. The 150 | ///xid of a REPLY message always matches that of the initiating CALL 151 | ///message. NB: The xid field is only used for clients matching reply 152 | ///messages with call messages or for servers detecting retransmissions; 153 | ///the service side cannot treat this id as any type of sequence number. 154 | pub struct rpc_msg { 155 | pub xid: u32, 156 | pub body: rpc_body, 157 | } 158 | XDRStruct!(rpc_msg, xid, body); 159 | 160 | #[allow(non_camel_case_types)] 161 | #[allow(clippy::upper_case_acronyms)] 162 | #[derive(Clone, Debug)] 163 | #[repr(u32)] 164 | /// Discriminant is msg_type 165 | pub enum rpc_body { 166 | CALL(call_body), 167 | REPLY(reply_body), 168 | } 169 | 170 | impl Default for rpc_body { 171 | fn default() -> rpc_body { 172 | rpc_body::CALL(call_body::default()) 173 | } 174 | } 175 | impl XDR for rpc_body { 176 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 177 | match self { 178 | rpc_body::CALL(v) => { 179 | 0_u32.serialize(dest)?; 180 | v.serialize(dest)?; 181 | } 182 | rpc_body::REPLY(v) => { 183 | 1_u32.serialize(dest)?; 184 | v.serialize(dest)?; 185 | } 186 | } 187 | Ok(()) 188 | } 189 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 190 | let mut c: u32 = 0; 191 | c.deserialize(src)?; 192 | if c == 0 { 193 | let mut r = call_body::default(); 194 | r.deserialize(src)?; 195 | *self = rpc_body::CALL(r); 196 | } else if c == 1 { 197 | let mut r = reply_body::default(); 198 | r.deserialize(src)?; 199 | *self = rpc_body::REPLY(r); 200 | } 201 | Ok(()) 202 | } 203 | } 204 | 205 | #[allow(non_camel_case_types)] 206 | #[derive(Clone, Debug, Default)] 207 | 208 | ///The RPC call message has three unsigned integer fields -- remote 209 | ///program number, remote program version number, and remote procedure 210 | ///number -- which uniquely identify the procedure to be called. 211 | ///Program numbers are administered by some central authority (like 212 | ///Sun). Once implementors have a program number, they can implement 213 | ///their remote program; the first implementation would most likely have 214 | ///the version number 1. Because most new protocols evolve, a version 215 | ///field of the call message identifies which version of the protocol 216 | ///the caller is using. Version numbers make speaking old and new 217 | ///protocols through the same server process possible. 218 | /// 219 | ///The procedure number identifies the procedure to be called. These 220 | ///numbers are documented in the specific program's protocol 221 | ///specification. For example, a file service's protocol specification 222 | ///may state that its procedure number 5 is "read" and procedure number 223 | ///12 is "write". 224 | /// 225 | ///Just as remote program protocols may change over several versions, 226 | ///the actual RPC message protocol could also change. Therefore, the 227 | ///call message also has in it the RPC version number, which is always 228 | ///equal to two for the version of RPC described here. 229 | /// 230 | ///The reply message to a request message has enough information to 231 | ///distinguish the following error conditions: 232 | /// 233 | ///(1) The remote implementation of RPC does not speak protocol version 234 | /// 2. The lowest and highest supported RPC version numbers are returned. 235 | /// 236 | ///(2) The remote program is not available on the remote system. 237 | /// 238 | ///(3) The remote program does not support the requested version number. 239 | ///The lowest and highest supported remote program version numbers are 240 | ///returned. 241 | /// 242 | ///(4) The requested procedure number does not exist. (This is usually 243 | ///a client side protocol or programming error.) 244 | /// 245 | ///(5) The parameters to the remote procedure appear to be garbage from 246 | ///the server's point of view. (Again, this is usually caused by a 247 | ///disagreement about the protocol between client and service.) 248 | /// 249 | /// In version 2 of the RPC protocol specification, rpcvers must be equal 250 | ///to 2. The fields prog, vers, and proc specify the remote program, 251 | ///its version number, and the procedure within the remote program to be 252 | ///called. After these fields are two authentication parameters: cred 253 | ///(authentication credentials) and verf (authentication verifier). The 254 | ///two authentication parameters are followed by the parameters to the 255 | ///remote procedure, which are specified by the specific program 256 | ///protocol. 257 | pub struct call_body { 258 | /// Must be = 2 259 | pub rpcvers: u32, 260 | pub prog: u32, 261 | pub vers: u32, 262 | pub proc: u32, 263 | pub cred: opaque_auth, 264 | pub verf: opaque_auth, 265 | /* procedure specific parameters start here */ 266 | } 267 | XDRStruct!(call_body, rpcvers, prog, vers, proc, cred, verf); 268 | #[allow(non_camel_case_types)] 269 | #[derive(Clone, Debug)] 270 | #[repr(u32)] 271 | 272 | pub enum reply_body { 273 | MSG_ACCEPTED(accepted_reply), 274 | MSG_DENIED(rejected_reply), 275 | } 276 | impl Default for reply_body { 277 | fn default() -> reply_body { 278 | reply_body::MSG_ACCEPTED(accepted_reply::default()) 279 | } 280 | } 281 | 282 | impl XDR for reply_body { 283 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 284 | match self { 285 | reply_body::MSG_ACCEPTED(v) => { 286 | 0_u32.serialize(dest)?; 287 | v.serialize(dest)?; 288 | } 289 | reply_body::MSG_DENIED(v) => { 290 | 1_u32.serialize(dest)?; 291 | v.serialize(dest)?; 292 | } 293 | } 294 | Ok(()) 295 | } 296 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 297 | let mut c: u32 = 0; 298 | c.deserialize(src)?; 299 | if c == 0 { 300 | let mut r = accepted_reply::default(); 301 | r.deserialize(src)?; 302 | *self = reply_body::MSG_ACCEPTED(r); 303 | } else if c == 1 { 304 | let mut r = rejected_reply::default(); 305 | r.deserialize(src)?; 306 | *self = reply_body::MSG_DENIED(r); 307 | } 308 | Ok(()) 309 | } 310 | } 311 | 312 | #[allow(non_camel_case_types)] 313 | #[derive(Copy, Clone, Debug, Default)] 314 | #[repr(C)] 315 | pub struct mismatch_info { 316 | pub low: u32, 317 | pub high: u32, 318 | } 319 | XDRStruct!(mismatch_info, low, high); 320 | 321 | ///Reply to an RPC call that was accepted by the server: 322 | ///There could be an error even though the call was accepted. The first 323 | ///field is an authentication verifier that the server generates in 324 | ///order to validate itself to the client. It is followed by a union 325 | ///whose discriminant is an enum accept_stat. The SUCCESS arm of the 326 | ///union is protocol specific. The PROG_UNAVAIL, PROC_UNAVAIL, and 327 | ///GARBAGE_ARGS arms of the union are void. The PROG_MISMATCH arm 328 | ///specifies the lowest and highest version numbers of the remote 329 | ///program supported by the server. 330 | /// Discriminant is reply_stat 331 | #[allow(non_camel_case_types)] 332 | #[derive(Clone, Debug, Default)] 333 | pub struct accepted_reply { 334 | pub verf: opaque_auth, 335 | pub reply_data: accept_body, 336 | } 337 | XDRStruct!(accepted_reply, verf, reply_data); 338 | 339 | #[allow(non_camel_case_types)] 340 | #[allow(clippy::upper_case_acronyms)] 341 | #[derive(Copy, Clone, Debug, Default)] 342 | #[repr(u32)] 343 | /// Discriminant is accept_stat 344 | pub enum accept_body { 345 | #[default] 346 | SUCCESS, 347 | PROG_UNAVAIL, 348 | /// remote can't support version # 349 | PROG_MISMATCH(mismatch_info), 350 | PROC_UNAVAIL, 351 | /// procedure can't decode params 352 | GARBAGE_ARGS, 353 | } 354 | impl XDR for accept_body { 355 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 356 | match self { 357 | accept_body::SUCCESS => { 358 | 0_u32.serialize(dest)?; 359 | } 360 | accept_body::PROG_UNAVAIL => { 361 | 1_u32.serialize(dest)?; 362 | } 363 | accept_body::PROG_MISMATCH(v) => { 364 | 2_u32.serialize(dest)?; 365 | v.serialize(dest)?; 366 | } 367 | accept_body::PROC_UNAVAIL => { 368 | 3_u32.serialize(dest)?; 369 | } 370 | accept_body::GARBAGE_ARGS => { 371 | 4_u32.serialize(dest)?; 372 | } 373 | } 374 | Ok(()) 375 | } 376 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 377 | let mut c: u32 = 0; 378 | c.deserialize(src)?; 379 | if c == 0 { 380 | *self = accept_body::SUCCESS; 381 | } else if c == 1 { 382 | *self = accept_body::PROG_UNAVAIL; 383 | } else if c == 2 { 384 | let mut r = mismatch_info::default(); 385 | r.deserialize(src)?; 386 | *self = accept_body::PROG_MISMATCH(r); 387 | } else if c == 3 { 388 | *self = accept_body::PROC_UNAVAIL; 389 | } else { 390 | *self = accept_body::GARBAGE_ARGS; 391 | } 392 | Ok(()) 393 | } 394 | } 395 | 396 | #[allow(non_camel_case_types)] 397 | #[derive(Clone, Debug)] 398 | #[repr(u32)] 399 | ///Reply to an RPC call that was rejected by the server: 400 | /// 401 | ///The call can be rejected for two reasons: either the server is not 402 | ///running a compatible version of the RPC protocol (RPC_MISMATCH), or 403 | ///the server refuses to authenticate the caller (AUTH_ERROR). In case 404 | ///of an RPC version mismatch, the server returns the lowest and highest 405 | ///supported RPC version numbers. In case of refused authentication, 406 | ///failure status is returned. 407 | /// Discriminant is reject_stat 408 | pub enum rejected_reply { 409 | RPC_MISMATCH(mismatch_info), 410 | AUTH_ERROR(auth_stat), 411 | } 412 | 413 | impl Default for rejected_reply { 414 | fn default() -> rejected_reply { 415 | rejected_reply::RPC_MISMATCH(mismatch_info::default()) 416 | } 417 | } 418 | 419 | impl XDR for rejected_reply { 420 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 421 | match self { 422 | rejected_reply::RPC_MISMATCH(v) => { 423 | 0_u32.serialize(dest)?; 424 | v.serialize(dest)?; 425 | } 426 | rejected_reply::AUTH_ERROR(v) => { 427 | 1_u32.serialize(dest)?; 428 | v.serialize(dest)?; 429 | } 430 | } 431 | Ok(()) 432 | } 433 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 434 | let mut c: u32 = 0; 435 | c.deserialize(src)?; 436 | if c == 0 { 437 | let mut r = mismatch_info::default(); 438 | r.deserialize(src)?; 439 | *self = rejected_reply::RPC_MISMATCH(r); 440 | } else if c == 1 { 441 | let mut r = auth_stat::default(); 442 | r.deserialize(src)?; 443 | *self = rejected_reply::AUTH_ERROR(r); 444 | } 445 | Ok(()) 446 | } 447 | } 448 | 449 | pub fn proc_unavail_reply_message(xid: u32) -> rpc_msg { 450 | let reply = reply_body::MSG_ACCEPTED(accepted_reply { 451 | verf: opaque_auth::default(), 452 | reply_data: accept_body::PROC_UNAVAIL, 453 | }); 454 | rpc_msg { 455 | xid, 456 | body: rpc_body::REPLY(reply), 457 | } 458 | } 459 | pub fn prog_unavail_reply_message(xid: u32) -> rpc_msg { 460 | let reply = reply_body::MSG_ACCEPTED(accepted_reply { 461 | verf: opaque_auth::default(), 462 | reply_data: accept_body::PROG_UNAVAIL, 463 | }); 464 | rpc_msg { 465 | xid, 466 | body: rpc_body::REPLY(reply), 467 | } 468 | } 469 | pub fn prog_mismatch_reply_message(xid: u32, accepted_ver: u32) -> rpc_msg { 470 | let reply = reply_body::MSG_ACCEPTED(accepted_reply { 471 | verf: opaque_auth::default(), 472 | reply_data: accept_body::PROG_MISMATCH(mismatch_info { 473 | low: accepted_ver, 474 | high: accepted_ver, 475 | }), 476 | }); 477 | rpc_msg { 478 | xid, 479 | body: rpc_body::REPLY(reply), 480 | } 481 | } 482 | pub fn garbage_args_reply_message(xid: u32) -> rpc_msg { 483 | let reply = reply_body::MSG_ACCEPTED(accepted_reply { 484 | verf: opaque_auth::default(), 485 | reply_data: accept_body::GARBAGE_ARGS, 486 | }); 487 | rpc_msg { 488 | xid, 489 | body: rpc_body::REPLY(reply), 490 | } 491 | } 492 | 493 | pub fn rpc_vers_mismatch(xid: u32) -> rpc_msg { 494 | let reply = reply_body::MSG_DENIED(rejected_reply::RPC_MISMATCH(mismatch_info::default())); 495 | rpc_msg { 496 | xid, 497 | body: rpc_body::REPLY(reply), 498 | } 499 | } 500 | 501 | pub fn make_success_reply(xid: u32) -> rpc_msg { 502 | let reply = reply_body::MSG_ACCEPTED(accepted_reply { 503 | verf: opaque_auth::default(), 504 | reply_data: accept_body::SUCCESS, 505 | }); 506 | rpc_msg { 507 | xid, 508 | body: rpc_body::REPLY(reply), 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /src/rpcwire.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use std::io::Cursor; 3 | use std::io::{Read, Write}; 4 | use tracing::{debug, error, trace, warn}; 5 | 6 | use crate::context::RPCContext; 7 | use crate::rpc::*; 8 | use crate::xdr::*; 9 | 10 | use crate::mount; 11 | use crate::mount_handlers; 12 | 13 | use crate::nfs; 14 | use crate::nfs_handlers; 15 | 16 | use crate::portmap; 17 | use crate::portmap_handlers; 18 | use tokio::io::AsyncReadExt; 19 | use tokio::io::AsyncWriteExt; 20 | use tokio::io::DuplexStream; 21 | use tokio::sync::mpsc; 22 | 23 | // Information from RFC 5531 24 | // https://datatracker.ietf.org/doc/html/rfc5531 25 | 26 | const NFS_ACL_PROGRAM: u32 = 100227; 27 | const NFS_ID_MAP_PROGRAM: u32 = 100270; 28 | const NFS_METADATA_PROGRAM: u32 = 200024; 29 | 30 | async fn handle_rpc( 31 | input: &mut impl Read, 32 | output: &mut impl Write, 33 | mut context: RPCContext, 34 | ) -> Result { 35 | let mut recv = rpc_msg::default(); 36 | recv.deserialize(input)?; 37 | let xid = recv.xid; 38 | if let rpc_body::CALL(call) = recv.body { 39 | if let auth_flavor::AUTH_UNIX = call.cred.flavor { 40 | let mut auth = auth_unix::default(); 41 | auth.deserialize(&mut Cursor::new(&call.cred.body))?; 42 | context.auth = auth; 43 | } 44 | if call.rpcvers != 2 { 45 | warn!("Invalid RPC version {} != 2", call.rpcvers); 46 | rpc_vers_mismatch(xid).serialize(output)?; 47 | return Ok(true); 48 | } 49 | 50 | if context.transaction_tracker.is_retransmission(xid, &context.client_addr) { 51 | // This is a retransmission 52 | // Drop the message and return 53 | debug!("Retransmission detected, xid: {}, client_addr: {}, call: {:?}", xid, context.client_addr, call); 54 | return Ok(false); 55 | } 56 | 57 | let res = { 58 | if call.prog == nfs::PROGRAM { 59 | nfs_handlers::handle_nfs(xid, call, input, output, &context).await 60 | } else if call.prog == portmap::PROGRAM { 61 | portmap_handlers::handle_portmap(xid, call, input, output, &context) 62 | } else if call.prog == mount::PROGRAM { 63 | mount_handlers::handle_mount(xid, call, input, output, &context).await 64 | } else if call.prog == NFS_ACL_PROGRAM 65 | || call.prog == NFS_ID_MAP_PROGRAM 66 | || call.prog == NFS_METADATA_PROGRAM 67 | { 68 | trace!("ignoring NFS_ACL packet"); 69 | prog_unavail_reply_message(xid).serialize(output)?; 70 | Ok(()) 71 | } else { 72 | warn!( 73 | "Unknown RPC Program number {} != {}", 74 | call.prog, 75 | nfs::PROGRAM 76 | ); 77 | prog_unavail_reply_message(xid).serialize(output)?; 78 | Ok(()) 79 | } 80 | }.map(|_| true); 81 | context.transaction_tracker.mark_processed(xid, &context.client_addr); 82 | res 83 | } else { 84 | error!("Unexpectedly received a Reply instead of a Call"); 85 | Err(anyhow!("Bad RPC Call format")) 86 | } 87 | } 88 | 89 | /// RFC 1057 Section 10 90 | /// When RPC messages are passed on top of a byte stream transport 91 | /// protocol (like TCP), it is necessary to delimit one message from 92 | /// another in order to detect and possibly recover from protocol errors. 93 | /// This is called record marking (RM). Sun uses this RM/TCP/IP 94 | /// transport for passing RPC messages on TCP streams. One RPC message 95 | /// fits into one RM record. 96 | /// 97 | /// A record is composed of one or more record fragments. A record 98 | /// fragment is a four-byte header followed by 0 to (2**31) - 1 bytes of 99 | /// fragment data. The bytes encode an unsigned binary number; as with 100 | /// XDR integers, the byte order is from highest to lowest. The number 101 | /// encodes two values -- a boolean which indicates whether the fragment 102 | /// is the last fragment of the record (bit value 1 implies the fragment 103 | /// is the last fragment) and a 31-bit unsigned binary value which is the 104 | /// length in bytes of the fragment's data. The boolean value is the 105 | /// highest-order bit of the header; the length is the 31 low-order bits. 106 | /// (Note that this record specification is NOT in XDR standard form!) 107 | async fn read_fragment( 108 | socket: &mut DuplexStream, 109 | append_to: &mut Vec, 110 | ) -> Result { 111 | let mut header_buf = [0_u8; 4]; 112 | socket.read_exact(&mut header_buf).await?; 113 | let fragment_header = u32::from_be_bytes(header_buf); 114 | let is_last = (fragment_header & (1 << 31)) > 0; 115 | let length = (fragment_header & ((1 << 31) - 1)) as usize; 116 | trace!("Reading fragment length:{}, last:{}", length, is_last); 117 | let start_offset = append_to.len(); 118 | append_to.resize(append_to.len() + length, 0); 119 | socket.read_exact(&mut append_to[start_offset..]).await?; 120 | trace!( 121 | "Finishing Reading fragment length:{}, last:{}", 122 | length, 123 | is_last 124 | ); 125 | Ok(is_last) 126 | } 127 | 128 | pub async fn write_fragment( 129 | socket: &mut tokio::net::TcpStream, 130 | buf: &Vec, 131 | ) -> Result<(), anyhow::Error> { 132 | // TODO: split into many fragments 133 | assert!(buf.len() < (1 << 31)); 134 | // set the last flag 135 | let fragment_header = buf.len() as u32 + (1 << 31); 136 | let header_buf = u32::to_be_bytes(fragment_header); 137 | socket.write_all(&header_buf).await?; 138 | trace!("Writing fragment length:{}", buf.len()); 139 | socket.write_all(buf).await?; 140 | Ok(()) 141 | } 142 | 143 | pub type SocketMessageType = Result, anyhow::Error>; 144 | 145 | /// The Socket Message Handler reads from a TcpStream and spawns off 146 | /// subtasks to handle each message. replies are queued into the 147 | /// reply_send_channel. 148 | #[derive(Debug)] 149 | pub struct SocketMessageHandler { 150 | cur_fragment: Vec, 151 | socket_receive_channel: DuplexStream, 152 | reply_send_channel: mpsc::UnboundedSender, 153 | context: RPCContext, 154 | } 155 | 156 | impl SocketMessageHandler { 157 | /// Creates a new SocketMessageHandler with the receiver for queued message replies 158 | pub fn new( 159 | context: &RPCContext, 160 | ) -> ( 161 | Self, 162 | DuplexStream, 163 | mpsc::UnboundedReceiver, 164 | ) { 165 | let (socksend, sockrecv) = tokio::io::duplex(256000); 166 | let (msgsend, msgrecv) = mpsc::unbounded_channel(); 167 | ( 168 | Self { 169 | cur_fragment: Vec::new(), 170 | socket_receive_channel: sockrecv, 171 | reply_send_channel: msgsend, 172 | context: context.clone(), 173 | }, 174 | socksend, 175 | msgrecv, 176 | ) 177 | } 178 | 179 | /// Reads a fragment from the socket. This should be looped. 180 | pub async fn read(&mut self) -> Result<(), anyhow::Error> { 181 | let is_last = 182 | read_fragment(&mut self.socket_receive_channel, &mut self.cur_fragment).await?; 183 | if is_last { 184 | let fragment = std::mem::take(&mut self.cur_fragment); 185 | let context = self.context.clone(); 186 | let send = self.reply_send_channel.clone(); 187 | tokio::spawn(async move { 188 | let mut write_buf: Vec = Vec::new(); 189 | let mut write_cursor = Cursor::new(&mut write_buf); 190 | let maybe_reply = 191 | handle_rpc(&mut Cursor::new(fragment), &mut write_cursor, context).await; 192 | match maybe_reply { 193 | Err(e) => { 194 | error!("RPC Error: {:?}", e); 195 | let _ = send.send(Err(e)); 196 | } 197 | Ok(true) => { 198 | let _ = std::io::Write::flush(&mut write_cursor); 199 | let _ = send.send(Ok(write_buf)); 200 | } 201 | Ok(false) => { 202 | // do not reply 203 | } 204 | } 205 | }); 206 | } 207 | Ok(()) 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/tcp.rs: -------------------------------------------------------------------------------- 1 | use crate::context::RPCContext; 2 | use crate::rpcwire::*; 3 | use crate::vfs::NFSFileSystem; 4 | use anyhow; 5 | use async_trait::async_trait; 6 | use std::net::SocketAddr; 7 | use std::sync::Arc; 8 | use std::{io, net::IpAddr}; 9 | use std::time::Duration; 10 | use tokio::io::AsyncWriteExt; 11 | use tokio::net::TcpListener; 12 | use tokio::sync::mpsc; 13 | use tracing::{debug, error, info}; 14 | use crate::transaction_tracker::TransactionTracker; 15 | 16 | /// A NFS Tcp Connection Handler 17 | pub struct NFSTcpListener { 18 | listener: TcpListener, 19 | port: u16, 20 | arcfs: Arc, 21 | mount_signal: Option>, 22 | export_name: Arc, 23 | transaction_tracker: Arc, 24 | } 25 | 26 | pub fn generate_host_ip(hostnum: u16) -> String { 27 | format!( 28 | "127.88.{}.{}", 29 | ((hostnum >> 8) & 0xFF) as u8, 30 | (hostnum & 0xFF) as u8 31 | ) 32 | } 33 | 34 | /// processes an established socket 35 | async fn process_socket( 36 | mut socket: tokio::net::TcpStream, 37 | context: RPCContext, 38 | ) -> Result<(), anyhow::Error> { 39 | let (mut message_handler, mut socksend, mut msgrecvchan) = SocketMessageHandler::new(&context); 40 | let _ = socket.set_nodelay(true); 41 | 42 | tokio::spawn(async move { 43 | loop { 44 | if let Err(e) = message_handler.read().await { 45 | debug!("Message loop broken due to {:?}", e); 46 | break; 47 | } 48 | } 49 | }); 50 | loop { 51 | tokio::select! { 52 | _ = socket.readable() => { 53 | let mut buf = [0; 128000]; 54 | 55 | match socket.try_read(&mut buf) { 56 | Ok(0) => { 57 | return Ok(()); 58 | } 59 | Ok(n) => { 60 | let _ = socksend.write_all(&buf[..n]).await; 61 | } 62 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { 63 | continue; 64 | } 65 | Err(e) => { 66 | debug!("Message handling closed : {:?}", e); 67 | return Err(e.into()); 68 | } 69 | } 70 | 71 | }, 72 | reply = msgrecvchan.recv() => { 73 | match reply { 74 | Some(Err(e)) => { 75 | debug!("Message handling closed : {:?}", e); 76 | return Err(e); 77 | } 78 | Some(Ok(msg)) => { 79 | if let Err(e) = write_fragment(&mut socket, &msg).await { 80 | error!("Write error {:?}", e); 81 | } 82 | } 83 | None => { 84 | return Err(anyhow::anyhow!("Unexpected socket context termination")); 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | #[async_trait] 93 | pub trait NFSTcp: Send + Sync { 94 | /// Gets the true listening port. Useful if the bound port number is 0 95 | fn get_listen_port(&self) -> u16; 96 | 97 | /// Gets the true listening IP. Useful on windows when the IP may be random 98 | fn get_listen_ip(&self) -> IpAddr; 99 | 100 | /// Sets a mount listener. A "true" signal will be sent on a mount 101 | /// and a "false" will be sent on an unmount 102 | fn set_mount_listener(&mut self, signal: mpsc::Sender); 103 | 104 | /// Loops forever and never returns handling all incoming connections. 105 | async fn handle_forever(&self) -> io::Result<()>; 106 | } 107 | 108 | impl NFSTcpListener { 109 | /// Binds to a ipstr of the form [ip address]:port. For instance 110 | /// "127.0.0.1:12000". fs is an instance of an implementation 111 | /// of NFSFileSystem. 112 | pub async fn bind(ipstr: &str, fs: T) -> io::Result> { 113 | let (ip, port) = ipstr.split_once(':').ok_or_else(|| { 114 | io::Error::new( 115 | io::ErrorKind::AddrNotAvailable, 116 | "IP Address must be of form ip:port", 117 | ) 118 | })?; 119 | let port = port.parse::().map_err(|_| { 120 | io::Error::new( 121 | io::ErrorKind::AddrNotAvailable, 122 | "Port not in range 0..=65535", 123 | ) 124 | })?; 125 | 126 | let arcfs: Arc = Arc::new(fs); 127 | 128 | if ip == "auto" { 129 | let mut num_tries_left = 32; 130 | 131 | for try_ip in 1u16.. { 132 | let ip = generate_host_ip(try_ip); 133 | 134 | let result = NFSTcpListener::bind_internal(&ip, port, arcfs.clone()).await; 135 | 136 | match &result { 137 | Err(_) => { 138 | if num_tries_left == 0 { 139 | return result; 140 | } else { 141 | num_tries_left -= 1; 142 | continue; 143 | } 144 | } 145 | Ok(_) => { 146 | return result; 147 | } 148 | } 149 | } 150 | unreachable!(); // Does not detect automatically that loop above never terminates. 151 | } else { 152 | // Otherwise, try this. 153 | NFSTcpListener::bind_internal(ip, port, arcfs).await 154 | } 155 | } 156 | 157 | async fn bind_internal(ip: &str, port: u16, arcfs: Arc) -> io::Result> { 158 | let ipstr = format!("{ip}:{port}"); 159 | let listener = TcpListener::bind(&ipstr).await?; 160 | info!("Listening on {:?}", &ipstr); 161 | 162 | let port = match listener.local_addr().unwrap() { 163 | SocketAddr::V4(s) => s.port(), 164 | SocketAddr::V6(s) => s.port(), 165 | }; 166 | Ok(NFSTcpListener { 167 | listener, 168 | port, 169 | arcfs, 170 | mount_signal: None, 171 | export_name: Arc::from("/".to_string()), 172 | transaction_tracker: Arc::new(TransactionTracker::new(Duration::from_secs(60))), 173 | }) 174 | } 175 | 176 | /// Sets an optional NFS export name. 177 | /// 178 | /// - `export_name`: The desired export name without slashes. 179 | /// 180 | /// Example: Name `foo` results in the export path `/foo`. 181 | /// Default path is `/` if not set. 182 | pub fn with_export_name>(&mut self, export_name: S) { 183 | self.export_name = Arc::new(format!( 184 | "/{}", 185 | export_name 186 | .as_ref() 187 | .trim_end_matches('/') 188 | .trim_start_matches('/') 189 | )) 190 | } 191 | } 192 | 193 | #[async_trait] 194 | impl NFSTcp for NFSTcpListener { 195 | /// Gets the true listening port. Useful if the bound port number is 0 196 | fn get_listen_port(&self) -> u16 { 197 | let addr = self.listener.local_addr().unwrap(); 198 | addr.port() 199 | } 200 | 201 | fn get_listen_ip(&self) -> IpAddr { 202 | let addr = self.listener.local_addr().unwrap(); 203 | addr.ip() 204 | } 205 | 206 | /// Sets a mount listener. A "true" signal will be sent on a mount 207 | /// and a "false" will be sent on an unmount 208 | fn set_mount_listener(&mut self, signal: mpsc::Sender) { 209 | self.mount_signal = Some(signal); 210 | } 211 | 212 | /// Loops forever and never returns handling all incoming connections. 213 | async fn handle_forever(&self) -> io::Result<()> { 214 | loop { 215 | let (socket, _) = self.listener.accept().await?; 216 | let context = RPCContext { 217 | local_port: self.port, 218 | client_addr: socket.peer_addr().unwrap().to_string(), 219 | auth: crate::rpc::auth_unix::default(), 220 | vfs: self.arcfs.clone(), 221 | mount_signal: self.mount_signal.clone(), 222 | export_name: self.export_name.clone(), 223 | transaction_tracker: self.transaction_tracker.clone(), 224 | }; 225 | info!("Accepting connection from {}", context.client_addr); 226 | debug!("Accepting socket {:?} {:?}", socket, context); 227 | tokio::spawn(async move { 228 | let _ = process_socket(socket, context).await; 229 | }); 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/transaction_tracker.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap}; 2 | use std::sync::Mutex; 3 | use std::time::{Duration, SystemTime}; 4 | 5 | /// `TransactionTracker` tracks the state of transactions to detect retransmissions. 6 | pub struct TransactionTracker { 7 | retention_period: Duration, 8 | transactions: Mutex>, 9 | } 10 | 11 | impl TransactionTracker { 12 | pub fn new(retention_period: Duration) -> Self { 13 | Self { 14 | retention_period, 15 | transactions: Mutex::new(HashMap::new()), 16 | } 17 | } 18 | 19 | /// Checks if the transaction is a retransmission. 20 | /// If it's a new transaction, it is marked as `InProgress`. 21 | /// 22 | /// Returns `true` if the transaction is a retransmission, `false` otherwise. 23 | pub fn is_retransmission(&self, xid: u32, client_addr: &str) -> bool { 24 | let key = (xid, client_addr.to_string()); 25 | let mut transactions = self.transactions.lock().expect("unable to unlock transactions mutex"); 26 | housekeeping(&mut transactions, self.retention_period); 27 | if transactions.contains_key(&key) { 28 | true 29 | } else { 30 | transactions.insert(key, TransactionState::InProgress); 31 | false 32 | } 33 | } 34 | 35 | /// Marks the transaction as processed. 36 | pub fn mark_processed(&self, xid: u32, client_addr: &str) { 37 | let key = (xid, client_addr.to_string()); 38 | let completion_time = SystemTime::now(); 39 | let mut transactions = self.transactions.lock().expect("unable to unlock transactions mutex"); 40 | if let Some(tx) = transactions.get_mut(&key) { 41 | *tx = TransactionState::Completed(completion_time); 42 | } 43 | } 44 | } 45 | 46 | fn housekeeping(transactions: &mut HashMap<(u32, String), TransactionState>, max_age: Duration) { 47 | let mut cutoff = SystemTime::now() - max_age; 48 | transactions.retain(|_, v| match v { 49 | TransactionState::InProgress => true, 50 | TransactionState::Completed(completion_time) => completion_time >= &mut cutoff, 51 | }); 52 | } 53 | 54 | pub enum TransactionState { 55 | InProgress, 56 | Completed(SystemTime), 57 | } 58 | -------------------------------------------------------------------------------- /src/vfs.rs: -------------------------------------------------------------------------------- 1 | use crate::nfs::*; 2 | use crate::nfs; 3 | use async_trait::async_trait; 4 | use std::cmp::Ordering; 5 | use std::sync::Once; 6 | use std::time::SystemTime; 7 | #[derive(Default, Debug)] 8 | pub struct DirEntrySimple { 9 | pub fileid: fileid3, 10 | pub name: filename3, 11 | } 12 | #[derive(Default, Debug)] 13 | pub struct ReadDirSimpleResult { 14 | pub entries: Vec, 15 | pub end: bool, 16 | } 17 | 18 | #[derive(Default, Debug)] 19 | pub struct DirEntry { 20 | pub fileid: fileid3, 21 | pub name: filename3, 22 | pub attr: fattr3, 23 | } 24 | #[derive(Default, Debug)] 25 | pub struct ReadDirResult { 26 | pub entries: Vec, 27 | pub end: bool, 28 | } 29 | 30 | impl ReadDirSimpleResult { 31 | fn from_readdir_result(result: &ReadDirResult) -> ReadDirSimpleResult { 32 | let entries: Vec = result 33 | .entries 34 | .iter() 35 | .map(|e| DirEntrySimple { 36 | fileid: e.fileid, 37 | name: e.name.clone(), 38 | }) 39 | .collect(); 40 | ReadDirSimpleResult { 41 | entries, 42 | end: result.end, 43 | } 44 | } 45 | } 46 | 47 | static mut GENERATION_NUMBER: u64 = 0; 48 | static GENERATION_NUMBER_INIT: Once = Once::new(); 49 | 50 | fn get_generation_number() -> u64 { 51 | unsafe { 52 | GENERATION_NUMBER_INIT.call_once(|| { 53 | GENERATION_NUMBER = SystemTime::now() 54 | .duration_since(std::time::UNIX_EPOCH) 55 | .unwrap() 56 | .as_millis() as u64; 57 | }); 58 | GENERATION_NUMBER 59 | } 60 | } 61 | 62 | /// What capabilities are supported 63 | pub enum VFSCapabilities { 64 | ReadOnly, 65 | ReadWrite, 66 | } 67 | 68 | /// The basic API to implement to provide an NFS file system 69 | /// 70 | /// Opaque FH 71 | /// --------- 72 | /// Files are only uniquely identified by a 64-bit file id. (basically an inode number) 73 | /// We automatically produce internally the opaque filehandle which is comprised of 74 | /// - A 64-bit generation number derived from the server startup time 75 | /// (i.e. so the opaque file handle expires when the NFS server restarts) 76 | /// - The 64-bit file id 77 | // 78 | /// readdir pagination 79 | /// ------------------ 80 | /// We do not use cookie verifier. We just use the start_after. The 81 | /// implementation should allow startat to start at any position. That is, 82 | /// the next query to readdir may be the last entry in the previous readdir 83 | /// response. 84 | // 85 | /// There is a wierd annoying thing about readdir that limits the number 86 | /// of bytes in the response (instead of the number of entries). The caller 87 | /// will have to truncate the readdir response / issue more calls to readdir 88 | /// accordingly to fill up the expected number of bytes without exceeding it. 89 | // 90 | /// Other requirements 91 | /// ------------------ 92 | /// getattr needs to be fast. NFS uses that a lot 93 | // 94 | /// The 0 fileid is reserved and should not be used 95 | /// 96 | #[async_trait] 97 | pub trait NFSFileSystem: Sync { 98 | /// Returns the set of capabilities supported 99 | fn capabilities(&self) -> VFSCapabilities; 100 | /// Returns the ID the of the root directory "/" 101 | fn root_dir(&self) -> fileid3; 102 | /// Look up the id of a path in a directory 103 | /// 104 | /// i.e. given a directory dir/ containing a file a.txt 105 | /// this may call lookup(id_of("dir/"), "a.txt") 106 | /// and this should return the id of the file "dir/a.txt" 107 | /// 108 | /// This method should be fast as it is used very frequently. 109 | async fn lookup(&self, dirid: fileid3, filename: &filename3) -> Result; 110 | 111 | /// Returns the attributes of an id. 112 | /// This method should be fast as it is used very frequently. 113 | async fn getattr(&self, id: fileid3) -> Result; 114 | 115 | /// Sets the attributes of an id 116 | /// this should return Err(nfsstat3::NFS3ERR_ROFS) if readonly 117 | async fn setattr(&self, id: fileid3, setattr: sattr3) -> Result; 118 | 119 | /// Reads the contents of a file returning (bytes, EOF) 120 | /// Note that offset/count may go past the end of the file and that 121 | /// in that case, all bytes till the end of file are returned. 122 | /// EOF must be flagged if the end of the file is reached by the read. 123 | async fn read(&self, id: fileid3, offset: u64, count: u32) 124 | -> Result<(Vec, bool), nfsstat3>; 125 | 126 | /// Writes the contents of a file returning (bytes, EOF) 127 | /// Note that offset/count may go past the end of the file and that 128 | /// in that case, the file is extended. 129 | /// If not supported due to readonly file system 130 | /// this should return Err(nfsstat3::NFS3ERR_ROFS) 131 | async fn write(&self, id: fileid3, offset: u64, data: &[u8]) -> Result; 132 | 133 | /// Creates a file with the following attributes. 134 | /// If not supported due to readonly file system 135 | /// this should return Err(nfsstat3::NFS3ERR_ROFS) 136 | async fn create( 137 | &self, 138 | dirid: fileid3, 139 | filename: &filename3, 140 | attr: sattr3, 141 | ) -> Result<(fileid3, fattr3), nfsstat3>; 142 | 143 | /// Creates a file if it does not already exist 144 | /// this should return Err(nfsstat3::NFS3ERR_ROFS) 145 | async fn create_exclusive( 146 | &self, 147 | dirid: fileid3, 148 | filename: &filename3, 149 | ) -> Result; 150 | 151 | /// Makes a directory with the following attributes. 152 | /// If not supported dur to readonly file system 153 | /// this should return Err(nfsstat3::NFS3ERR_ROFS) 154 | async fn mkdir( 155 | &self, 156 | dirid: fileid3, 157 | dirname: &filename3, 158 | ) -> Result<(fileid3, fattr3), nfsstat3>; 159 | 160 | /// Removes a file. 161 | /// If not supported due to readonly file system 162 | /// this should return Err(nfsstat3::NFS3ERR_ROFS) 163 | async fn remove(&self, dirid: fileid3, filename: &filename3) -> Result<(), nfsstat3>; 164 | 165 | /// Removes a file. 166 | /// If not supported due to readonly file system 167 | /// this should return Err(nfsstat3::NFS3ERR_ROFS) 168 | async fn rename( 169 | &self, 170 | from_dirid: fileid3, 171 | from_filename: &filename3, 172 | to_dirid: fileid3, 173 | to_filename: &filename3, 174 | ) -> Result<(), nfsstat3>; 175 | 176 | /// Returns the contents of a directory with pagination. 177 | /// Directory listing should be deterministic. 178 | /// Up to max_entries may be returned, and start_after is used 179 | /// to determine where to start returning entries from. 180 | /// 181 | /// For instance if the directory has entry with ids [1,6,2,11,8,9] 182 | /// and start_after=6, readdir should returning 2,11,8,... 183 | // 184 | async fn readdir( 185 | &self, 186 | dirid: fileid3, 187 | start_after: fileid3, 188 | max_entries: usize, 189 | ) -> Result; 190 | 191 | /// Simple version of readdir. 192 | /// Only need to return filename and id 193 | async fn readdir_simple( 194 | &self, 195 | dirid: fileid3, 196 | count: usize, 197 | ) -> Result { 198 | Ok(ReadDirSimpleResult::from_readdir_result( 199 | &self.readdir(dirid, 0, count).await?, 200 | )) 201 | } 202 | 203 | /// Makes a symlink with the following attributes. 204 | /// If not supported due to readonly file system 205 | /// this should return Err(nfsstat3::NFS3ERR_ROFS) 206 | async fn symlink( 207 | &self, 208 | dirid: fileid3, 209 | linkname: &filename3, 210 | symlink: &nfspath3, 211 | attr: &sattr3, 212 | ) -> Result<(fileid3, fattr3), nfsstat3>; 213 | 214 | /// Reads a symlink 215 | async fn readlink(&self, id: fileid3) -> Result; 216 | 217 | /// Get static file system Information 218 | async fn fsinfo( 219 | &self, 220 | root_fileid: fileid3, 221 | ) -> Result { 222 | 223 | let dir_attr: nfs::post_op_attr = match self.getattr(root_fileid).await { 224 | Ok(v) => nfs::post_op_attr::attributes(v), 225 | Err(_) => nfs::post_op_attr::Void, 226 | }; 227 | 228 | let res = fsinfo3 { 229 | obj_attributes: dir_attr, 230 | rtmax: 1024 * 1024, 231 | rtpref: 1024 * 124, 232 | rtmult: 1024 * 1024, 233 | wtmax: 1024 * 1024, 234 | wtpref: 1024 * 1024, 235 | wtmult: 1024 * 1024, 236 | dtpref: 1024 * 1024, 237 | maxfilesize: 128 * 1024 * 1024 * 1024, 238 | time_delta: nfs::nfstime3 { 239 | seconds: 0, 240 | nseconds: 1000000, 241 | }, 242 | properties: nfs::FSF_SYMLINK | nfs::FSF_HOMOGENEOUS | nfs::FSF_CANSETTIME, 243 | }; 244 | Ok(res) 245 | } 246 | 247 | /// Converts the fileid to an opaque NFS file handle. Optional. 248 | fn id_to_fh(&self, id: fileid3) -> nfs_fh3 { 249 | let gennum = get_generation_number(); 250 | let mut ret: Vec = Vec::new(); 251 | ret.extend_from_slice(&gennum.to_le_bytes()); 252 | ret.extend_from_slice(&id.to_le_bytes()); 253 | nfs_fh3 { data: ret } 254 | } 255 | /// Converts an opaque NFS file handle to a fileid. Optional. 256 | fn fh_to_id(&self, id: &nfs_fh3) -> Result { 257 | if id.data.len() != 16 { 258 | return Err(nfsstat3::NFS3ERR_BADHANDLE); 259 | } 260 | let gen = u64::from_le_bytes(id.data[0..8].try_into().unwrap()); 261 | let id = u64::from_le_bytes(id.data[8..16].try_into().unwrap()); 262 | let gennum = get_generation_number(); 263 | match gen.cmp(&gennum) { 264 | Ordering::Less => Err(nfsstat3::NFS3ERR_STALE), 265 | Ordering::Greater => Err(nfsstat3::NFS3ERR_BADHANDLE), 266 | Ordering::Equal => Ok(id), 267 | } 268 | } 269 | /// Converts a complete path to a fileid. Optional. 270 | /// The default implementation walks the directory structure with lookup() 271 | async fn path_to_id(&self, path: &[u8]) -> Result { 272 | let splits = path.split(|&r| r == b'/'); 273 | let mut fid = self.root_dir(); 274 | for component in splits { 275 | if component.is_empty() { 276 | continue; 277 | } 278 | fid = self.lookup(fid, &component.into()).await?; 279 | } 280 | Ok(fid) 281 | } 282 | 283 | fn serverid(&self) -> cookieverf3 { 284 | let gennum = get_generation_number(); 285 | gennum.to_le_bytes() 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/write_counter.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::io::Write; 3 | 4 | // from https://stackoverflow.com/questions/42187591/how-to-keep-track-of-how-many-bytes-written-when-using-stdiowrite 5 | 6 | pub struct WriteCounter { 7 | inner: W, 8 | count: usize, 9 | } 10 | 11 | impl WriteCounter 12 | where 13 | W: Write, 14 | { 15 | pub fn new(inner: W) -> Self { 16 | WriteCounter { inner, count: 0 } 17 | } 18 | 19 | pub fn into_inner(self) -> W { 20 | self.inner 21 | } 22 | 23 | pub fn bytes_written(&self) -> usize { 24 | self.count 25 | } 26 | } 27 | 28 | impl Write for WriteCounter 29 | where 30 | W: Write, 31 | { 32 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 33 | let res = self.inner.write(buf); 34 | if let Ok(size) = res { 35 | self.count += size 36 | } 37 | res 38 | } 39 | 40 | fn flush(&mut self) -> std::io::Result<()> { 41 | self.inner.flush() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/xdr.rs: -------------------------------------------------------------------------------- 1 | use byteorder::BigEndian; 2 | use byteorder::{ReadBytesExt, WriteBytesExt}; 3 | use std::io::{Read, Write}; 4 | pub type XDREndian = BigEndian; 5 | use crate::nfs::nfsstring; 6 | 7 | /// See https://datatracker.ietf.org/doc/html/rfc1014 8 | 9 | #[allow(clippy::upper_case_acronyms)] 10 | pub trait XDR { 11 | fn serialize(&self, dest: &mut R) -> std::io::Result<()>; 12 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()>; 13 | } 14 | 15 | /// Serializes a basic enumeration. 16 | /// Casts everything as u32 BigEndian 17 | #[allow(non_camel_case_types)] 18 | #[macro_export] 19 | macro_rules! XDREnumSerde { 20 | ($t:ident) => { 21 | impl XDR for $t { 22 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 23 | dest.write_u32::(*self as u32) 24 | } 25 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 26 | let r: u32 = src.read_u32::()?; 27 | if let Some(p) = FromPrimitive::from_u32(r) { 28 | *self = p; 29 | } else { 30 | return Err(std::io::Error::new( 31 | std::io::ErrorKind::InvalidData, 32 | format!("Invalid value for {}", stringify!($t)), 33 | )); 34 | } 35 | Ok(()) 36 | } 37 | } 38 | }; 39 | } 40 | 41 | /// Serializes a bool as a 4 byte big endian integer. 42 | impl XDR for bool { 43 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 44 | let val: u32 = *self as u32; 45 | dest.write_u32::(val) 46 | } 47 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 48 | let val: u32 = src.read_u32::()?; 49 | *self = val > 0; 50 | Ok(()) 51 | } 52 | } 53 | 54 | /// Serializes a i32 as a 4 byte big endian integer. 55 | impl XDR for i32 { 56 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 57 | dest.write_i32::(*self) 58 | } 59 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 60 | *self = src.read_i32::()?; 61 | Ok(()) 62 | } 63 | } 64 | 65 | /// Serializes a i64 as a 8 byte big endian integer. 66 | impl XDR for i64 { 67 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 68 | dest.write_i64::(*self) 69 | } 70 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 71 | *self = src.read_i64::()?; 72 | Ok(()) 73 | } 74 | } 75 | 76 | /// Serializes a u32 as a 4 byte big endian integer. 77 | impl XDR for u32 { 78 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 79 | dest.write_u32::(*self) 80 | } 81 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 82 | *self = src.read_u32::()?; 83 | Ok(()) 84 | } 85 | } 86 | 87 | /// Serializes a u64 as a 8 byte big endian integer. 88 | impl XDR for u64 { 89 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 90 | dest.write_u64::(*self) 91 | } 92 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 93 | *self = src.read_u64::()?; 94 | Ok(()) 95 | } 96 | } 97 | 98 | impl XDR for [u8; N] { 99 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 100 | dest.write_all(self) 101 | } 102 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 103 | src.read_exact(self) 104 | } 105 | } 106 | 107 | impl XDR for Vec { 108 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 109 | assert!(self.len() < u32::MAX as usize); 110 | let length = self.len() as u32; 111 | length.serialize(dest)?; 112 | dest.write_all(self)?; 113 | // write padding 114 | let pad = ((4 - length % 4) % 4) as usize; 115 | let zeros: [u8; 4] = [0, 0, 0, 0]; 116 | if pad > 0 { 117 | dest.write_all(&zeros[..pad])?; 118 | } 119 | Ok(()) 120 | } 121 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 122 | let mut length: u32 = 0; 123 | length.deserialize(src)?; 124 | self.resize(length as usize, 0); 125 | src.read_exact(self)?; 126 | // read padding 127 | let pad = ((4 - length % 4) % 4) as usize; 128 | let mut zeros: [u8; 4] = [0, 0, 0, 0]; 129 | src.read_exact(&mut zeros[..pad])?; 130 | Ok(()) 131 | } 132 | } 133 | 134 | impl XDR for nfsstring { 135 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 136 | self.0.serialize(dest) 137 | } 138 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 139 | self.0.deserialize(src) 140 | } 141 | } 142 | 143 | impl XDR for Vec { 144 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 145 | assert!(self.len() < u32::MAX as usize); 146 | let length = self.len() as u32; 147 | length.serialize(dest)?; 148 | for i in self { 149 | i.serialize(dest)?; 150 | } 151 | Ok(()) 152 | } 153 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 154 | let mut length: u32 = 0; 155 | length.deserialize(src)?; 156 | self.resize(length as usize, 0); 157 | for i in self { 158 | i.deserialize(src)?; 159 | } 160 | Ok(()) 161 | } 162 | } 163 | 164 | #[allow(non_camel_case_types)] 165 | #[macro_export] 166 | macro_rules! XDRStruct { 167 | ( 168 | $t:ident, 169 | $($element:ident),* 170 | ) => { 171 | impl XDR for $t { 172 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 173 | $(self.$element.serialize(dest)?;)* 174 | Ok(()) 175 | } 176 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 177 | $(self.$element.deserialize(src)?;)* 178 | Ok(()) 179 | } 180 | } 181 | }; 182 | } 183 | 184 | /// This macro only handles XDR Unions of the form 185 | /// union pre_op_attr switch (bool attributes_follow) { 186 | /// case TRUE: 187 | /// wcc_attr attributes; 188 | /// case FALSE: 189 | /// void; 190 | /// }; 191 | /// This is translated to 192 | /// enum pre_op_attr { 193 | /// Void, 194 | /// attributes(wcc_attr) 195 | /// } 196 | /// The serde methods can be generated with XDRBoolUnion(pre_op_attr, attributes, wcc_attr) 197 | /// The "true" type must have the Default trait 198 | #[allow(non_camel_case_types)] 199 | #[macro_export] 200 | macro_rules! XDRBoolUnion { 201 | ( 202 | $t:ident, $enumcase:ident, $enumtype:ty 203 | ) => { 204 | impl XDR for $t { 205 | fn serialize(&self, dest: &mut R) -> std::io::Result<()> { 206 | match self { 207 | $t::Void => { 208 | false.serialize(dest)?; 209 | } 210 | $t::$enumcase(v) => { 211 | true.serialize(dest)?; 212 | v.serialize(dest)?; 213 | } 214 | } 215 | Ok(()) 216 | } 217 | fn deserialize(&mut self, src: &mut R) -> std::io::Result<()> { 218 | let mut c: bool = false; 219 | c.deserialize(src)?; 220 | if c == false { 221 | *self = $t::Void; 222 | } else { 223 | let mut r = <$enumtype>::default(); 224 | r.deserialize(src)?; 225 | *self = $t::$enumcase(r); 226 | } 227 | Ok(()) 228 | } 229 | } 230 | }; 231 | } 232 | 233 | pub(crate) use XDRBoolUnion; 234 | pub(crate) use XDREnumSerde; 235 | pub(crate) use XDRStruct; 236 | --------------------------------------------------------------------------------