1use std::io::{self, Read, Write};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4#[repr(u8)]
5pub enum EntryKind {
6 Dir = 0,
7 File = 1,
8 Symlink = 2,
9}
10
11impl TryFrom<u8> for EntryKind {
12 type Error = io::Error;
13
14 fn try_from(v: u8) -> Result<Self, Self::Error> {
15 match v {
16 0 => Ok(EntryKind::Dir),
17 1 => Ok(EntryKind::File),
18 2 => Ok(EntryKind::Symlink),
19 _ => Err(io::Error::new(
20 io::ErrorKind::InvalidData,
21 format!("invalid entry kind: {v}"),
22 )),
23 }
24 }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28#[repr(u8)]
29pub enum WorkingDir {
30 Inherit = 0,
31 PackageRoot = 1,
32 EntrypointParent = 2,
33}
34
35impl TryFrom<u8> for WorkingDir {
36 type Error = io::Error;
37
38 fn try_from(v: u8) -> Result<Self, Self::Error> {
39 match v {
40 0 => Ok(WorkingDir::Inherit),
41 1 => Ok(WorkingDir::PackageRoot),
42 2 => Ok(WorkingDir::EntrypointParent),
43 _ => Err(io::Error::new(
44 io::ErrorKind::InvalidData,
45 format!("invalid working_dir: {v}"),
46 )),
47 }
48 }
49}
50
51bitflags! {
52 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
53 pub struct EntryPointFlags: u8 {
54 const MEMFD_ELIGIBLE = 1 << 0;
55 }
56}
57
58#[derive(Debug, Clone)]
59pub struct Block {
60 pub payload_offset: u64,
62 pub compressed_size: u64,
64 pub original_size: u64,
66 pub content_hash: [u8; 32],
74}
75
76pub const BLOCK_SIZE: usize = 56;
78
79pub const BLOCK_SIZE_V1: usize = 24;
81
82impl Block {
83 pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> {
84 w.write_all(&self.payload_offset.to_le_bytes())?;
85 w.write_all(&self.compressed_size.to_le_bytes())?;
86 w.write_all(&self.original_size.to_le_bytes())?;
87 w.write_all(&self.content_hash)?;
88 Ok(())
89 }
90
91 pub fn read_from<R: Read>(r: &mut R, version: u16) -> io::Result<Self> {
93 let mut buf = [0u8; BLOCK_SIZE];
94 let width = if version >= 2 {
95 BLOCK_SIZE
96 } else {
97 BLOCK_SIZE_V1
98 };
99 r.read_exact(&mut buf[..width])?;
100
101 Ok(Block {
102 payload_offset: u64::from_le_bytes(buf[0..8].try_into().unwrap()),
103 compressed_size: u64::from_le_bytes(buf[8..16].try_into().unwrap()),
104 original_size: u64::from_le_bytes(buf[16..24].try_into().unwrap()),
105 content_hash: buf[24..56].try_into().unwrap(),
107 })
108 }
109
110 pub fn has_content_hash(&self) -> bool {
112 self.content_hash != [0u8; 32]
113 }
114}
115
116#[derive(Debug, Clone)]
117pub struct EntryPoint {
118 pub name: u32,
120 pub target_entry: u32,
122 pub args: u32,
124 pub working_dir: WorkingDir,
126 pub flags: EntryPointFlags,
128}
129
130pub const ENTRYPOINT_SIZE: usize = 14;
132
133impl EntryPoint {
134 pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> {
135 w.write_all(&self.name.to_le_bytes())?;
136 w.write_all(&self.target_entry.to_le_bytes())?;
137 w.write_all(&self.args.to_le_bytes())?;
138 w.write_all(&[self.working_dir as u8])?;
139 w.write_all(&[self.flags.bits()])?;
140 Ok(())
141 }
142
143 pub fn read_from<R: Read>(r: &mut R) -> io::Result<Self> {
144 let mut buf = [0u8; ENTRYPOINT_SIZE];
145 r.read_exact(&mut buf)?;
146
147 Ok(EntryPoint {
148 name: u32::from_le_bytes(buf[0..4].try_into().unwrap()),
149 target_entry: u32::from_le_bytes(buf[4..8].try_into().unwrap()),
150 args: u32::from_le_bytes(buf[8..12].try_into().unwrap()),
151 working_dir: WorkingDir::try_from(buf[12])?,
152 flags: EntryPointFlags::from_bits_retain(buf[13]),
153 })
154 }
155
156 pub fn is_memfd_eligible(&self) -> bool {
157 self.flags.contains(EntryPointFlags::MEMFD_ELIGIBLE)
158 }
159}
160
161#[derive(Debug, Clone)]
162pub struct Entry {
163 pub kind: EntryKind,
165 pub parent: u32,
167 pub name: u32,
169 pub mode: u32,
171 pub mtime_secs: u64,
173 pub mtime_nsec: u32,
175 pub content_hash: [u8; 32],
177 pub blocks: Vec<Block>,
180 pub symlink_target: u32,
182}
183
184pub const ENTRY_HEADER_SIZE: usize = 65;
188
189impl Entry {
190 pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> {
191 w.write_all(&[self.kind as u8])?;
192 w.write_all(&self.parent.to_le_bytes())?;
193 w.write_all(&self.name.to_le_bytes())?;
194 w.write_all(&self.mode.to_le_bytes())?;
195 w.write_all(&self.mtime_secs.to_le_bytes())?;
196 w.write_all(&self.mtime_nsec.to_le_bytes())?;
197 w.write_all(&self.content_hash)?;
198 w.write_all(&(self.blocks.len() as u32).to_le_bytes())?;
201 w.write_all(&self.symlink_target.to_le_bytes())?;
202 for block in &self.blocks {
203 block.write_to(w)?;
204 }
205 Ok(())
206 }
207
208 pub fn read_from<R: Read>(r: &mut R, version: u16) -> io::Result<Self> {
210 let mut buf = [0u8; ENTRY_HEADER_SIZE];
211 r.read_exact(&mut buf)?;
212
213 let kind = EntryKind::try_from(buf[0])?;
214 let parent = u32::from_le_bytes(buf[1..5].try_into().unwrap());
215 let name = u32::from_le_bytes(buf[5..9].try_into().unwrap());
216 let mode = u32::from_le_bytes(buf[9..13].try_into().unwrap());
217 let mtime_secs = u64::from_le_bytes(buf[13..21].try_into().unwrap());
218 let mtime_nsec = u32::from_le_bytes(buf[21..25].try_into().unwrap());
219 let mut content_hash = [0u8; 32];
220 content_hash.copy_from_slice(&buf[25..57]);
221 let num_blocks = u32::from_le_bytes(buf[57..61].try_into().unwrap());
222 let symlink_target = u32::from_le_bytes(buf[61..65].try_into().unwrap());
223
224 let cap = (num_blocks as usize).min(4096);
229 let mut blocks = Vec::with_capacity(cap);
230 for _ in 0..num_blocks {
231 blocks.push(Block::read_from(r, version)?);
232 }
233
234 Ok(Entry {
235 kind,
236 parent,
237 name,
238 mode,
239 mtime_secs,
240 mtime_nsec,
241 content_hash,
242 blocks,
243 symlink_target,
244 })
245 }
246}