nfs 0.1.0

A userspace NFSv3 and NFSv4 client library.
Documentation
#![cfg_attr(not(feature = "protocol"), allow(dead_code))]

use std::net::ToSocketAddrs;
use std::time::Duration;

use crate::error::{Error, Result};
use crate::rpc::{Auth, AuthSys, RpcClient};
use crate::v3::proto::FileHandle;
use crate::xdr::{Decode, Decoder, Encode, Encoder};

pub const MOUNT_PROGRAM: u32 = 100005;
pub const MOUNT_VERSION: u32 = 3;
pub const MNTPATHLEN: usize = 1024;
pub const MNTNAMLEN: usize = 255;

const MOUNTPROC3_MNT: u32 = 1;
const MOUNTPROC3_UMNT: u32 = 3;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum MountStatus {
    Ok = 0,
    Perm = 1,
    NoEnt = 2,
    Io = 5,
    Access = 13,
    NotDir = 20,
    Invalid = 22,
    NameTooLong = 63,
    NotSupported = 10004,
    ServerFault = 10006,
    Unknown(u32),
}

impl MountStatus {
    pub fn from_u32(value: u32) -> Self {
        match value {
            0 => Self::Ok,
            1 => Self::Perm,
            2 => Self::NoEnt,
            5 => Self::Io,
            13 => Self::Access,
            20 => Self::NotDir,
            22 => Self::Invalid,
            63 => Self::NameTooLong,
            10004 => Self::NotSupported,
            10006 => Self::ServerFault,
            value => Self::Unknown(value),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MountInfo {
    pub file_handle: FileHandle,
    pub auth_flavors: Vec<u32>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct DirPath<'a>(&'a str);

impl Encode for DirPath<'_> {
    fn encode(&self, encoder: &mut Encoder) -> crate::xdr::Result<()> {
        encoder.write_string(self.0, MNTPATHLEN)
    }
}

#[derive(Debug)]
pub struct MountClient {
    rpc: RpcClient,
}

impl MountClient {
    pub fn connect<A: ToSocketAddrs>(addr: A, auth: AuthSys) -> Result<Self> {
        Self::connect_with_timeout(addr, auth, None)
    }

    pub fn connect_with_timeout<A: ToSocketAddrs>(
        addr: A,
        auth: AuthSys,
        timeout: Option<Duration>,
    ) -> Result<Self> {
        Ok(Self {
            rpc: RpcClient::connect_with_timeout(addr, Auth::sys(auth), timeout)?,
        })
    }

    pub fn set_timeout(&self, timeout: Option<Duration>) -> Result<()> {
        self.rpc.set_timeout(timeout)
    }

    pub fn mount(&mut self, export_path: &str) -> Result<MountInfo> {
        let payload = self.rpc.call(
            MOUNT_PROGRAM,
            MOUNT_VERSION,
            MOUNTPROC3_MNT,
            &DirPath(export_path),
        )?;
        let mut decoder = Decoder::new(&payload);
        let status = MountStatus::from_u32(u32::decode(&mut decoder)?);
        if status != MountStatus::Ok {
            decoder.finish()?;
            return Err(Error::Mount { status });
        }

        let file_handle = FileHandle::decode(&mut decoder)?;
        let auth_flavors = decoder.read_array::<u32>(128)?;
        decoder.finish()?;
        Ok(MountInfo {
            file_handle,
            auth_flavors,
        })
    }

    pub fn unmount(&mut self, export_path: &str) -> Result<()> {
        let payload = self.rpc.call(
            MOUNT_PROGRAM,
            MOUNT_VERSION,
            MOUNTPROC3_UMNT,
            &DirPath(export_path),
        )?;
        let decoder = Decoder::new(&payload);
        decoder.finish()?;
        Ok(())
    }
}