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