fernfs 0.1.5

A Rust NFS Server implementation
Documentation
use std::io::Cursor;
use std::sync::{Arc, Mutex, RwLock};
use std::time::Duration;

use async_trait::async_trait;
use num_traits::FromPrimitive;

use fernfs::protocol::nfs::portmap::PortmapTable;
use fernfs::protocol::nfs::v3::handle_nfs;
use fernfs::protocol::rpc::{Context, TransactionTracker};
use fernfs::vfs::{self, Capabilities, NFSFileSystem, ReadDirResult};
use fernfs::xdr::{self, nfs3, Serialize};

const ROOT_ID: nfs3::fileid3 = 1;
const CREATED_ID: nfs3::fileid3 = 2;

#[derive(Default)]
struct ExclusiveCaptureFS {
    captured: Mutex<Option<nfs3::createverf3>>,
    generation: u64,
}

impl ExclusiveCaptureFS {
    fn new() -> Self {
        Self { captured: Mutex::new(None), generation: 42 }
    }
}

#[async_trait]
impl vfs::NFSFileSystem for ExclusiveCaptureFS {
    fn generation(&self) -> u64 {
        self.generation
    }

    fn capabilities(&self) -> Capabilities {
        Capabilities::ReadWrite
    }

    fn root_dir(&self) -> nfs3::fileid3 {
        ROOT_ID
    }

    async fn lookup(
        &self,
        _dirid: nfs3::fileid3,
        _filename: &nfs3::filename3,
    ) -> Result<nfs3::fileid3, nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn getattr(&self, id: nfs3::fileid3) -> Result<nfs3::fattr3, nfs3::nfsstat3> {
        if id != ROOT_ID {
            return Err(nfs3::nfsstat3::NFS3ERR_NOENT);
        }
        Ok(nfs3::fattr3 { ftype: nfs3::ftype3::NF3DIR, fileid: id, ..Default::default() })
    }

    async fn setattr(
        &self,
        _id: nfs3::fileid3,
        _setattr: nfs3::sattr3,
    ) -> Result<nfs3::fattr3, nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn read(
        &self,
        _id: nfs3::fileid3,
        _offset: u64,
        _count: u32,
    ) -> Result<(Vec<u8>, bool), nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn write(
        &self,
        _id: nfs3::fileid3,
        _offset: u64,
        _data: &[u8],
        _stable: nfs3::file::stable_how,
    ) -> Result<(nfs3::fattr3, nfs3::file::stable_how, nfs3::count3), nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn create(
        &self,
        _dirid: nfs3::fileid3,
        _filename: &nfs3::filename3,
        _attr: nfs3::sattr3,
    ) -> Result<(nfs3::fileid3, nfs3::fattr3), nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn create_exclusive(
        &self,
        _dirid: nfs3::fileid3,
        _filename: &nfs3::filename3,
        verifier: nfs3::createverf3,
    ) -> Result<nfs3::fileid3, nfs3::nfsstat3> {
        *self.captured.lock().unwrap() = Some(verifier);
        Ok(CREATED_ID)
    }

    async fn mkdir(
        &self,
        _dirid: nfs3::fileid3,
        _dirname: &nfs3::filename3,
    ) -> Result<(nfs3::fileid3, nfs3::fattr3), nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn remove(
        &self,
        _dirid: nfs3::fileid3,
        _filename: &nfs3::filename3,
    ) -> Result<(), nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn rename(
        &self,
        _from_dirid: nfs3::fileid3,
        _from_filename: &nfs3::filename3,
        _to_dirid: nfs3::fileid3,
        _to_filename: &nfs3::filename3,
    ) -> Result<(), nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn readdir(
        &self,
        _dirid: nfs3::fileid3,
        _start_after: nfs3::fileid3,
        _max_entries: usize,
    ) -> Result<ReadDirResult, nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn symlink(
        &self,
        _dirid: nfs3::fileid3,
        _linkname: &nfs3::filename3,
        _symlink: &nfs3::nfspath3,
        _attr: &nfs3::sattr3,
    ) -> Result<(nfs3::fileid3, nfs3::fattr3), nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn readlink(&self, _id: nfs3::fileid3) -> Result<nfs3::nfspath3, nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn link(
        &self,
        _file_id: nfs3::fileid3,
        _link_dir_id: nfs3::fileid3,
        _link_name: &nfs3::filename3,
    ) -> Result<nfs3::fattr3, nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn mknod(
        &self,
        _dir_id: nfs3::fileid3,
        _name: &nfs3::filename3,
        _ftype: nfs3::ftype3,
        _specdata: nfs3::specdata3,
        _attrs: &nfs3::sattr3,
    ) -> Result<(nfs3::fileid3, nfs3::fattr3), nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }

    async fn commit(
        &self,
        _file_id: nfs3::fileid3,
        _offset: u64,
        _count: u32,
    ) -> Result<nfs3::fattr3, nfs3::nfsstat3> {
        Err(nfs3::nfsstat3::NFS3ERR_NOTSUPP)
    }
}

#[tokio::test]
async fn create_exclusive_passes_verifier_to_vfs() {
    let fs = Arc::new(ExclusiveCaptureFS::new());
    let context = Context {
        local_port: 0,
        client_addr: "127.0.0.1:1234".to_string(),
        auth: xdr::rpc::auth_unix::default(),
        vfs: fs.clone(),
        mount_signal: None,
        export_name: Arc::from("/".to_string()),
        transaction_tracker: Arc::new(TransactionTracker::new(Duration::from_secs(60))),
        portmap_table: Arc::new(RwLock::new(PortmapTable::default())),
    };

    let dir_handle = fs.id_to_fh(ROOT_ID);
    let dirops = nfs3::diropargs3 { dir: dir_handle, name: b"file".as_slice().into() };
    let verifier: nfs3::createverf3 = [1, 2, 3, 4, 5, 6, 7, 8];

    let mut input = Cursor::new(Vec::new());
    dirops.serialize(&mut input).expect("serialize diropargs");
    nfs3::createmode3::EXCLUSIVE.serialize(&mut input).expect("serialize create mode");
    verifier.serialize(&mut input).expect("serialize verifier");
    input.set_position(0);

    let call = xdr::rpc::call_body {
        rpcvers: 2,
        prog: nfs3::PROGRAM,
        vers: nfs3::VERSION,
        proc: nfs3::NFSProgram::NFSPROC3_CREATE as u32,
        cred: xdr::rpc::opaque_auth::default(),
        verf: xdr::rpc::opaque_auth::default(),
    };

    let mut output = Cursor::new(Vec::new());
    handle_nfs(9, call, &mut input, &mut output, &context).await.expect("handle_nfs");

    assert_eq!(*fs.captured.lock().unwrap(), Some(verifier));

    output.set_position(0);
    let _rpc = xdr::deserialize::<xdr::rpc::rpc_msg>(&mut output).expect("deserialize rpc");
    let status_raw = xdr::deserialize::<u32>(&mut output).expect("deserialize status");
    let status = nfs3::nfsstat3::from_u32(status_raw).expect("invalid nfsstat3 value");
    assert_eq!(status, nfs3::nfsstat3::NFS3_OK);
    let fh = xdr::deserialize::<nfs3::post_op_fh3>(&mut output).expect("deserialize fh");
    let fh = fh.expect("expected file handle");
    assert_eq!(fs.fh_to_id(&fh).expect("fh_to_id"), CREATED_ID);
    let _ = xdr::deserialize::<nfs3::post_op_attr>(&mut output).expect("deserialize attr");
    let _ = xdr::deserialize::<nfs3::wcc_data>(&mut output).expect("deserialize wcc");
}