Skip to main content

arcbox_vm_proto/
file.rs

1//! File channel: the guest vsock port the agent serves file I/O on.
2//!
3//! Frame format is identical to the exec channel: `[u8 type][u32 LE len][payload]`.
4//! One vsock connection per operation; the agent closes after sending the
5//! final frame.
6//!
7//! | Hex  | Name              | Direction      | Payload                          |
8//! |------|-------------------|----------------|----------------------------------|
9//! | 0x20 | [`FILE_WRITE_REQ`]  | Host → Agent   | JSON `{"path": str, "mode": u32}`|
10//! | 0x21 | [`FILE_DATA`]       | bidirectional  | raw bytes (one chunk)            |
11//! | 0x22 | [`FILE_DONE`]       | bidirectional  | empty — end of data stream       |
12//! | 0x23 | [`FILE_READ_REQ`]   | Host → Agent   | JSON `{"path": str}`             |
13//! | 0x24 | [`FILE_STAT_REQ`]   | Host → Agent   | JSON [`StatReq`]                 |
14//! | 0x25 | [`FILE_LIST_REQ`]   | Host → Agent   | JSON [`ListDirReq`]              |
15//! | 0x26 | [`FILE_MKDIR_REQ`]  | Host → Agent   | JSON [`MakeDirReq`]              |
16//! | 0x27 | [`FILE_REMOVE_REQ`] | Host → Agent   | JSON [`RemoveReq`]               |
17//! | 0x28 | [`FILE_MOVE_REQ`]   | Host → Agent   | JSON [`MoveReq`]                 |
18//! | 0x29 | [`FILE_WATCH_REQ`]  | Host → Agent   | JSON [`WatchReq`]                |
19//! | 0x30 | [`FILE_ACK`]        | Agent → Host   | empty — operation succeeded      |
20//! | 0x31 | [`FILE_ERR`]        | Agent → Host   | UTF-8 error message              |
21//! | 0x32 | [`FILE_STAT`]       | Agent → Host   | JSON [`FileStatDto`]             |
22//! | 0x33 | [`FILE_LIST`]       | Agent → Host   | JSON `[FileStatDto, ...]`        |
23//! | 0x34 | [`FILE_EVENT`]      | Agent → Host   | JSON [`FsEventDto`]              |
24//!
25//! Stat/List answer one data frame; MakeDir/Remove/Move answer [`FILE_ACK`].
26//! Watch answers [`FILE_ACK`] once the inotify watch is established, then
27//! streams [`FILE_EVENT`] frames until either side closes the connection —
28//! the host closing it is the cancellation signal, the agent side closing
29//! it (sandbox stop) is the clean end of the stream.
30//!
31//! [`FILE_ERR`] payloads carry a machine-readable errno prefix
32//! ([`ERR_NOT_FOUND`] and friends) on every verb — the path verbs and the
33//! read/write flows alike — so the host maps them onto typed errors instead
34//! of a blanket transport error.
35//!
36//! ## Write flow
37//! ```text
38//! Host  →  FILE_WRITE_REQ  {path, mode}
39//! Host  →  FILE_DATA       [chunk 1..N]
40//! Host  →  FILE_DONE
41//!           ←  FILE_ACK  (success)
42//!           ←  FILE_ERR  (failure)
43//! ```
44//!
45//! ## Read flow
46//! ```text
47//! Host  →  FILE_READ_REQ  {path}
48//!           ←  FILE_DATA  [chunk 1..N]
49//!           ←  FILE_DONE  (success — all bytes sent)
50//!           ←  FILE_ERR   (failure)
51//! ```
52
53use serde::{Deserialize, Serialize};
54
55/// Guest-side vsock port for file I/O.
56pub const FILE_PORT: u32 = 53;
57
58/// Open a file for writing. Payload: JSON `{"path": str, "mode": u32}`.
59pub const FILE_WRITE_REQ: u8 = 0x20;
60/// One chunk of file bytes, in either direction.
61pub const FILE_DATA: u8 = 0x21;
62/// End of a data stream, in either direction. Empty payload.
63pub const FILE_DONE: u8 = 0x22;
64/// Read a file. Payload: JSON `{"path": str}`.
65pub const FILE_READ_REQ: u8 = 0x23;
66/// Stat one entry. Payload: JSON [`StatReq`].
67pub const FILE_STAT_REQ: u8 = 0x24;
68/// List a directory. Payload: JSON [`ListDirReq`].
69pub const FILE_LIST_REQ: u8 = 0x25;
70/// Create a directory. Payload: JSON [`MakeDirReq`].
71pub const FILE_MKDIR_REQ: u8 = 0x26;
72/// Remove an entry. Payload: JSON [`RemoveReq`].
73pub const FILE_REMOVE_REQ: u8 = 0x27;
74/// Rename an entry. Payload: JSON [`MoveReq`].
75pub const FILE_MOVE_REQ: u8 = 0x28;
76/// Watch a directory. Payload: JSON [`WatchReq`].
77pub const FILE_WATCH_REQ: u8 = 0x29;
78/// The operation succeeded. Empty payload.
79pub const FILE_ACK: u8 = 0x30;
80/// The operation failed. Payload: UTF-8 message, errno-prefixed.
81pub const FILE_ERR: u8 = 0x31;
82/// One entry's metadata. Payload: JSON [`FileStatDto`].
83pub const FILE_STAT: u8 = 0x32;
84/// A directory listing. Payload: JSON array of [`FileStatDto`].
85pub const FILE_LIST: u8 = 0x33;
86/// One filesystem event on a watched tree. Payload: JSON [`FsEventDto`].
87pub const FILE_EVENT: u8 = 0x34;
88
89/// Maximum total file size for file I/O operations (256 MiB).
90pub const MAX_FILE_SIZE: usize = 256 * 1024 * 1024;
91
92// Machine-readable errno prefixes on `FILE_ERR` payloads for the path
93// verbs. The remainder of the payload is the affected path; the host maps
94// each prefix onto a typed error.
95
96/// `FILE_ERR` prefix: the path does not exist.
97pub const ERR_NOT_FOUND: &str = "ENOENT: ";
98/// `FILE_ERR` prefix: a directory was expected.
99pub const ERR_NOT_A_DIRECTORY: &str = "ENOTDIR: ";
100/// `FILE_ERR` prefix: a non-recursive remove hit a non-empty directory.
101pub const ERR_NOT_EMPTY: &str = "ENOTEMPTY: ";
102
103// `FileStatDto.kind` vocabulary.
104
105/// [`FileStatDto::kind`]: a regular file.
106pub const KIND_FILE: &str = "file";
107/// [`FileStatDto::kind`]: a directory.
108pub const KIND_DIR: &str = "dir";
109/// [`FileStatDto::kind`]: a symbolic link (reported, never followed).
110pub const KIND_SYMLINK: &str = "symlink";
111/// [`FileStatDto::kind`]: anything else (device, socket, fifo, ...).
112pub const KIND_OTHER: &str = "other";
113
114// `FsEventDto.kind` vocabulary.
115
116/// [`FsEventDto::kind`]: an entry appeared.
117pub const EVENT_CREATED: &str = "created";
118/// [`FsEventDto::kind`]: an entry's contents or attributes changed.
119pub const EVENT_MODIFIED: &str = "modified";
120/// [`FsEventDto::kind`]: an entry disappeared.
121pub const EVENT_REMOVED: &str = "removed";
122/// [`FsEventDto::kind`]: an entry was renamed within the watched tree.
123pub const EVENT_RENAMED: &str = "renamed";
124
125/// [`FILE_STAT_REQ`] payload.
126#[derive(Debug, Serialize, Deserialize)]
127pub struct StatReq {
128    /// Guest path.
129    pub path: String,
130}
131
132/// [`FILE_LIST_REQ`] payload.
133#[derive(Debug, Serialize, Deserialize)]
134pub struct ListDirReq {
135    /// Guest path of the directory.
136    pub path: String,
137}
138
139/// [`FILE_MKDIR_REQ`] payload. `mode` carries the Unix permission bits
140/// for created directories; `0` defaults to `0o755` on the agent side.
141#[derive(Debug, Serialize, Deserialize)]
142pub struct MakeDirReq {
143    /// Guest path to create (parents included).
144    pub path: String,
145    /// Permission bits, or `0` for the default.
146    pub mode: u32,
147}
148
149/// [`FILE_REMOVE_REQ`] payload.
150#[derive(Debug, Serialize, Deserialize)]
151pub struct RemoveReq {
152    /// Guest path.
153    pub path: String,
154    /// Remove directories and their contents.
155    pub recursive: bool,
156}
157
158/// [`FILE_MOVE_REQ`] payload.
159#[derive(Debug, Serialize, Deserialize)]
160pub struct MoveReq {
161    /// Guest source path.
162    pub from: String,
163    /// Guest destination path.
164    pub to: String,
165}
166
167/// [`FILE_WATCH_REQ`] payload.
168#[derive(Debug, Serialize, Deserialize)]
169pub struct WatchReq {
170    /// Guest path of the directory to watch.
171    pub path: String,
172    /// Also watch subdirectories, present and future.
173    pub recursive: bool,
174}
175
176/// Metadata of one filesystem entry ([`FILE_STAT`] / [`FILE_LIST`] payload).
177///
178/// Mirrors `arcbox.sandbox.v1.FileStat`: symlinks are reported, never
179/// followed; `mode` is the low 12 bits of `st_mode`; `size` is 0 for
180/// non-regular files.
181#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
182pub struct FileStatDto {
183    /// Entry name (a listing) or the requested path's final component.
184    pub name: String,
185    /// One of the `KIND_*` constants.
186    pub kind: String,
187    /// Byte size for regular files, `0` otherwise.
188    pub size: u64,
189    /// Low 12 permission bits of `st_mode`.
190    pub mode: u32,
191    /// Modification time, seconds since the Unix epoch.
192    pub mtime_secs: i64,
193    /// Modification time, nanosecond part.
194    pub mtime_nanos: u32,
195    /// Owner uid.
196    pub uid: u32,
197    /// Owner gid.
198    pub gid: u32,
199    /// Link target for symlinks, empty otherwise.
200    #[serde(default)]
201    pub symlink_target: String,
202}
203
204/// One filesystem event ([`FILE_EVENT`] payload). Mirrors
205/// `arcbox.sandbox.v1.FsEvent`: `path` is the old path for renames,
206/// with `renamed_to` set only then.
207#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
208pub struct FsEventDto {
209    /// One of the `EVENT_*` constants.
210    pub kind: String,
211    /// Affected path (the old path for a rename).
212    pub path: String,
213    /// New path for a rename, empty otherwise.
214    #[serde(default)]
215    pub renamed_to: String,
216}