ninep-proto 0.4.0

9P2000.L wire protocol types (shared by the ZeroFS 9P server and client)
Documentation
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
//! Fixed 9P wire values shared with the native kernel client.
//!
//! The same declarations are used by the kernel, server, and userspace clients.

pub const VERSION_9P2000L: &[u8] = b"9P2000.L";
pub const VERSION_9P2000L_ZEROFS: &[u8] = b"9P2000.L.Z";
pub const NOFID: u32 = u32::MAX;

pub const QID_TYPE_DIR: u8 = 0x80;
pub const QID_TYPE_SYMLINK: u8 = 0x02;
pub const QID_TYPE_FILE: u8 = 0;

pub const GETATTR_ALL: u64 = 0x0000_3fff;

pub const SETATTR_MODE: u32 = 0x0000_0001;
pub const SETATTR_UID: u32 = 0x0000_0002;
pub const SETATTR_GID: u32 = 0x0000_0004;
pub const SETATTR_SIZE: u32 = 0x0000_0008;
pub const SETATTR_ATIME: u32 = 0x0000_0010;
pub const SETATTR_MTIME: u32 = 0x0000_0020;
/// Accepted and ignored: every mutation stamps ctime from the server clock, and
/// the Linux 9p client sets this bit alongside mode, owner and size changes.
pub const SETATTR_CTIME: u32 = 0x0000_0040;
pub const SETATTR_ATIME_SET: u32 = 0x0000_0080;
pub const SETATTR_MTIME_SET: u32 = 0x0000_0100;
/// ZeroFS extension: persist an atime selected by the server for a successful
/// VFS access without treating it as an explicit metadata change.
pub const SETATTR_ATIME_ACCESS: u32 = 0x8000_0000;
pub const SETATTR_KNOWN: u32 = SETATTR_MODE
    | SETATTR_UID
    | SETATTR_GID
    | SETATTR_SIZE
    | SETATTR_ATIME
    | SETATTR_MTIME
    | SETATTR_CTIME
    | SETATTR_ATIME_SET
    | SETATTR_MTIME_SET;

pub const FALLOC_FL_KEEP_SIZE: u32 = 0x01;
pub const FALLOC_FL_PUNCH_HOLE: u32 = 0x02;
pub const FALLOC_FL_ZERO_RANGE: u32 = 0x10;

/// Standard data-only durability request bit. ZeroFS currently performs the
/// same full durability barrier whether this compatibility bit is set or not.
pub const P9_FSYNC_DATASYNC: u32 = 1 << 0;
/// `Tfsyncdur.datasync` limits the verified barrier to the inode named by its fid.
/// Without this flag the barrier is filesystem-wide, preserving compatibility
/// with clients that predate scoped verified fsync.
pub const P9_FSYNC_INODE: u32 = 1 << 1;
pub const P9_FSYNC_KNOWN_FLAGS: u32 = P9_FSYNC_DATASYNC | P9_FSYNC_INODE;

pub const P9_ENOTLEADER: u32 = 108;
pub const P9_ENOTLEADER_CLEAN: u32 = 107;
pub const P9_EOPIDSTALE: u32 = 116;
pub const P9_OP_FLAG_RETRY: u8 = 1 << 0;
pub const P9_OP_KNOWN_FLAGS: u8 = P9_OP_FLAG_RETRY;

pub const P9_MAX_MSIZE: u32 = 10 * 1024 * 1024;
pub const P9_SIZE_FIELD_LEN: usize = core::mem::size_of::<u32>();
pub const P9_TYPE_FIELD_LEN: usize = core::mem::size_of::<u8>();
pub const P9_TAG_FIELD_LEN: usize = core::mem::size_of::<u16>();
pub const P9_COUNT_FIELD_LEN: usize = core::mem::size_of::<u32>();
pub const P9_HEADER_SIZE: usize = P9_SIZE_FIELD_LEN + P9_TYPE_FIELD_LEN + P9_TAG_FIELD_LEN;
pub const P9_MIN_MESSAGE_SIZE: u32 = P9_HEADER_SIZE as u32;
pub const P9_IOHDRSZ: u32 = (P9_HEADER_SIZE + P9_COUNT_FIELD_LEN) as u32;
pub const P9_TWRITE_HDR: u32 = (P9_HEADER_SIZE + 4 + 8 + P9_COUNT_FIELD_LEN) as u32;
pub const P9_RLOPENATREAD_HDR: u32 =
    (P9_HEADER_SIZE + Qid::WIRE_SIZE + 4 + 1 + P9_COUNT_FIELD_LEN) as u32;

pub const P9_MAX_GROUPS: usize = 16;
pub const P9_MAX_NAME_LEN: u32 = 255;

pub const P9_REBIND_REPLAY: u8 = 1 << 0;
pub const P9_REBIND_OPENED: u8 = 1 << 1;
pub const P9_REBIND_KNOWN_FLAGS: u8 = P9_REBIND_REPLAY | P9_REBIND_OPENED;
pub const P9_REBIND_CREDENTIAL_SENTINEL: u8 = 0xff;
pub const P9_REBIND_CREDENTIAL_VERSION: u8 = 1;
/// High bit of the credential group-count byte. The sender supplied the
/// primary gid, could not encode the complete supplementary-group list, and
/// already performed the caller's group DAC check locally.
pub const P9_REBIND_CREDENTIAL_GROUPS_INCOMPLETE: u8 = 1 << 7;
pub const P9_REBIND_CREDENTIAL_HEADER_SIZE: usize = 7;
pub const P9_REBIND_CREDENTIAL_MAX_SIZE: usize =
    P9_REBIND_CREDENTIAL_HEADER_SIZE + P9_MAX_GROUPS * 4;

pub const P9_OP_ID_LEN: usize = 16;
pub const P9_OP_FLAGS_LEN: usize = 1;
pub const P9_OP_ORIGIN_EPOCH_LEN: usize = 8;
pub const P9_OP_ENVELOPE_LEN: usize = P9_OP_ID_LEN + P9_OP_FLAGS_LEN + P9_OP_ORIGIN_EPOCH_LEN;

/// Storage backing a variable-length wire field.
///
/// The shared representation only needs a byte slice. Ownership and
/// allocation policy are supplied by userspace (`Vec`/`Bytes`) or the kernel
/// (borrowed slices and kernel-owned buffers).
pub trait ByteStorage: AsRef<[u8]> {}

impl<T: AsRef<[u8]> + ?Sized> ByteStorage for T {}

/// A variable-length byte field whose length is carried by its enclosing
/// message.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct WireBytes<B: ByteStorage>(pub B);

impl<B: ByteStorage> WireBytes<B> {
    pub fn new(bytes: B) -> Self {
        Self(bytes)
    }

    pub fn len(&self) -> usize {
        self.0.as_ref().len()
    }

    pub fn is_empty(&self) -> bool {
        self.0.as_ref().is_empty()
    }
}

impl<B: ByteStorage> From<B> for WireBytes<B> {
    fn from(bytes: B) -> Self {
        Self(bytes)
    }
}

impl<B: ByteStorage> AsRef<[u8]> for WireBytes<B> {
    fn as_ref(&self) -> &[u8] {
        self.0.as_ref()
    }
}

impl<B: ByteStorage> core::ops::Deref for WireBytes<B> {
    type Target = B;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

/// A length-prefixed 9P string using caller-selected byte storage.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WireString<B: ByteStorage> {
    pub len: u16,
    pub data: B,
}

impl<B: ByteStorage> WireString<B> {
    pub fn from_storage(data: B) -> Self {
        Self {
            len: data.as_ref().len() as u16,
            data,
        }
    }

    pub fn as_str(&self) -> Result<&str, core::str::Utf8Error> {
        core::str::from_utf8(self.data.as_ref())
    }

    pub fn len(&self) -> usize {
        self.data.as_ref().len()
    }

    pub fn is_empty(&self) -> bool {
        self.data.as_ref().is_empty()
    }

    /// Serialized size on the wire: u16 length prefix + bytes.
    pub fn wire_size(&self) -> usize {
        2 + self.len()
    }
}

impl<B: ByteStorage> AsRef<[u8]> for WireString<B> {
    fn as_ref(&self) -> &[u8] {
        self.data.as_ref()
    }
}

/// Idempotency and HA-origin fields carried by a ZeroFS mutation.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct MutationEnvelope {
    pub op_id: [u8; 16],
    pub flags: u8,
    pub origin_writer_epoch: u64,
}

/// 9P2000.L and ZeroFS-private message identifiers.
pub mod message_type {
    pub const RLERROR: u8 = 7;
    pub const TSTATFS: u8 = 8;
    pub const RSTATFS: u8 = 9;
    pub const TLOPEN: u8 = 12;
    pub const RLOPEN: u8 = 13;
    pub const TLCREATE: u8 = 14;
    pub const RLCREATE: u8 = 15;
    pub const TSYMLINK: u8 = 16;
    pub const RSYMLINK: u8 = 17;
    pub const TMKNOD: u8 = 18;
    pub const RMKNOD: u8 = 19;
    pub const TRENAME: u8 = 20;
    pub const RRENAME: u8 = 21;
    pub const TREADLINK: u8 = 22;
    pub const RREADLINK: u8 = 23;
    pub const TGETATTR: u8 = 24;
    pub const RGETATTR: u8 = 25;
    pub const TSETATTR: u8 = 26;
    pub const RSETATTR: u8 = 27;
    pub const TXATTRWALK: u8 = 30;
    pub const RXATTRWALK: u8 = 31;
    pub const TREADDIR: u8 = 40;
    pub const RREADDIR: u8 = 41;
    pub const TFSYNC: u8 = 50;
    pub const RFSYNC: u8 = 51;
    pub const TLOCK: u8 = 52;
    pub const RLOCK: u8 = 53;
    pub const TGETLOCK: u8 = 54;
    pub const RGETLOCK: u8 = 55;
    pub const TLINK: u8 = 70;
    pub const RLINK: u8 = 71;
    pub const TMKDIR: u8 = 72;
    pub const RMKDIR: u8 = 73;
    pub const TRENAMEAT: u8 = 74;
    pub const RRENAMEAT: u8 = 75;
    pub const TUNLINKAT: u8 = 76;
    pub const RUNLINKAT: u8 = 77;
    pub const TVERSION: u8 = 100;
    pub const RVERSION: u8 = 101;
    pub const TATTACH: u8 = 104;
    pub const RATTACH: u8 = 105;
    pub const TFLUSH: u8 = 108;
    pub const RFLUSH: u8 = 109;
    pub const TWALK: u8 = 110;
    pub const RWALK: u8 = 111;
    pub const TREAD: u8 = 116;
    pub const RREAD: u8 = 117;
    pub const TWRITE: u8 = 118;
    pub const RWRITE: u8 = 119;
    pub const TCLUNK: u8 = 120;
    pub const RCLUNK: u8 = 121;
    pub const TFALLOCATE: u8 = 228;
    pub const RFALLOCATE: u8 = 229;
    pub const TLOPENATREAD: u8 = 230;
    pub const RLOPENATREAD: u8 = 231;
    pub const TFSYNCDUR: u8 = 232;
    pub const TGETLINEAGE: u8 = 233;
    pub const RGETLINEAGE: u8 = 234;
    pub const TLOPENAT: u8 = 236;
    pub const RLOPENAT: u8 = 237;
    pub const TLCREATEATTR: u8 = 238;
    pub const RLCREATEATTR: u8 = 239;
    pub const TMKDIRATTR: u8 = 240;
    pub const RMKDIRATTR: u8 = 241;
    pub const TSYMLINKATTR: u8 = 242;
    pub const RSYMLINKATTR: u8 = 243;
    pub const TMKNODATTR: u8 = 244;
    pub const RMKNODATTR: u8 = 245;
    pub const TLINKATTR: u8 = 246;
    pub const RLINKATTR: u8 = 247;
    pub const TSETATTRATTR: u8 = 248;
    pub const RSETATTRATTR: u8 = 249;
    pub const TREBIND: u8 = 250;
    pub const RREBIND: u8 = 251;
    pub const TWALKGETATTR: u8 = 252;
    pub const RWALKGETATTR: u8 = 253;
    pub const TREADDIRATTR: u8 = 254;
    pub const RREADDIRATTR: u8 = 255;
}

/// A ZeroFS inode identity.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Qid {
    pub type_: u8,
    pub version: u32,
    pub path: u64,
}

impl Qid {
    /// Serialized size on the wire: type u8 + version u32 + path u64.
    pub const WIRE_SIZE: usize = 1 + 4 + 8;
}

/// Attributes returned by ZeroFS.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Stat {
    pub qid: Qid,
    pub mode: u32,
    pub uid: u32,
    pub gid: u32,
    pub nlink: u64,
    pub rdev: u64,
    pub size: u64,
    pub blksize: u64,
    pub blocks: u64,
    pub atime_sec: u64,
    pub atime_nsec: u64,
    pub mtime_sec: u64,
    pub mtime_nsec: u64,
    pub ctime_sec: u64,
    pub ctime_nsec: u64,
    pub btime_sec: u64,
    pub btime_nsec: u64,
    pub r#gen: u64,
    /// Standard 9P2000.L content-change counter; ZeroFS currently emits zero.
    pub data_version: u64,
}

impl Stat {
    /// Serialized size on the wire: qid + three u32s + fifteen u64s.
    pub const WIRE_SIZE: usize = Qid::WIRE_SIZE + 3 * 4 + 15 * 8;
}

// Replies whose every field is fixed width need no storage parameter, so one
// declaration serves the owned and borrowed codec alike.

/// Durability lineage and the active HA writer epoch.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rgetlineage {
    pub token: u64,
    /// HA writer epoch used as mutation-envelope origin; zero when standalone.
    pub writer_epoch: u64,
}

/// Identity of a fid rebound by inode id.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rrebind {
    pub qid: Qid,
}

/// Attributes for a fid, with the mask of fields the server answered.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rgetattr {
    pub valid: u64,
    pub stat: Stat,
}

/// Standard open reply layout, also used on the wire by private `Rlopenat`.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rlopen {
    pub qid: Qid,
    pub iounit: u32,
}

/// Post-operation stat of a created and opened regular file.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rlcreateattr {
    pub iounit: u32,
    pub stat: Stat,
}

/// Bytes accepted by a write.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rwrite {
    pub count: u32,
}

/// Remote filesystem capacity and limits.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rstatfs {
    /// Filesystem type.
    pub r#type: u32,
    /// Optimal transfer block size.
    pub bsize: u32,
    /// Total data blocks.
    pub blocks: u64,
    /// Free data blocks.
    pub bfree: u64,
    /// Free blocks available to non-superusers.
    pub bavail: u64,
    /// Total file nodes.
    pub files: u64,
    /// Free file nodes.
    pub ffree: u64,
    /// Filesystem id.
    pub fsid: u64,
    /// Maximum filename length.
    pub namelen: u32,
}

/// Outcome of a `Tlock` request.
///
/// [`Rlock`] keeps the wire field raw so an unknown status fails that request
/// without turning a decodable frame into a session-wide protocol fault.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum LockStatus {
    /// The range is held by the requesting fid.
    Success = 0,
    /// A conflicting lock exists and the request wanted to block.
    Blocked = 1,
    /// The server refused the lock.
    LockError = 2,
    /// The server is in its post-restart grace period.
    Grace = 3,
}

impl LockStatus {
    pub const fn as_wire(self) -> u8 {
        self as u8
    }

    /// The status this byte names, or `None` for one this protocol does not
    /// define.
    pub const fn from_wire(status: u8) -> Option<Self> {
        match status {
            0 => Some(Self::Success),
            1 => Some(Self::Blocked),
            2 => Some(Self::LockError),
            3 => Some(Self::Grace),
            _ => None,
        }
    }
}

/// Outcome of a lock request, as the raw wire byte. Map it with
/// [`LockStatus::from_wire`].
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rlock {
    pub status: u8,
}

/// A failed request's Linux errno.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rlerror {
    pub ecode: u32,
}