use crate::deku_bytes::DekuBytes;
use deku::prelude::*;
pub const VERSION_9P2000L: &[u8] = b"9P2000.L";
pub const VERSION_9P2000L_ZEROFS: &[u8] = b"9P2000.L.zerofs";
pub const VERSION_9P2000L_ZEROFS2: &[u8] = b"9P2000.L.zerofs2";
pub const VERSION_9P2000L_ZEROFS3: &[u8] = b"9P2000.L.zerofs3";
pub const VERSION_9P2000L_ZEROFS4: &[u8] = b"9P2000.L.zerofs4";
pub const QID_TYPE_DIR: u8 = 0x80;
pub const QID_TYPE_SYMLINK: u8 = 0x02;
pub const QID_TYPE_FILE: u8 = 0x00;
pub const GETATTR_ALL: u64 = 0x00003fff;
pub const SETATTR_MODE: u32 = 0x00000001;
pub const SETATTR_UID: u32 = 0x00000002;
pub const SETATTR_GID: u32 = 0x00000004;
pub const SETATTR_SIZE: u32 = 0x00000008;
pub const SETATTR_ATIME: u32 = 0x00000010;
pub const SETATTR_MTIME: u32 = 0x00000020;
pub const SETATTR_ATIME_SET: u32 = 0x00000080;
pub const SETATTR_MTIME_SET: u32 = 0x00000100;
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
#[deku(id_type = "u8")]
pub enum LockType {
#[deku(id = "0")]
ReadLock, #[deku(id = "1")]
WriteLock, #[deku(id = "2")]
Unlock, }
pub const P9_LOCK_FLAGS_BLOCK: u32 = 1;
pub const P9_ENOTLEADER: u32 = 108;
pub const P9_MAX_MSIZE: u32 = 10 * 1024 * 1024;
pub const P9_CHANNEL_SIZE: usize = 1000;
pub const P9_SIZE_FIELD_LEN: usize = std::mem::size_of::<u32>();
pub const P9_TYPE_FIELD_LEN: usize = std::mem::size_of::<u8>();
pub const P9_TAG_FIELD_LEN: usize = std::mem::size_of::<u16>();
pub const P9_COUNT_FIELD_LEN: usize = std::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_DEBUG_BUFFER_SIZE: usize = 40;
pub const P9_READDIR_BATCH_SIZE: usize = 1000;
pub const P9_MAX_GROUPS: usize = 16;
pub const P9_NOBODY_UID: u32 = 65534;
pub const P9_MAX_NAME_LEN: u32 = 255;
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
#[deku(id_type = "u8")]
pub enum LockStatus {
#[deku(id = "0")]
Success,
#[deku(id = "1")]
Blocked,
#[deku(id = "2")]
LockError,
#[deku(id = "3")]
Grace,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Qid {
pub type_: u8,
#[deku(endian = "little")]
pub version: u32,
#[deku(endian = "little")]
pub path: u64,
}
impl Qid {
pub const WIRE_SIZE: usize = 1 + 4 + 8;
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Stat {
pub qid: Qid,
#[deku(endian = "little")]
pub mode: u32,
#[deku(endian = "little")]
pub uid: u32,
#[deku(endian = "little")]
pub gid: u32,
#[deku(endian = "little")]
pub nlink: u64,
#[deku(endian = "little")]
pub rdev: u64,
#[deku(endian = "little")]
pub size: u64,
#[deku(endian = "little")]
pub blksize: u64,
#[deku(endian = "little")]
pub blocks: u64,
#[deku(endian = "little")]
pub atime_sec: u64,
#[deku(endian = "little")]
pub atime_nsec: u64,
#[deku(endian = "little")]
pub mtime_sec: u64,
#[deku(endian = "little")]
pub mtime_nsec: u64,
#[deku(endian = "little")]
pub ctime_sec: u64,
#[deku(endian = "little")]
pub ctime_nsec: u64,
#[deku(endian = "little")]
pub btime_sec: u64,
#[deku(endian = "little")]
pub btime_nsec: u64,
#[deku(endian = "little")]
pub r#gen: u64,
#[deku(endian = "little")]
pub data_version: u64,
}
impl Stat {
pub const WIRE_SIZE: usize = Qid::WIRE_SIZE + 3 * 4 + 15 * 8;
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct P9String {
#[deku(endian = "little", update = "self.data.len()")]
pub len: u16,
#[deku(count = "len")]
pub data: Vec<u8>,
}
impl P9String {
pub fn new(data: Vec<u8>) -> Self {
Self {
len: data.len() as u16,
data,
}
}
pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
std::str::from_utf8(&self.data)
}
pub fn wire_size(&self) -> usize {
2 + self.data.len()
}
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct DirEntry {
pub qid: Qid,
#[deku(endian = "little")]
pub offset: u64,
pub type_: u8,
pub name: P9String,
}
impl DirEntry {
pub fn wire_size(&self) -> usize {
Qid::WIRE_SIZE + 8 + 1 + self.name.wire_size()
}
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tversion {
#[deku(endian = "little")]
pub msize: u32,
pub version: P9String,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tattach {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub afid: u32,
pub uname: P9String,
pub aname: P9String,
#[deku(endian = "little")]
pub n_uname: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Twalk {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub newfid: u32,
#[deku(endian = "little", update = "self.wnames.len()")]
pub nwname: u16,
#[deku(count = "nwname")]
pub wnames: Vec<P9String>,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tlopen {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub flags: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tlcreate {
#[deku(endian = "little")]
pub fid: u32,
pub name: P9String,
#[deku(endian = "little")]
pub flags: u32,
#[deku(endian = "little")]
pub mode: u32,
#[deku(endian = "little")]
pub gid: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tread {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub offset: u64,
#[deku(endian = "little")]
pub count: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Twrite {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub offset: u64,
#[deku(endian = "little")]
pub count: u32,
#[deku(ctx = "count")]
pub data: DekuBytes,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tclunk {
#[deku(endian = "little")]
pub fid: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Treaddir {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub offset: u64,
#[deku(endian = "little")]
pub count: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tgetattr {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub request_mask: u64,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tsetattr {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub valid: u32,
#[deku(endian = "little")]
pub mode: u32,
#[deku(endian = "little")]
pub uid: u32,
#[deku(endian = "little")]
pub gid: u32,
#[deku(endian = "little")]
pub size: u64,
#[deku(endian = "little")]
pub atime_sec: u64,
#[deku(endian = "little")]
pub atime_nsec: u64,
#[deku(endian = "little")]
pub mtime_sec: u64,
#[deku(endian = "little")]
pub mtime_nsec: u64,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tmkdir {
#[deku(endian = "little")]
pub dfid: u32,
pub name: P9String,
#[deku(endian = "little")]
pub mode: u32,
#[deku(endian = "little")]
pub gid: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tsymlink {
#[deku(endian = "little")]
pub dfid: u32,
pub name: P9String,
pub symtgt: P9String,
#[deku(endian = "little")]
pub gid: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tmknod {
#[deku(endian = "little")]
pub dfid: u32,
pub name: P9String,
#[deku(endian = "little")]
pub mode: u32,
#[deku(endian = "little")]
pub major: u32,
#[deku(endian = "little")]
pub minor: u32,
#[deku(endian = "little")]
pub gid: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tlink {
#[deku(endian = "little")]
pub dfid: u32,
#[deku(endian = "little")]
pub fid: u32,
pub name: P9String,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Trename {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub dfid: u32,
pub name: P9String,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Trenameat {
#[deku(endian = "little")]
pub olddirfid: u32,
pub oldname: P9String,
#[deku(endian = "little")]
pub newdirfid: u32,
pub newname: P9String,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tunlinkat {
#[deku(endian = "little")]
pub dirfid: u32,
pub name: P9String,
#[deku(endian = "little")]
pub flags: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tfsync {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub datasync: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Treadlink {
#[deku(endian = "little")]
pub fid: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tstatfs {
#[deku(endian = "little")]
pub fid: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tflush {
#[deku(endian = "little")]
pub oldtag: u16,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rflush;
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Txattrwalk {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub newfid: u32,
pub name: P9String,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tlock {
#[deku(endian = "little")]
pub fid: u32,
pub lock_type: LockType,
#[deku(endian = "little")]
pub flags: u32,
#[deku(endian = "little")]
pub start: u64,
#[deku(endian = "little")]
pub length: u64,
#[deku(endian = "little")]
pub proc_id: u32,
pub client_id: P9String,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tgetlock {
#[deku(endian = "little")]
pub fid: u32,
pub lock_type: LockType,
#[deku(endian = "little")]
pub start: u64,
#[deku(endian = "little")]
pub length: u64,
#[deku(endian = "little")]
pub proc_id: u32,
pub client_id: P9String,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rxattrwalk {
#[deku(endian = "little")]
pub size: u64,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rlock {
pub status: LockStatus,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rgetlock {
pub lock_type: LockType,
#[deku(endian = "little")]
pub start: u64,
#[deku(endian = "little")]
pub length: u64,
#[deku(endian = "little")]
pub proc_id: u32,
pub client_id: P9String,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rversion {
#[deku(endian = "little")]
pub msize: u32,
pub version: P9String,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rattach {
pub qid: Qid,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Trebind {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub inode_id: u64,
#[deku(endian = "little")]
pub n_uname: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rrebind {
pub qid: Qid,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Twalkgetattr {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub newfid: u32,
#[deku(endian = "little", update = "self.wnames.len()")]
pub nwname: u16,
#[deku(count = "nwname")]
pub wnames: Vec<P9String>,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rwalkgetattr {
#[deku(endian = "little", update = "self.wqids.len()")]
pub nwqid: u16,
#[deku(count = "nwqid")]
pub wqids: Vec<Qid>,
pub stat: Stat,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Treaddirattr {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub offset: u64,
#[deku(endian = "little")]
pub count: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct DirEntryPlus {
pub qid: Qid,
#[deku(endian = "little")]
pub offset: u64,
pub type_: u8,
pub name: P9String,
pub stat: Stat,
}
impl DirEntryPlus {
pub fn wire_size(&self) -> usize {
Qid::WIRE_SIZE + 8 + 1 + self.name.wire_size() + Stat::WIRE_SIZE
}
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rreaddirattr {
#[deku(endian = "little", update = "self.data.len()")]
pub count: u32,
#[deku(ctx = "count")]
pub data: DekuBytes,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tlopenat {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub newfid: u32,
#[deku(endian = "little")]
pub flags: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rlopenat {
pub qid: Qid,
#[deku(endian = "little")]
pub iounit: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tlcreateattr {
#[deku(endian = "little")]
pub dfid: u32,
#[deku(endian = "little")]
pub newfid: u32,
pub name: P9String,
#[deku(endian = "little")]
pub flags: u32,
#[deku(endian = "little")]
pub mode: u32,
#[deku(endian = "little")]
pub gid: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rlcreateattr {
#[deku(endian = "little")]
pub iounit: u32,
pub stat: Stat,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rmkdirattr {
pub stat: Stat,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rsymlinkattr {
pub stat: Stat,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rmknodattr {
pub stat: Stat,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rlinkattr {
pub stat: Stat,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rsetattrattr {
pub stat: Stat,
}
impl Rreaddirattr {
pub fn from_entries(entries: Vec<DirEntryPlus>) -> Result<Self, DekuError> {
use deku::DekuContainerWrite;
let mut data = Vec::with_capacity(entries.iter().map(DirEntryPlus::wire_size).sum());
for entry in entries {
data.extend_from_slice(&entry.to_bytes()?);
}
Ok(Rreaddirattr {
count: data.len() as u32,
data: DekuBytes::from(data),
})
}
pub fn to_entries(&self) -> Result<Vec<DirEntryPlus>, DekuError> {
use deku::DekuContainerRead;
let mut entries = Vec::new();
let mut offset = 0;
while offset < self.data.len() {
let remaining = &self.data.0[offset..];
let (_, entry) = DirEntryPlus::from_bytes((remaining, 0))?;
offset += entry.to_bytes()?.len();
entries.push(entry);
}
Ok(entries)
}
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rwalk {
#[deku(endian = "little", update = "self.wqids.len()")]
pub nwqid: u16,
#[deku(count = "nwqid")]
pub wqids: Vec<Qid>,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rlopen {
pub qid: Qid,
#[deku(endian = "little")]
pub iounit: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rlcreate {
pub qid: Qid,
#[deku(endian = "little")]
pub iounit: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rread {
#[deku(endian = "little", update = "self.data.len()")]
pub count: u32,
#[deku(ctx = "count")]
pub data: DekuBytes,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rwrite {
#[deku(endian = "little")]
pub count: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rreaddir {
#[deku(endian = "little", update = "self.data.len()")]
pub count: u32,
#[deku(ctx = "count")]
pub data: DekuBytes,
}
impl Rreaddir {
pub fn from_entries(entries: Vec<DirEntry>) -> Result<Self, DekuError> {
use deku::DekuContainerWrite;
let mut data = Vec::with_capacity(entries.iter().map(DirEntry::wire_size).sum());
for entry in entries {
let bytes = entry.to_bytes()?;
data.extend_from_slice(&bytes);
}
Ok(Rreaddir {
count: data.len() as u32,
data: DekuBytes::from(data),
})
}
pub fn to_entries(&self) -> Result<Vec<DirEntry>, DekuError> {
use deku::DekuContainerRead;
let mut entries = Vec::new();
let mut offset = 0;
while offset < self.data.len() {
let remaining = &self.data.0[offset..];
let (_, entry) = DirEntry::from_bytes((remaining, 0))?;
let entry_bytes = entry.to_bytes()?;
offset += entry_bytes.len();
entries.push(entry);
}
Ok(entries)
}
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rgetattr {
#[deku(endian = "little")]
pub valid: u64,
pub stat: Stat,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rmkdir {
pub qid: Qid,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rsymlink {
pub qid: Qid,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rmknod {
pub qid: Qid,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rreadlink {
pub target: P9String,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rlerror {
#[deku(endian = "little")]
pub ecode: u32,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rclunk;
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rsetattr;
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rrename;
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rlink;
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rrenameat;
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Runlinkat;
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rfsync;
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tgetlineage;
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rgetlineage {
#[deku(endian = "little")]
pub token: u64,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Tfsyncdur {
#[deku(endian = "little")]
pub fid: u32,
#[deku(endian = "little")]
pub datasync: u32,
#[deku(endian = "little")]
pub token: u64,
}
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct Rstatfs {
#[deku(endian = "little")]
pub r#type: u32, #[deku(endian = "little")]
pub bsize: u32, #[deku(endian = "little")]
pub blocks: u64, #[deku(endian = "little")]
pub bfree: u64, #[deku(endian = "little")]
pub bavail: u64, #[deku(endian = "little")]
pub files: u64, #[deku(endian = "little")]
pub ffree: u64, #[deku(endian = "little")]
pub fsid: u64, #[deku(endian = "little")]
pub namelen: u32, }
pub const T_LCREATE: u8 = 14;
pub const T_SYMLINK: u8 = 16;
pub const T_MKNOD: u8 = 18;
pub const T_RENAME: u8 = 20;
pub const T_LINK: u8 = 70;
pub const T_MKDIR: u8 = 72;
pub const T_RENAMEAT: u8 = 74;
pub const T_UNLINKAT: u8 = 76;
pub const T_LCREATEATTR: u8 = 238;
pub const T_MKDIRATTR: u8 = 240;
pub const T_SYMLINKATTR: u8 = 242;
pub const T_MKNODATTR: u8 = 244;
pub const T_LINKATTR: u8 = 246;
#[derive(Debug, Clone, DekuRead, DekuWrite)]
#[deku(ctx = "_type: u8", id = "_type")]
pub enum Message {
#[deku(id = "100")]
Tversion(Tversion),
#[deku(id = "101")]
Rversion(Rversion),
#[deku(id = "104")]
Tattach(Tattach),
#[deku(id = "105")]
Rattach(Rattach),
#[deku(id = "110")]
Twalk(Twalk),
#[deku(id = "111")]
Rwalk(Rwalk),
#[deku(id = "12")]
Tlopen(Tlopen),
#[deku(id = "13")]
Rlopen(Rlopen),
#[deku(id = "T_LCREATE")]
Tlcreate(Tlcreate),
#[deku(id = "15")]
Rlcreate(Rlcreate),
#[deku(id = "116")]
Tread(Tread),
#[deku(id = "117")]
Rread(Rread),
#[deku(id = "118")]
Twrite(Twrite),
#[deku(id = "119")]
Rwrite(Rwrite),
#[deku(id = "120")]
Tclunk(Tclunk),
#[deku(id = "121")]
Rclunk(Rclunk),
#[deku(id = "40")]
Treaddir(Treaddir),
#[deku(id = "41")]
Rreaddir(Rreaddir),
#[deku(id = "24")]
Tgetattr(Tgetattr),
#[deku(id = "25")]
Rgetattr(Rgetattr),
#[deku(id = "26")]
Tsetattr(Tsetattr),
#[deku(id = "27")]
Rsetattr(Rsetattr),
#[deku(id = "T_MKDIR")]
Tmkdir(Tmkdir),
#[deku(id = "73")]
Rmkdir(Rmkdir),
#[deku(id = "T_SYMLINK")]
Tsymlink(Tsymlink),
#[deku(id = "17")]
Rsymlink(Rsymlink),
#[deku(id = "T_MKNOD")]
Tmknod(Tmknod),
#[deku(id = "19")]
Rmknod(Rmknod),
#[deku(id = "22")]
Treadlink(Treadlink),
#[deku(id = "23")]
Rreadlink(Rreadlink),
#[deku(id = "T_LINK")]
Tlink(Tlink),
#[deku(id = "71")]
Rlink(Rlink),
#[deku(id = "T_RENAME")]
Trename(Trename),
#[deku(id = "21")]
Rrename(Rrename),
#[deku(id = "T_RENAMEAT")]
Trenameat(Trenameat),
#[deku(id = "75")]
Rrenameat(Rrenameat),
#[deku(id = "T_UNLINKAT")]
Tunlinkat(Tunlinkat),
#[deku(id = "77")]
Runlinkat(Runlinkat),
#[deku(id = "50")]
Tfsync(Tfsync),
#[deku(id = "51")]
Rfsync(Rfsync),
#[deku(id = "232")]
Tfsyncdur(Tfsyncdur),
#[deku(id = "233")]
Tgetlineage(Tgetlineage),
#[deku(id = "234")]
Rgetlineage(Rgetlineage),
#[deku(id = "52")]
Tlock(Tlock),
#[deku(id = "53")]
Rlock(Rlock),
#[deku(id = "54")]
Tgetlock(Tgetlock),
#[deku(id = "55")]
Rgetlock(Rgetlock),
#[deku(id = "7")]
Rlerror(Rlerror),
#[deku(id = "108")]
Tflush(Tflush),
#[deku(id = "109")]
Rflush(Rflush),
#[deku(id = "30")]
Txattrwalk(Txattrwalk),
#[deku(id = "31")]
Rxattrwalk(Rxattrwalk),
#[deku(id = "8")]
Tstatfs(Tstatfs),
#[deku(id = "9")]
Rstatfs(Rstatfs),
#[deku(id = "236")]
Tlopenat(Tlopenat),
#[deku(id = "237")]
Rlopenat(Rlopenat),
#[deku(id = "T_LCREATEATTR")]
Tlcreateattr(Tlcreateattr),
#[deku(id = "239")]
Rlcreateattr(Rlcreateattr),
#[deku(id = "T_MKDIRATTR")]
Tmkdirattr(Tmkdir),
#[deku(id = "241")]
Rmkdirattr(Rmkdirattr),
#[deku(id = "T_SYMLINKATTR")]
Tsymlinkattr(Tsymlink),
#[deku(id = "243")]
Rsymlinkattr(Rsymlinkattr),
#[deku(id = "T_MKNODATTR")]
Tmknodattr(Tmknod),
#[deku(id = "245")]
Rmknodattr(Rmknodattr),
#[deku(id = "T_LINKATTR")]
Tlinkattr(Tlink),
#[deku(id = "247")]
Rlinkattr(Rlinkattr),
#[deku(id = "248")]
Tsetattrattr(Tsetattr),
#[deku(id = "249")]
Rsetattrattr(Rsetattrattr),
#[deku(id = "250")]
Trebind(Trebind),
#[deku(id = "251")]
Rrebind(Rrebind),
#[deku(id = "252")]
Twalkgetattr(Twalkgetattr),
#[deku(id = "253")]
Rwalkgetattr(Rwalkgetattr),
#[deku(id = "254")]
Treaddirattr(Treaddirattr),
#[deku(id = "255")]
Rreaddirattr(Rreaddirattr),
}
impl Message {
pub fn is_mutation(&self) -> bool {
self.durability_fid().is_some()
}
pub fn durability_fid(&self) -> Option<u32> {
match self {
Message::Twrite(m) => Some(m.fid),
Message::Tsetattr(m) => Some(m.fid),
Message::Tsetattrattr(m) => Some(m.fid),
Message::Tlcreate(m) => Some(m.fid),
Message::Tlcreateattr(m) => Some(m.dfid),
Message::Tmkdir(m) => Some(m.dfid),
Message::Tmkdirattr(m) => Some(m.dfid),
Message::Tsymlink(m) => Some(m.dfid),
Message::Tsymlinkattr(m) => Some(m.dfid),
Message::Tmknod(m) => Some(m.dfid),
Message::Tmknodattr(m) => Some(m.dfid),
Message::Tlink(m) => Some(m.dfid),
Message::Tlinkattr(m) => Some(m.dfid),
Message::Trename(m) => Some(m.dfid),
Message::Trenameat(m) => Some(m.newdirfid),
Message::Tunlinkat(m) => Some(m.dirfid),
_ => None,
}
}
pub fn durability_fids(&self) -> impl Iterator<Item = u32> {
let extra = match self {
Message::Trenameat(m) => Some(m.olddirfid),
_ => None,
};
self.durability_fid().into_iter().chain(extra)
}
}
pub const P9_TYPE_OFFSET: usize = 4;
pub const P9_FRAME_HEADER_LEN: usize = 7;
pub const P9_OP_ID_LEN: usize = 16;
#[derive(Debug, Clone, DekuRead, DekuWrite)]
pub struct P9Message {
#[deku(endian = "little")]
pub size: u32,
pub type_: u8,
#[deku(endian = "little")]
pub tag: u16,
#[deku(skip, default = "[0u8; 16]")]
pub op_id: [u8; 16],
#[deku(ctx = "*type_")]
pub body: Message,
}
impl P9Message {
pub fn to_bytes(&self) -> Result<Vec<u8>, DekuError> {
self.to_bytes_ctx(false)
}
pub fn to_bytes_ctx(&self, op_id_enabled: bool) -> Result<Vec<u8>, DekuError> {
let mut bytes = DekuContainerWrite::to_bytes(self)?;
if op_id_enabled && Self::carries_op_id(self.type_) {
bytes.splice(
P9_FRAME_HEADER_LEN..P9_FRAME_HEADER_LEN,
self.op_id.iter().copied(),
);
}
let size = bytes.len() as u32;
bytes[0..4].copy_from_slice(&size.to_le_bytes());
Ok(bytes)
}
pub fn from_bytes_ctx(input: &[u8], op_id_enabled: bool) -> Result<P9Message, DekuError> {
if op_id_enabled
&& input.len() > P9_FRAME_HEADER_LEN
&& Self::carries_op_id(input[P9_TYPE_OFFSET])
{
if input.len() < P9_FRAME_HEADER_LEN + P9_OP_ID_LEN {
return Err(DekuError::Parse(
"frame too short to contain an op-id".into(),
));
}
let mut op_id = [0u8; 16];
op_id.copy_from_slice(&input[P9_FRAME_HEADER_LEN..P9_FRAME_HEADER_LEN + P9_OP_ID_LEN]);
let mut standard = Vec::with_capacity(input.len() - P9_OP_ID_LEN);
standard.extend_from_slice(&input[..P9_FRAME_HEADER_LEN]);
standard.extend_from_slice(&input[P9_FRAME_HEADER_LEN + P9_OP_ID_LEN..]);
let (_, mut msg) = P9Message::from_bytes((&standard, 0))?;
msg.op_id = op_id;
Ok(msg)
} else {
let (_, msg) = P9Message::from_bytes((input, 0))?;
Ok(msg)
}
}
fn carries_op_id(type_: u8) -> bool {
matches!(
type_,
T_LCREATE | T_SYMLINK | T_MKNOD | T_RENAME | T_LINK | T_MKDIR | T_RENAMEAT | T_UNLINKAT
| T_LCREATEATTR | T_MKDIRATTR | T_SYMLINKATTR | T_MKNODATTR | T_LINKATTR
)
}
pub fn new(tag: u16, body: Message) -> Self {
let type_ = match &body {
Message::Tversion(_) => 100,
Message::Rversion(_) => 101,
Message::Tattach(_) => 104,
Message::Rattach(_) => 105,
Message::Twalk(_) => 110,
Message::Rwalk(_) => 111,
Message::Tlopen(_) => 12,
Message::Rlopen(_) => 13,
Message::Tlcreate(_) => T_LCREATE,
Message::Rlcreate(_) => 15,
Message::Tread(_) => 116,
Message::Rread(_) => 117,
Message::Twrite(_) => 118,
Message::Rwrite(_) => 119,
Message::Tclunk(_) => 120,
Message::Rclunk(_) => 121,
Message::Treaddir(_) => 40,
Message::Rreaddir(_) => 41,
Message::Tgetattr(_) => 24,
Message::Rgetattr(_) => 25,
Message::Tsetattr(_) => 26,
Message::Rsetattr(_) => 27,
Message::Tmkdir(_) => T_MKDIR,
Message::Rmkdir(_) => 73,
Message::Tsymlink(_) => T_SYMLINK,
Message::Rsymlink(_) => 17,
Message::Tmknod(_) => T_MKNOD,
Message::Rmknod(_) => 19,
Message::Treadlink(_) => 22,
Message::Rreadlink(_) => 23,
Message::Tlink(_) => T_LINK,
Message::Rlink(_) => 71,
Message::Trename(_) => T_RENAME,
Message::Rrename(_) => 21,
Message::Trenameat(_) => T_RENAMEAT,
Message::Rrenameat(_) => 75,
Message::Tunlinkat(_) => T_UNLINKAT,
Message::Runlinkat(_) => 77,
Message::Tfsync(_) => 50,
Message::Rfsync(_) => 51,
Message::Tfsyncdur(_) => 232,
Message::Tgetlineage(_) => 233,
Message::Rgetlineage(_) => 234,
Message::Tlock(_) => 52,
Message::Rlock(_) => 53,
Message::Tgetlock(_) => 54,
Message::Rgetlock(_) => 55,
Message::Rlerror(_) => 7,
Message::Tflush(_) => 108,
Message::Rflush(_) => 109,
Message::Txattrwalk(_) => 30,
Message::Rxattrwalk(_) => 31,
Message::Tstatfs(_) => 8,
Message::Rstatfs(_) => 9,
Message::Tlopenat(_) => 236,
Message::Rlopenat(_) => 237,
Message::Tlcreateattr(_) => T_LCREATEATTR,
Message::Rlcreateattr(_) => 239,
Message::Tmkdirattr(_) => T_MKDIRATTR,
Message::Rmkdirattr(_) => 241,
Message::Tsymlinkattr(_) => T_SYMLINKATTR,
Message::Rsymlinkattr(_) => 243,
Message::Tmknodattr(_) => T_MKNODATTR,
Message::Rmknodattr(_) => 245,
Message::Tlinkattr(_) => T_LINKATTR,
Message::Rlinkattr(_) => 247,
Message::Tsetattrattr(_) => 248,
Message::Rsetattrattr(_) => 249,
Message::Trebind(_) => 250,
Message::Rrebind(_) => 251,
Message::Twalkgetattr(_) => 252,
Message::Rwalkgetattr(_) => 253,
Message::Treaddirattr(_) => 254,
Message::Rreaddirattr(_) => 255,
};
Self {
size: 0, type_,
tag,
op_id: [0u8; 16],
body,
}
}
pub fn new_with_op_id(tag: u16, op_id: [u8; 16], body: Message) -> Self {
let mut msg = Self::new(tag, body);
msg.op_id = op_id;
msg
}
}
#[cfg(test)]
mod tests {
use super::*;
use deku::DekuContainerWrite;
fn tmkdir() -> Message {
Message::Tmkdir(Tmkdir {
dfid: 1,
name: P9String::new(b"d".to_vec()),
mode: 0o755,
gid: 0,
})
}
#[test]
fn durability_fids_yields_both_directories_for_a_renameat() {
let m = Message::Trenameat(Trenameat {
olddirfid: 11,
oldname: P9String::new(b"a".to_vec()),
newdirfid: 22,
newname: P9String::new(b"b".to_vec()),
});
assert_eq!(
m.durability_fid(),
Some(22),
"the primary fid is the dest dir"
);
let mut fids: Vec<u32> = m.durability_fids().collect();
fids.sort_unstable();
assert_eq!(fids, vec![11, 22], "both source and dest dirs are covered");
}
#[test]
fn durability_fids_is_the_single_fid_for_non_rename_ops() {
let fids: Vec<u32> = tmkdir().durability_fids().collect();
assert_eq!(
fids,
vec![1],
"a single-directory op yields just its own fid"
);
}
#[test]
fn op_id_round_trips_on_a_request_under_zerofs3() {
let op_id = [7u8; 16];
let msg = P9Message::new_with_op_id(5, op_id, tmkdir());
let bytes = msg.to_bytes_ctx(true).unwrap();
let decoded = P9Message::from_bytes_ctx(&bytes, true).unwrap();
assert_eq!(
decoded.op_id, op_id,
"the op-id must round-trip under .zerofs3"
);
assert_eq!(decoded.tag, 5);
assert!(matches!(decoded.body, Message::Tmkdir(_)));
}
#[test]
fn op_id_is_absent_in_standard_framing() {
let op_id = [7u8; 16];
let msg = P9Message::new_with_op_id(5, op_id, tmkdir());
let with = msg.to_bytes_ctx(true).unwrap();
let without = msg.to_bytes_ctx(false).unwrap();
assert_eq!(
with.len(),
without.len() + 16,
"the op-id adds exactly 16 bytes on the wire, and nothing without .zerofs3"
);
let decoded = P9Message::from_bytes_ctx(&without, false).unwrap();
assert_eq!(
decoded.op_id, [0u8; 16],
"standard framing carries no op-id"
);
let (_, plain) = P9Message::from_bytes((&without, 0)).unwrap();
assert_eq!(plain.tag, 5);
}
#[test]
fn idempotent_requests_carry_no_op_id() {
let msg = P9Message::new_with_op_id(5, [7u8; 16], Message::Tclunk(Tclunk { fid: 9 }));
let with = msg.to_bytes_ctx(true).unwrap();
let without = msg.to_bytes_ctx(false).unwrap();
assert_eq!(with, without, "an uncovered op must not carry an op-id");
}
fn qid() -> Qid {
Qid {
type_: 0x80,
version: 7,
path: 42,
}
}
fn stat() -> Stat {
Stat {
qid: qid(),
mode: 0o755,
uid: 1000,
gid: 1000,
nlink: 2,
rdev: 0,
size: 4096,
blksize: 32768,
blocks: 8,
atime_sec: 1,
atime_nsec: 2,
mtime_sec: 3,
mtime_nsec: 4,
ctime_sec: 5,
ctime_nsec: 6,
btime_sec: 7,
btime_nsec: 8,
r#gen: 9,
data_version: 10,
}
}
#[test]
fn wire_size_matches_serialization() {
for name in [&b""[..], b"a", "h\u{e9}llo-\u{4e16}\u{754c}.txt".as_bytes()] {
let entry = DirEntry {
qid: qid(),
offset: 99,
type_: 4,
name: P9String::new(name.to_vec()),
};
assert_eq!(entry.wire_size(), entry.to_bytes().unwrap().len());
let plus = DirEntryPlus {
qid: qid(),
offset: 99,
type_: 4,
name: P9String::new(name.to_vec()),
stat: stat(),
};
assert_eq!(plus.wire_size(), plus.to_bytes().unwrap().len());
}
}
}