# Request

> The payload of the channel request: the tag byte 8 and the request of the channel.

Canonical: https://protocol.diverge.network/2.3.0/container-proxy-endpoints/fuse-mount/server/setattr/request/
Specification revision: 2.3.0

```text
[8][attrs: 29 bytes][path …]
```

- **The path.** UTF-8, `/`-separated, with no leading `/`; empty is
  the mount itself. Where the path is the last thing in the payload
  it runs to the end of the payload; where bytes or a second path
  follow it, a two-byte length precedes it.
- **`attrs`.** Twenty-nine bytes before the path: `[set: u8][mode: u32][uid: u32][gid: u32][atime: 12 bytes][mtime: 12 bytes]`, every integer big-endian, a time `[secs: u64][nanos: u32]`. The bits of `set` say which fields are set: `1` the mode, `2` the owner, `4` the group, `8` the access time, `16` the modification time. A field whose bit is clear is zero and is not set. The mode is the permission bits.
- **A file mount.** For a mount of kind `file`, the path is empty.

The channel request is defined by `diverge-sdk-rs/src/container_proxy/outside/endpoints/fuse/mount/server/channel_request/frame.rs`:

```rust
//! What a server's channel request frame carries for a FUSE mount.

use std::fmt;

use super::{Path, Read, Rename, Setattr, Truncate, Write};
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};

/// What a mount asks the server for, on behalf of the program using
/// it.
///
/// A payload leads with one byte saying which, and the rest is that
/// variant's own bytes. No ask names the mount: the scope it rides is
/// the mount.
///
/// | tag | asks for | payload |
/// |-----|----------|---------|
/// | `0` | [`Read`](Self::Read) | `[path_len: u16 BE][path…][offset: u64 BE][length: u32 BE]` |
/// | `1` | [`Write`](Self::Write) | `[path_len: u16 BE][path…][offset: u64 BE][bytes…]` |
/// | `2` | [`List`](Self::List) | `[path…]` |
/// | `3` | [`Remove`](Self::Remove) | `[path…]` |
/// | `4` | [`Rename`](Self::Rename) | `[from_len: u16 BE][from…][to…]` |
/// | `5` | [`Mkdir`](Self::Mkdir) | `[path…]` |
/// | `6` | [`Stat`](Self::Stat) | `[path…]` |
/// | `7` | [`Truncate`](Self::Truncate) | `[size: u64 BE][path…]` |
/// | `8` | [`Setattr`](Self::Setattr) | `[attrs: 29 bytes][path…]` |
///
/// Where a path is the last thing in a payload it runs to the end;
/// where bytes, fixed fields or a second path follow it, it carries
/// a length prefix. The nine are the nine of
/// [`shared::containers::fuse`](crate::shared::containers::fuse), and
/// each is answered with that module's own frame, one message and
/// the finish; what each MEANS — a file mount's empty path, the root
/// that is never removed or moved, whose refusal a refusal is — is
/// stated there once.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame<'a> {
    /// Read a piece of a file of the mount. Tag `0`.
    ///
    /// Every `read(2)` of the file, with its offset and size. See
    /// [`read`](crate::shared::containers::fuse::read).
    Read(Read<'a>),
    /// Write a piece of a file of the mount, in place. Tag `1`.
    ///
    /// Every `write(2)` of the file, with its offset. See
    /// [`write`](crate::shared::containers::fuse::write).
    Write(Write<'a>),
    /// List a directory of the mount. Tag `2`.
    ///
    /// Every listing in a directory mount: names and kinds. See
    /// [`list`](crate::shared::containers::fuse::list).
    List(Path<'a>),
    /// Remove a file or an empty directory of the mount. Tag `3`.
    ///
    /// See [`remove`](crate::shared::containers::fuse::remove).
    Remove(Path<'a>),
    /// Rename an entry within the mount. Tag `4`.
    ///
    /// See [`rename`](crate::shared::containers::fuse::rename).
    Rename(Rename<'a>),
    /// Make a directory in the mount. Tag `5`.
    ///
    /// See [`mkdir`](crate::shared::containers::fuse::mkdir).
    Mkdir(Path<'a>),
    /// What an entry of the mount is: kind, size, mode, owner, group
    /// and times. Tag `6`.
    ///
    /// Every attribute of a file mount, and every lookup and
    /// attribute of an entry in a directory mount: a `stat` costs
    /// fifty-seven bytes back, not the file. See
    /// [`stat`](crate::shared::containers::fuse::stat).
    Stat(Path<'a>),
    /// Set a file of the mount to a length. Tag `7`.
    ///
    /// Every `truncate(2)`, `ftruncate(2)` and `O_TRUNC` open. See
    /// [`truncate`](crate::shared::containers::fuse::truncate).
    Truncate(Truncate<'a>),
    /// Set some of an entry's attributes. Tag `8`.
    ///
    /// Every `chmod(2)`, `chown(2)` and `utimensat(2)`. See
    /// [`setattr`](crate::shared::containers::fuse::setattr).
    Setattr(Setattr<'a>),
}

/// Tag for [`Frame::Read`].
const READ: u8 = 0;

/// Tag for [`Frame::Write`].
const WRITE: u8 = 1;

/// Tag for [`Frame::List`].
const LIST: u8 = 2;

/// Tag for [`Frame::Remove`].
const REMOVE: u8 = 3;

/// Tag for [`Frame::Rename`].
const RENAME: u8 = 4;

/// Tag for [`Frame::Mkdir`].
const MKDIR: u8 = 5;

/// Tag for [`Frame::Stat`].
const STAT: u8 = 6;

/// Tag for [`Frame::Truncate`].
const TRUNCATE: u8 = 7;

/// Tag for [`Frame::Setattr`].
const SETATTR: u8 = 8;

impl Encode for Frame<'_> {
    /// One way to fail: a path too long for its prefix, on the three
    /// asks where a path has one. Everything else is bytes copied.
    type Error = FrameEncodeError;

    fn encode(&self, out: &mut Writer<'_>) -> Result<(), FrameEncodeError> {
        match self {
            Frame::Read(read) => {
                out.extend_from_slice(&[READ]);
                read.encode(out)
            }
            Frame::Write(write) => {
                out.extend_from_slice(&[WRITE]);
                write.encode(out)
            }
            Frame::List(path) => {
                out.extend_from_slice(&[LIST]);
                path.encode(out).map_err(|error| match error {})
            }
            Frame::Remove(path) => {
                out.extend_from_slice(&[REMOVE]);
                path.encode(out).map_err(|error| match error {})
            }
            Frame::Rename(rename) => {
                out.extend_from_slice(&[RENAME]);
                rename.encode(out)
            }
            Frame::Mkdir(path) => {
                out.extend_from_slice(&[MKDIR]);
                path.encode(out).map_err(|error| match error {})
            }
            Frame::Stat(path) => {
                out.extend_from_slice(&[STAT]);
                // Its error is `Infallible`, and an empty match on one
                // is how you say so: there is no value to handle.
                path.encode(out).map_err(|error| match error {})
            }
            Frame::Truncate(truncate) => {
                out.extend_from_slice(&[TRUNCATE]);
                truncate.encode(out).map_err(|error| match error {})
            }
            Frame::Setattr(setattr) => {
                out.extend_from_slice(&[SETATTR]);
                setattr.encode(out).map_err(|error| match error {})
            }
        }
    }
}

/// A mount ask that could not be written.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FrameEncodeError {
    /// A path of more bytes than a two-byte length prefix can say,
    /// carrying how many there were. Only the asks where something
    /// follows the path prefix it; a path that ends the payload takes
    /// any length.
    PathLength(usize),
}

impl fmt::Display for FrameEncodeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FrameEncodeError::PathLength(len) => {
                write!(f, "fuse path is {len} bytes, more than 65535")
            }
        }
    }
}

impl std::error::Error for FrameEncodeError {}

impl<'a> Decode<'a> for Frame<'a> {
    /// Four ways to fail, and none of them is a parse.
    type Error = FrameError;

    fn decode(bytes: &'a [u8]) -> Result<Self, Self::Error> {
        let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
        match *tag {
            READ => Read::decode(rest).map(Frame::Read),
            WRITE => Write::decode(rest).map(Frame::Write),
            LIST => Path::decode(rest).map(Frame::List),
            REMOVE => Path::decode(rest).map(Frame::Remove),
            RENAME => Rename::decode(rest).map(Frame::Rename),
            MKDIR => Path::decode(rest).map(Frame::Mkdir),
            STAT => Path::decode(rest).map(Frame::Stat),
            TRUNCATE => Truncate::decode(rest).map(Frame::Truncate),
            SETATTR => Setattr::decode(rest).map(Frame::Setattr),
            tag => Err(FrameError::UnknownTag(tag)),
        }
    }
}

/// A mount ask that could not be read.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FrameError {
    /// No bytes at all, so not even a tag.
    Empty,
    /// A tag that is none of this frame's nine.
    UnknownTag(u8),
    /// Fewer bytes than the ask's fixed part promises — a length
    /// prefix, the path a prefix said was there, an offset, a length,
    /// a size, or the attributes.
    Truncated,
    /// A path that is not UTF-8.
    PathUtf8,
}

impl fmt::Display for FrameError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FrameError::Empty => {
                f.write_str("fuse mount channel request frame is empty")
            }
            FrameError::UnknownTag(tag) => {
                write!(f, "unknown fuse mount channel request tag {tag}")
            }
            FrameError::Truncated => {
                f.write_str("fuse ask is shorter than it promises")
            }
            FrameError::PathUtf8 => f.write_str("fuse path is not utf-8"),
        }
    }
}

impl std::error::Error for FrameError {}
```

and by `diverge-sdk-rs/src/container_proxy/outside/endpoints/fuse/mount/server/channel_request/setattr.rs`:

```rust
//! Set a mounted entry's attributes.

use std::str;

use super::FrameError;
use crate::wire::encode::{Encode, Writer};
use crate::shared::containers::fuse::Attrs;

/// Set some of an entry's attributes, by its path. Answered with one
/// [`Ack`](crate::shared::containers::fuse::ack::Frame).
///
/// ```text
/// [attrs: 29 bytes][path: utf8…]
/// ```
///
/// The attributes precede the path, so the path runs to the end and
/// needs no prefix — see [`Attrs`] for the twenty-nine bytes. On a
/// file mount the path is empty.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Setattr<'a> {
    /// The entry's path inside the mount; empty for a file mount, and
    /// for a directory mount's root.
    pub path: &'a str,
    /// Which attributes, and to what.
    pub attrs: Attrs,
}

impl Encode for Setattr<'_> {
    /// [`Infallible`](std::convert::Infallible): fixed bytes and a
    /// path's own.
    type Error = std::convert::Infallible;

    fn encode(&self, out: &mut Writer<'_>) -> Result<(), Self::Error> {
        self.attrs.encode(out);
        out.extend_from_slice(self.path.as_bytes());
        Ok(())
    }
}

impl<'a> Setattr<'a> {
    /// Decode from the bytes after the ask's tag. The path borrows
    /// from `bytes`.
    pub fn decode(bytes: &'a [u8]) -> Result<Self, FrameError> {
        let (attrs, path) = Attrs::decode(bytes).map_err(|_| FrameError::Truncated)?;
        Ok(Setattr {
            path: str::from_utf8(path).map_err(|_| FrameError::PathUtf8)?,
            attrs,
        })
    }
}
```

and by `diverge-sdk-rs/src/shared/containers/fuse/setattr/attrs.rs`:

```rust
//! Which attributes to set, and to what.

use super::super::{RequestError, Time, time::TIME_LEN};
use crate::wire::encode::Writer;

/// The attributes a setattr may change, each set or left: the mode,
/// the owner, the group, the access time and the modification time.
/// The change time is not here — it is the caller's to stamp when
/// anything else changes.
///
/// ```text
/// [set: u8][mode: u32 BE][uid: u32 BE][gid: u32 BE][atime][mtime]
/// ```
///
/// Twenty-nine bytes, fixed: a flags byte saying which fields are
/// set — bit `0` the mode, `1` the owner, `2` the group, `3` the
/// access time, `4` the modification time — and every field's bytes
/// whether set or not, zero when not. Fixed rather than optional so
/// a path may follow and run to the end.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Attrs {
    /// The permission bits, `0o644` and the like.
    pub mode: Option<u32>,
    /// The owner's id.
    pub uid: Option<u32>,
    /// The group's id.
    pub gid: Option<u32>,
    /// When the entry was last read.
    pub atime: Option<Time>,
    /// When the entry's content last changed.
    pub mtime: Option<Time>,
}

/// The bytes the attributes occupy.
pub(crate) const ATTRS_LEN: usize = 1 + 4 + 4 + 4 + 2 * TIME_LEN;

const MODE: u8 = 1 << 0;
const UID: u8 = 1 << 1;
const GID: u8 = 1 << 2;
const ATIME: u8 = 1 << 3;
const MTIME: u8 = 1 << 4;

impl Attrs {
    /// Whether anything is set.
    pub fn is_empty(&self) -> bool {
        self.mode.is_none() && self.uid.is_none() && self.gid.is_none() && self.atime.is_none() && self.mtime.is_none()
    }

    /// Write the twenty-nine bytes.
    pub(crate) fn encode(&self, out: &mut Writer<'_>) {
        let mut set = 0u8;
        set |= self.mode.map_or(0, |_| MODE);
        set |= self.uid.map_or(0, |_| UID);
        set |= self.gid.map_or(0, |_| GID);
        set |= self.atime.map_or(0, |_| ATIME);
        set |= self.mtime.map_or(0, |_| MTIME);
        out.extend_from_slice(&[set]);
        out.extend_from_slice(&self.mode.unwrap_or(0).to_be_bytes());
        out.extend_from_slice(&self.uid.unwrap_or(0).to_be_bytes());
        out.extend_from_slice(&self.gid.unwrap_or(0).to_be_bytes());
        self.atime.unwrap_or_default().encode(out);
        self.mtime.unwrap_or_default().encode(out);
    }

    /// Read the twenty-nine bytes off the front: the attributes, then
    /// the rest.
    pub(crate) fn decode(bytes: &[u8]) -> Result<(Self, &[u8]), RequestError> {
        if bytes.len() < ATTRS_LEN {
            return Err(RequestError::Truncated);
        }
        let set = bytes[0];
        let mode: [u8; 4] = bytes[1..5].try_into().expect("four bytes were taken");
        let uid: [u8; 4] = bytes[5..9].try_into().expect("four bytes were taken");
        let gid: [u8; 4] = bytes[9..13].try_into().expect("four bytes were taken");
        let (atime, rest) = Time::decode(&bytes[13..]).map_err(|_| RequestError::Truncated)?;
        let (mtime, rest) = Time::decode(rest).map_err(|_| RequestError::Truncated)?;
        Ok((
            Attrs {
                mode: (set & MODE != 0).then_some(u32::from_be_bytes(mode)),
                uid: (set & UID != 0).then_some(u32::from_be_bytes(uid)),
                gid: (set & GID != 0).then_some(u32::from_be_bytes(gid)),
                atime: (set & ATIME != 0).then_some(atime),
                mtime: (set & MTIME != 0).then_some(mtime),
            },
            rest,
        ))
    }
}
```

and by `diverge-sdk-rs/src/shared/containers/fuse/time.rs`:

```rust
//! A moment, as a stat carries one and a setattr sets one.

use super::ResponseError;
use crate::wire::encode::Writer;

/// Seconds since the Unix epoch and nanoseconds into the second:
/// twelve bytes, fixed.
///
/// ```text
/// [secs: u64 BE][nanos: u32 BE]
/// ```
///
/// Unsigned, so nothing predates 1970; nanoseconds, because a build
/// tool that compares modification times compares them at the
/// filesystem's own resolution, and a second would make two saves
/// one.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Time {
    /// Seconds since `1970-01-01T00:00:00Z`.
    pub secs: u64,
    /// Nanoseconds into the second, under a billion.
    pub nanos: u32,
}

/// The bytes a time occupies.
pub(crate) const TIME_LEN: usize = 8 + 4;

impl Time {
    /// Write the twelve bytes.
    pub(crate) fn encode(&self, out: &mut Writer<'_>) {
        out.extend_from_slice(&self.secs.to_be_bytes());
        out.extend_from_slice(&self.nanos.to_be_bytes());
    }

    /// Read the twelve bytes off the front: the time, then the rest.
    pub(crate) fn decode(bytes: &[u8]) -> Result<(Self, &[u8]), ResponseError> {
        let fixed = bytes.get(..TIME_LEN).ok_or(ResponseError::Truncated)?;
        let secs: [u8; 8] = fixed[..8].try_into().expect("eight bytes were taken");
        let nanos: [u8; 4] = fixed[8..].try_into().expect("four bytes were taken");
        Ok((
            Time {
                secs: u64::from_be_bytes(secs),
                nanos: u32::from_be_bytes(nanos),
            },
            &bytes[TIME_LEN..],
        ))
    }
}
```
