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}