# FUSE

> A mount made on the server’s request, and kinds 12 to 18: the seven operations by which the server serves the files and directories the caller holds live, each answered with exactly one message.

Canonical: https://provider.diverge.network/2.3.0/proxy/fuse/
Specification revision: 2.3.0

The server makes every mount by one request on
[`/fuse/mount`](/2.3.0/proxy/fuse/mount/). Seven asks carry the operations
of the mounts made. Every ask names a mount by its `id` and an entry
by its path. The server answers every ask with exactly one message.

| Kind | Ask | Payload after the kind | Answered on | Answer |
|------|-----|------------------------|-------------|--------|
| `18` | stat | `[id_len: u16, big-endian][id …][path …]` | [`/fuse/stat/{channel}`](/2.3.0/proxy/fuse/stat/) | `0` and nine bytes; `1`; or `2` and a message |
| `12` | read | `[id_len: u16, big-endian][id …][path …]` | [`/fuse/read/{channel}`](/2.3.0/proxy/fuse/read/) | `0` and the bytes; `1`; or `2` and a message |
| `13` | write | `[id_len: u16, big-endian][id …][path_len: u16, big-endian][path …][bytes …]` | [`/fuse/write/{channel}`](/2.3.0/proxy/fuse/write/) | `0`; or `1` and a message |
| `14` | list | `[id_len: u16, big-endian][id …][path …]` | [`/fuse/list/{channel}`](/2.3.0/proxy/fuse/list/) | `0` and the entries; `1`; or `2` and a message |
| `15` | remove | `[id_len: u16, big-endian][id …][path …]` | [`/fuse/remove/{channel}`](/2.3.0/proxy/fuse/remove/) | `0`; or `1` and a message |
| `16` | rename | `[id_len: u16, big-endian][id …][from_len: u16, big-endian][from …][to …]` | [`/fuse/rename/{channel}`](/2.3.0/proxy/fuse/rename/) | `0`; or `1` and a message |
| `17` | mkdir | `[id_len: u16, big-endian][id …][path …]` | [`/fuse/mkdir/{channel}`](/2.3.0/proxy/fuse/mkdir/) | `0`; or `1` and a message |

- **The id.** The `id` with which the mount was requested, UTF-8,
  carried on every ask for that mount.
- **The path.** The path of the entry relative to the root of the
  mount: components separated by `/`, without a leading `/`, with no
  empty component and no `.` or `..` component. The path is empty
  for a file mount and for the root of a directory mount. Where the
  path is the last field of the payload it runs to the end of the
  payload; where bytes or a second path follow it, a length prefix
  precedes it.
- **A file mount.** For a mount requested with `kind` `file`, the
  proxy asks only `stat`, `read` and `write`, and every such ask
  carries the empty path. For a mount requested with `kind`
  `directory`, the proxy asks all seven operations, each carrying the
  entry's path.
- **A read-only mount.** For a mount requested with `readonly`
  `true`, the proxy asks no `write`, no `remove`, no `rename` and no
  `mkdir`.
- **One message.** The server sends exactly one message, the frame
  its page defines, and closes the connection. A path that the proxy or the server closes cleanly with no message
before the close states that the ask was not served.
- **No repetition.** The proxy does not ask an operation again. An
  operation whose path ended abruptly, and an operation whose
  `/requests` connection ended before the server opened its path,
  are reported inside the container as failed.
- **A file is one message.** A `read` answer carries the whole file
  in one message, and a `write` ask carries the whole file in one
  message. Neither is paged or split.

The target of `stat`, `read`, `list`, `remove` and `mkdir` is defined
by `diverge-provider-sdk/src/shared/containers/fuse/target.rs`:

```rust
//! What an ask names: a mount, and an entry in it.

use super::{RequestEncodeError, RequestError, prefixed};
use crate::encode::{Encode, Writer};

/// One entry of one mount: the mount's id, and the entry's path
/// relative to the mount root — empty for a file mount, and for a
/// directory mount's root.
///
/// ```text
/// [id_len: u16 BE][id: utf8…][path: utf8…]
/// ```
///
/// The path runs to the end of the payload, so it needs no prefix and
/// takes any length. This is the whole ask for [`read`](super::read),
/// [`list`](super::list), [`remove`](super::remove) and
/// [`mkdir`](super::mkdir), each of which is answered by its own
/// frame.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Target<'a> {
    /// The mount's id.
    pub id: &'a str,
    /// The entry's path inside the mount, `/`-separated, no leading
    /// slash; empty is the mount itself.
    pub path: &'a str,
}

impl Encode for Target<'_> {
    /// One way to fail: an id longer than the length prefix holds.
    type Error = RequestEncodeError;

    fn encode(&self, out: &mut Writer<'_>) -> Result<(), RequestEncodeError> {
        prefixed::put(out, self.id.as_bytes()).map_err(RequestEncodeError::IdLength)?;
        out.extend_from_slice(self.path.as_bytes());
        Ok(())
    }
}

impl<'a> Target<'a> {
    /// Decode from the bytes after the ask's kind. The id and the path
    /// borrow from `bytes`.
    pub fn decode(bytes: &'a [u8]) -> Result<Self, RequestError> {
        let (id, path) = prefixed::take(bytes)?;
        Ok(Target {
            id: std::str::from_utf8(id).map_err(|_| RequestError::IdUtf8)?,
            path: std::str::from_utf8(path).map_err(|_| RequestError::PathUtf8)?,
        })
    }
}
```

An entry of a listing, and the `Kind` — one byte on these answers,
and the string `"file"` or `"directory"` on a mount request — are
defined by `diverge-provider-sdk/src/shared/containers/fuse/entry.rs`:

```rust
//! One entry of a listed directory.

use serde::{Deserialize, Serialize};

use super::{ResponseEncodeError, ResponseError, prefixed};
use crate::encode::Writer;

/// What an entry is — and, on a
/// [`mount`](crate::container_proxy::fuse::mount) request, which kind
/// of mount: one file, or a tree. One byte on the binary answers;
/// `"file"` or `"directory"` where it rides JSON.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Kind {
    /// A regular file. Kind `0`.
    File,
    /// A directory. Kind `1`.
    Directory,
}

/// Kind byte for a file.
const FILE: u8 = 0;

/// Kind byte for a directory.
const DIRECTORY: u8 = 1;

impl Kind {
    /// The one byte that says which.
    pub(crate) fn byte(self) -> u8 {
        match self {
            Kind::File => FILE,
            Kind::Directory => DIRECTORY,
        }
    }

    /// Which, from its byte.
    pub(crate) fn from_byte(byte: u8) -> Result<Self, ResponseError> {
        match byte {
            FILE => Ok(Kind::File),
            DIRECTORY => Ok(Kind::Directory),
            other => Err(ResponseError::UnknownKind(other)),
        }
    }
}

/// One entry of a directory, as a [`list`](super::list) answers it.
///
/// ```text
/// [kind: u8][name_len: u16 BE][name: utf8…]
/// ```
///
/// The name is the entry's own, one path component, never `.` or
/// `..`. What a listing says is what a `readdir` needs and no more;
/// an entry's size is a [`stat`](super::stat) of its own.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Entry<'a> {
    /// The entry's name.
    pub name: &'a str,
    /// File or directory.
    pub kind: Kind,
}

impl Entry<'_> {
    /// Write one entry.
    pub(crate) fn encode(&self, out: &mut Writer<'_>) -> Result<(), ResponseEncodeError> {
        out.extend_from_slice(&[self.kind.byte()]);
        prefixed::put(out, self.name.as_bytes()).map_err(ResponseEncodeError::NameLength)
    }
}

impl<'a> Entry<'a> {
    /// Split one entry off the front: the entry, then the rest.
    pub(crate) fn decode(bytes: &'a [u8]) -> Result<(Self, &'a [u8]), ResponseError> {
        let (kind, rest) = bytes.split_first().ok_or(ResponseError::Truncated)?;
        let kind = Kind::from_byte(*kind)?;
        let (name, rest) = prefixed::take(rest).map_err(|_| ResponseError::Truncated)?;
        Ok((
            Entry {
                name: std::str::from_utf8(name).map_err(|_| ResponseError::NameUtf8)?,
                kind,
            },
            rest,
        ))
    }
}
```

The nine bytes of a `stat` answer are defined by
`diverge-provider-sdk/src/shared/containers/fuse/stat/stat.rs`:

```rust
//! What an entry is and how long it is.

use super::super::{Kind, ResponseError};
use crate::encode::Writer;

/// An entry's kind and size, as a [`stat`](super) answers them.
///
/// ```text
/// [kind: u8][size: u64 BE]
/// ```
///
/// The size is the file's byte length, and `0` for a directory. Nine
/// bytes, fixed: everything a `stat(2)` on the mount needs that the
/// proxy does not answer itself.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Stat {
    /// File or directory.
    pub kind: Kind,
    /// The file's length in bytes; `0` for a directory.
    pub size: u64,
}

/// The bytes a stat occupies.
const FIXED: usize = 1 + 8;

impl Stat {
    /// Write the nine bytes.
    pub(crate) fn encode(&self, out: &mut Writer<'_>) {
        out.extend_from_slice(&[self.kind.byte()]);
        out.extend_from_slice(&self.size.to_be_bytes());
    }

    /// Read the nine bytes off the front: the stat, then the rest.
    pub(crate) fn decode(bytes: &[u8]) -> Result<(Self, &[u8]), ResponseError> {
        let fixed = bytes.get(..FIXED).ok_or(ResponseError::Truncated)?;
        let kind = Kind::from_byte(fixed[0])?;
        let size: [u8; 8] = fixed[1..].try_into().expect("eight bytes were taken");
        Ok((
            Stat {
                kind,
                size: u64::from_be_bytes(size),
            },
            &bytes[FIXED..],
        ))
    }
}
```

The answer to `write`, `remove`, `rename` and `mkdir` is defined by
`diverge-provider-sdk/src/shared/containers/fuse/ack/frame.rs`:

```rust
//! Ok, or why not.

use std::convert::Infallible;

use super::super::ResponseError;
use crate::encode::{Encode, Writer};

/// The one message that answers a mutation.
///
/// ```text
/// [kind: u8][message…]
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Frame<'a> {
    /// Kind `0`. It happened: the caller holds the result.
    Ok,
    /// Kind `1`. It did not, and this says why, for a reader rather
    /// than a program: what a caller can refuse — a read-only mount it
    /// was written to anyway, a directory it will not empty, a path it
    /// will not serve — is its policy and not this specification's.
    Error(&'a str),
}

impl Encode for Frame<'_> {
    /// [`Infallible`]: a kind byte and bytes copied.
    type Error = Infallible;

    fn encode(&self, out: &mut Writer<'_>) -> Result<(), Infallible> {
        match self {
            Frame::Ok => out.extend_from_slice(&[0]),
            Frame::Error(message) => {
                out.extend_from_slice(&[1]);
                out.extend_from_slice(message.as_bytes());
            }
        }
        Ok(())
    }
}

impl<'a> Frame<'a> {
    /// Decode the one message. The message borrows from `bytes`.
    pub fn decode(bytes: &'a [u8]) -> Result<Self, ResponseError> {
        let (kind, rest) = bytes.split_first().ok_or(ResponseError::Empty)?;
        match *kind {
            0 => Ok(Frame::Ok),
            1 => std::str::from_utf8(rest)
                .map(Frame::Error)
                .map_err(|_| ResponseError::MessageUtf8),
            other => Err(ResponseError::UnknownKind(other)),
        }
    }
}
```

The ways in which an ask or an answer fails to decode are defined by
`diverge-provider-sdk/src/shared/containers/fuse/error.rs`:

```rust
//! Why a fuse ask or answer could not be read or written.

use std::error;
use std::fmt;

/// A fuse ask that could not be read.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RequestError {
    /// Fewer bytes than the ask's fixed part promises — a length
    /// prefix, or the id or path a prefix said was there.
    Truncated,
    /// An id that is not UTF-8.
    IdUtf8,
    /// A path that is not UTF-8.
    PathUtf8,
}

impl fmt::Display for RequestError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            RequestError::Truncated => {
                f.write_str("fuse request is shorter than it promises")
            }
            RequestError::IdUtf8 => f.write_str("fuse mount id is not utf-8"),
            RequestError::PathUtf8 => f.write_str("fuse path is not utf-8"),
        }
    }
}

impl error::Error for RequestError {}

/// A fuse ask that could not be written.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RequestEncodeError {
    /// An id of more bytes than a two-byte length prefix can say,
    /// carrying how many there were.
    IdLength(usize),
    /// 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 RequestEncodeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            RequestEncodeError::IdLength(len) => {
                write!(f, "fuse mount id is {len} bytes, more than 65535")
            }
            RequestEncodeError::PathLength(len) => {
                write!(f, "fuse path is {len} bytes, more than 65535")
            }
        }
    }
}

impl error::Error for RequestEncodeError {}

/// A fuse answer that could not be read.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResponseError {
    /// No bytes at all, so not even a kind.
    Empty,
    /// A kind this answer does not define — of the message, or of an
    /// entry in a listing.
    UnknownKind(u8),
    /// An error message that is not UTF-8.
    MessageUtf8,
    /// A listing shorter than its counts and lengths promise, or a
    /// stat shorter than its nine bytes.
    Truncated,
    /// An entry's name that is not UTF-8.
    NameUtf8,
}

impl fmt::Display for ResponseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ResponseError::Empty => f.write_str("fuse response is empty"),
            ResponseError::UnknownKind(kind) => {
                write!(f, "unknown fuse response kind {kind}")
            }
            ResponseError::MessageUtf8 => {
                f.write_str("fuse error message is not utf-8")
            }
            ResponseError::Truncated => {
                f.write_str("fuse answer is shorter than it promises")
            }
            ResponseError::NameUtf8 => f.write_str("fuse entry name is not utf-8"),
        }
    }
}

impl error::Error for ResponseError {}

/// A fuse answer that could not be written.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResponseEncodeError {
    /// An entry's name of more bytes than a two-byte length prefix can
    /// say, carrying how many there were.
    NameLength(usize),
}

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

impl error::Error for ResponseEncodeError {}
```
