Skip to main content

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}