nfs-rs 0.8.2

An asynchronous pure Rust client library for NFSv3, experimental NFSv4.0, and NFSv4.1
Documentation
use bytes::{Buf, Bytes};

use super::mount::{Mount41, extract_stateid};
use super::state::StateId;
use crate::error::{NfsError, Result};
use crate::nfs4::attrs::encode_setattr;

impl Mount41 {
    #[allow(clippy::too_many_arguments)]
    pub(crate) async fn setattr(
        &self,
        fh: Bytes,
        _guard_ctime: Option<crate::Time>,
        mode: Option<u32>,
        uid: Option<u32>,
        gid: Option<u32>,
        size: Option<u64>,
        atime: Option<crate::Time>,
        mtime: Option<crate::Time>,
    ) -> Result<()> {
        // Complete retained DS writes before changing EOF. A later recall
        // must never replay pre-truncate data and extend the file again.
        let _io_guard = if size.is_some() {
            let guard = self.layout_manager.write_file_io(&fh).await;
            self.flush_layoutcommit(&fh).await?;
            Some(guard)
        } else {
            None
        };
        // Build fattr4 bitmap + attr_vals for the requested attributes
        let (attrmask, attr_vals) = encode_setattr(mode, uid, gid, size, atime, mtime);
        let stateid = [0u8; 16]; // anonymous stateid
        let resp = self
            .compound("setattr", |b| {
                b.putfh(&fh).setattr(&stateid, &attrmask, &attr_vals)
            })
            .await?;
        resp.op_ok(1)?; // PUTFH
        // SETATTR4res has status + bitmap (not a union with void default)
        let setattr_op = resp
            .results
            .get(2)
            .ok_or_else(|| NfsError::Xdr("SETATTR response missing op at index 2".to_string()))?;
        if !matches!(setattr_op.status, crate::nfs4::fastxdr::nfsstat4::NFS4_OK) {
            return Err(NfsError::Nfs4(setattr_op.status));
        }
        Ok(())
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) async fn setattr_path(
        &self,
        path: &str,
        specify_guard: bool,
        mode: Option<u32>,
        uid: Option<u32>,
        gid: Option<u32>,
        size: Option<u64>,
        atime: Option<crate::Time>,
        mtime: Option<crate::Time>,
    ) -> Result<()> {
        let obj = self.lookup_path(path).await?;
        let guard = if specify_guard {
            let attr = self.getattr(obj.fh.clone()).await?;
            Some(attr.ctime)
        } else {
            None
        };
        self.setattr(obj.fh, guard, mode, uid, gid, size, atime, mtime)
            .await
    }

    pub(crate) async fn lock(
        &self,
        fh: Bytes,
        lock_type: u32,
        offset: u64,
        length: u64,
    ) -> Result<Bytes> {
        // LOCK requires a valid open stateid — file must be opened first
        let sid = self.state.get_stateid(&fh).await;
        if sid == StateId::anonymous() {
            return Err(NfsError::InvalidInput(
                "file must be opened before locking (call open() first)".to_string(),
            ));
        }
        let open_stateid = sid.raw;
        let client_id = self.session_holder.get().await.client_id();

        let resp = self
            .compound("lock", |b| {
                b.require_generation(sid.generation).putfh(&fh).lock(
                    lock_type,
                    false, // reclaim
                    offset,
                    length,
                    true, // new_lock_owner
                    &open_stateid,
                    0, // lock_seqid
                    0, // open_seqid
                    b"nfs-rs-lock",
                    client_id,
                )
            })
            .await?;
        resp.op_ok(1)?; // PUTFH
        let lock_op = resp.op_ok(2)?;
        let mut data = lock_op.data.clone();
        // LOCK4resok: lock_stateid (stateid4 = 16 bytes)
        let stateid_raw = extract_stateid(&mut data)?;
        Ok(Bytes::copy_from_slice(&stateid_raw))
    }

    pub(crate) async fn locku(
        &self,
        fh: Bytes,
        lock_stateid: Bytes,
        lock_type: u32,
        offset: u64,
        length: u64,
    ) -> Result<()> {
        if lock_stateid.len() < 16 {
            return Err(NfsError::InvalidInput(
                "lock_stateid must be 16 bytes".to_string(),
            ));
        }
        let mut sid = [0u8; 16];
        sid.copy_from_slice(&lock_stateid[..16]);

        let resp = self
            .compound("locku", |b| {
                b.putfh(&fh).locku(lock_type, 0, &sid, offset, length)
            })
            .await?;
        resp.op_ok(1)?; // PUTFH
        resp.op_ok(2)?; // LOCKU
        Ok(())
    }

    pub(crate) async fn lock_test(
        &self,
        fh: Bytes,
        lock_type: u32,
        offset: u64,
        length: u64,
    ) -> Result<()> {
        if !matches!(lock_type, 1 | 2) || length == 0 {
            return Err(NfsError::InvalidInput(
                "LOCKT requires type 1/2 and non-zero length".to_string(),
            ));
        }
        let client_id = self.session_holder.get().await.client_id();
        let resp = self
            .compound("lockt", |b| {
                b.putfh(&fh)
                    .lockt(lock_type, offset, length, b"nfs-rs-lockt", client_id)
            })
            .await?;
        resp.op_ok(1)?;
        resp.op_ok(2)?;
        Ok(())
    }

    pub(crate) async fn commit(&self, fh: Bytes, offset: u64, count: u32) -> Result<()> {
        self.commit_with_verifier(fh, offset, count)
            .await
            .map(|_| ())
    }

    /// COMMIT returning the server write verifier (COMMIT4resok.writeverf).
    pub(crate) async fn commit_with_verifier(
        &self,
        fh: Bytes,
        offset: u64,
        count: u32,
    ) -> Result<Option<[u8; 8]>> {
        let resp = self
            .compound("commit", |b| b.putfh(&fh).commit(offset, count))
            .await?;
        resp.op_ok(1)?; // PUTFH
        let commit_op = resp.op_ok(2)?; // COMMIT
        let mut d = commit_op.data.clone();
        if d.remaining() < 8 {
            return Err(NfsError::Xdr("COMMIT result too short".to_string()));
        }
        let mut verifier = [0u8; 8];
        d.copy_to_slice(&mut verifier);
        Ok(Some(verifier))
    }

    pub(crate) async fn commit_path(&self, path: &str, offset: u64, count: u32) -> Result<()> {
        let obj = self.lookup_path(path).await?;
        self.commit(obj.fh, offset, count).await
    }
}