microsandbox_protocol/fs.rs
1//! Filesystem-related protocol message payloads.
2
3use serde::{Deserialize, Serialize};
4
5use crate::bulk::BulkOffer;
6
7//--------------------------------------------------------------------------------------------------
8// Constants
9//--------------------------------------------------------------------------------------------------
10
11/// Maximum chunk size for streaming file data (3 MiB).
12///
13/// This stays safely under the 4 MiB frame limit after CBOR envelope overhead.
14pub const FS_CHUNK_SIZE: usize = 3 * 1024 * 1024;
15
16//--------------------------------------------------------------------------------------------------
17// Types
18//--------------------------------------------------------------------------------------------------
19
20/// A filesystem operation requested by the host.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub enum FsOp {
23 /// Resolve a path to its canonical absolute form.
24 RealPath {
25 /// Guest path to resolve.
26 path: String,
27 },
28
29 /// Get metadata for a path.
30 Stat {
31 /// Guest path to stat.
32 path: String,
33
34 /// Whether to follow symlinks.
35 follow_symlink: bool,
36 },
37
38 /// Update metadata for a path.
39 SetStat {
40 /// Guest path to update.
41 path: String,
42
43 /// Whether to follow symlinks.
44 follow_symlink: bool,
45
46 /// Attributes to update.
47 attrs: FsSetAttrs,
48 },
49
50 /// List directory contents.
51 List {
52 /// Guest directory path to list.
53 path: String,
54 },
55
56 /// Read a symlink target.
57 ReadLink {
58 /// Guest symlink path to read.
59 path: String,
60 },
61
62 /// Create a symlink.
63 Symlink {
64 /// Symlink target.
65 target: String,
66
67 /// Symlink path to create.
68 link_path: String,
69
70 /// Guest user (`user[:group]`) to create the symlink as. `None` uses root.
71 #[serde(default)]
72 user: Option<String>,
73 },
74
75 /// Create a directory (and parents).
76 Mkdir {
77 /// Guest directory path to create.
78 path: String,
79
80 /// Permission bits to set on creation (e.g. 0o755).
81 #[serde(default)]
82 mode: Option<u32>,
83
84 /// Guest user (`user[:group]`) to create the directories as. `None` uses root.
85 #[serde(default)]
86 user: Option<String>,
87 },
88
89 /// Remove a file.
90 Remove {
91 /// Guest file path to remove.
92 path: String,
93 },
94
95 /// Remove a directory.
96 RemoveDir {
97 /// Guest directory path to remove.
98 path: String,
99
100 /// Whether to remove recursively.
101 recursive: bool,
102 },
103
104 /// Copy a file or directory within the guest.
105 Copy {
106 /// Source path in guest.
107 src: String,
108 /// Destination path in guest.
109 dst: String,
110 },
111
112 /// Rename/move a file or directory.
113 Rename {
114 /// Source path in guest.
115 src: String,
116 /// Destination path in guest.
117 dst: String,
118 },
119
120 /// Open a file and allocate an agentd-side handle.
121 OpenFile {
122 /// Guest file path to open.
123 path: String,
124
125 /// File open options.
126 options: FsOpenOptions,
127 },
128
129 /// Open a directory and allocate an agentd-side handle.
130 OpenDir {
131 /// Guest directory path to open.
132 path: String,
133 },
134
135 /// Close a file or directory handle.
136 CloseHandle {
137 /// Agentd-side handle.
138 handle: u64,
139 },
140
141 /// Read from an open file handle.
142 Read {
143 /// Agentd-side file handle.
144 handle: u64,
145
146 /// Byte offset to read from.
147 offset: u64,
148
149 /// Maximum bytes to read. `None` means read to EOF.
150 len: Option<u64>,
151 },
152
153 /// Write to an open file handle.
154 Write {
155 /// Agentd-side file handle.
156 handle: u64,
157
158 /// Byte offset to write at.
159 offset: u64,
160
161 /// Expected byte count. `None` disables count validation.
162 len: Option<u64>,
163 },
164
165 /// Read the next batch of entries from an open directory handle.
166 ReadDir {
167 /// Agentd-side directory handle.
168 handle: u64,
169
170 /// Maximum entries to return. `None` uses the agent default.
171 limit: Option<u32>,
172 },
173
174 /// Get metadata for an open file or directory handle.
175 FStat {
176 /// Agentd-side handle.
177 handle: u64,
178 },
179
180 /// Update metadata for an open file handle.
181 FSetStat {
182 /// Agentd-side handle.
183 handle: u64,
184
185 /// Attributes to update.
186 attrs: FsSetAttrs,
187 },
188}
189
190/// Attributes accepted by setstat-style filesystem operations.
191#[derive(Debug, Clone, Default, Serialize, Deserialize)]
192pub struct FsSetAttrs {
193 /// Unix permission bits.
194 pub mode: Option<u32>,
195
196 /// Owner user ID.
197 pub uid: Option<u32>,
198
199 /// Owner group ID.
200 pub gid: Option<u32>,
201
202 /// File size.
203 pub size: Option<u64>,
204
205 /// Access time as Unix timestamp seconds.
206 pub atime: Option<i64>,
207
208 /// Modification time as Unix timestamp seconds.
209 pub mtime: Option<i64>,
210}
211
212/// Options used when opening a file handle.
213#[derive(Debug, Clone, Default, Serialize, Deserialize)]
214pub struct FsOpenOptions {
215 /// Open for reading.
216 pub read: bool,
217
218 /// Open for writing.
219 pub write: bool,
220
221 /// Append writes to the end.
222 pub append: bool,
223
224 /// Create the file if it is missing.
225 pub create: bool,
226
227 /// Truncate the file after opening.
228 pub truncate: bool,
229
230 /// Create a new file and fail if it already exists.
231 pub create_new: bool,
232
233 /// Permission bits to set on creation.
234 pub mode: Option<u32>,
235
236 /// Guest user (`user[:group]`) to open the file as. `None` uses root.
237 #[serde(default)]
238 pub user: Option<String>,
239}
240
241/// Request to perform a filesystem operation in the guest.
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct FsRequest {
244 /// The operation to perform.
245 pub op: FsOp,
246
247 /// Generation-8 raw-bulk offer for streaming reads and writes.
248 #[serde(default, skip_serializing_if = "Option::is_none")]
249 pub bulk: Option<BulkOffer>,
250}
251
252/// Metadata about a filesystem entry (wire format).
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct FsEntryInfo {
255 /// Path of the entry.
256 pub path: String,
257
258 /// Kind of entry: `"file"`, `"dir"`, `"symlink"`, or `"other"`.
259 pub kind: String,
260
261 /// Size in bytes.
262 pub size: u64,
263
264 /// Unix permission bits.
265 pub mode: u32,
266
267 /// Last modification time as Unix timestamp (seconds since epoch).
268 pub modified: Option<i64>,
269
270 /// Owner user ID.
271 pub uid: u32,
272
273 /// Owner group ID.
274 pub gid: u32,
275
276 /// Last access time as Unix timestamp (seconds since epoch).
277 pub atime: Option<i64>,
278
279 /// Last modification time as Unix timestamp (seconds since epoch).
280 pub mtime: Option<i64>,
281}
282
283/// Data variants that can be included in a filesystem response.
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub enum FsResponseData {
286 /// Stat result.
287 Stat(FsEntryInfo),
288
289 /// Directory listing result.
290 List(Vec<FsEntryInfo>),
291
292 /// Open handle.
293 Handle(u64),
294
295 /// Resolved path or symlink target.
296 Path(String),
297}
298
299/// Terminal response for a filesystem operation.
300///
301/// This is always the last message sent for a given correlation ID.
302/// For streaming reads, it follows the `FsData` chunks.
303/// For simple operations, it carries the result directly.
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct FsResponse {
306 /// Whether the operation succeeded.
307 pub ok: bool,
308
309 /// Error message if `ok` is false.
310 #[serde(default)]
311 pub error: Option<String>,
312
313 /// Optional result data (for stat/list operations).
314 #[serde(default)]
315 pub data: Option<FsResponseData>,
316}
317
318/// A chunk of file data for streaming read/write operations.
319///
320/// An empty `data` field signals EOF (like `ExecStdin` with empty data).
321#[derive(Debug, Serialize, Deserialize)]
322pub struct FsData {
323 /// The raw file data.
324 #[serde(with = "serde_bytes")]
325 pub data: Vec<u8>,
326}