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)]
65pub struct Block {
66 pub payload_offset: u64,
68 pub compressed_size: u64,
70 pub original_size: u64,
72 pub content_hash: [u8; 32],
80}
81
82pub const BLOCK_SIZE: usize = 56;
84
85pub const BLOCK_SIZE_V1: usize = 24;
87
88impl Block {
89 pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> {
90 w.write_all(&self.payload_offset.to_le_bytes())?;
91 w.write_all(&self.compressed_size.to_le_bytes())?;
92 w.write_all(&self.original_size.to_le_bytes())?;
93 w.write_all(&self.content_hash)?;
94 Ok(())
95 }
96
97 pub fn read_from<R: Read>(r: &mut R, version: u16) -> io::Result<Self> {
99 let mut buf = [0u8; BLOCK_SIZE];
100 let width = if version >= 2 {
101 BLOCK_SIZE
102 } else {
103 BLOCK_SIZE_V1
104 };
105 r.read_exact(&mut buf[..width])?;
106
107 Ok(Block {
108 payload_offset: u64::from_le_bytes(buf[0..8].try_into().unwrap()),
109 compressed_size: u64::from_le_bytes(buf[8..16].try_into().unwrap()),
110 original_size: u64::from_le_bytes(buf[16..24].try_into().unwrap()),
111 content_hash: buf[24..56].try_into().unwrap(),
113 })
114 }
115
116 pub fn has_content_hash(&self) -> bool {
118 self.content_hash != [0u8; 32]
119 }
120}
121
122#[derive(Debug, Clone)]
123pub struct EntryPoint {
124 pub name: u32,
126 pub target_entry: u32,
128 pub args: u32,
130 pub working_dir: WorkingDir,
132 pub flags: EntryPointFlags,
134}
135
136pub const ENTRYPOINT_SIZE: usize = 14;
138
139impl EntryPoint {
140 pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> {
141 w.write_all(&self.name.to_le_bytes())?;
142 w.write_all(&self.target_entry.to_le_bytes())?;
143 w.write_all(&self.args.to_le_bytes())?;
144 w.write_all(&[self.working_dir as u8])?;
145 w.write_all(&[self.flags.bits()])?;
146 Ok(())
147 }
148
149 pub fn read_from<R: Read>(r: &mut R) -> io::Result<Self> {
150 let mut buf = [0u8; ENTRYPOINT_SIZE];
151 r.read_exact(&mut buf)?;
152
153 Ok(EntryPoint {
154 name: u32::from_le_bytes(buf[0..4].try_into().unwrap()),
155 target_entry: u32::from_le_bytes(buf[4..8].try_into().unwrap()),
156 args: u32::from_le_bytes(buf[8..12].try_into().unwrap()),
157 working_dir: WorkingDir::try_from(buf[12])?,
158 flags: EntryPointFlags::from_bits_retain(buf[13]),
159 })
160 }
161
162 pub fn is_memfd_eligible(&self) -> bool {
163 self.flags.contains(EntryPointFlags::MEMFD_ELIGIBLE)
164 }
165}
166
167#[derive(Debug, Clone)]
168pub struct Entry {
169 pub kind: EntryKind,
171 pub parent: u32,
173 pub name: u32,
175 pub mode: u32,
177 pub mtime_secs: u64,
179 pub mtime_nsec: u32,
181 pub content_hash: [u8; 32],
183 pub blocks: Vec<Block>,
186 pub symlink_target: u32,
188}
189
190pub const ENTRY_HEADER_SIZE: usize = 65;
194
195impl Entry {
196 pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> {
197 w.write_all(&[self.kind as u8])?;
198 w.write_all(&self.parent.to_le_bytes())?;
199 w.write_all(&self.name.to_le_bytes())?;
200 w.write_all(&self.mode.to_le_bytes())?;
201 w.write_all(&self.mtime_secs.to_le_bytes())?;
202 w.write_all(&self.mtime_nsec.to_le_bytes())?;
203 w.write_all(&self.content_hash)?;
204 w.write_all(&(self.blocks.len() as u32).to_le_bytes())?;
207 w.write_all(&self.symlink_target.to_le_bytes())?;
208 for block in &self.blocks {
209 block.write_to(w)?;
210 }
211 Ok(())
212 }
213
214 pub fn read_from<R: Read>(r: &mut R, version: u16) -> io::Result<Self> {
216 let mut buf = [0u8; ENTRY_HEADER_SIZE];
217 r.read_exact(&mut buf)?;
218
219 let kind = EntryKind::try_from(buf[0])?;
220 let parent = u32::from_le_bytes(buf[1..5].try_into().unwrap());
221 let name = u32::from_le_bytes(buf[5..9].try_into().unwrap());
222 let mode = u32::from_le_bytes(buf[9..13].try_into().unwrap());
223 let mtime_secs = u64::from_le_bytes(buf[13..21].try_into().unwrap());
224 let mtime_nsec = u32::from_le_bytes(buf[21..25].try_into().unwrap());
225 let mut content_hash = [0u8; 32];
226 content_hash.copy_from_slice(&buf[25..57]);
227 let num_blocks = u32::from_le_bytes(buf[57..61].try_into().unwrap());
228 let symlink_target = u32::from_le_bytes(buf[61..65].try_into().unwrap());
229
230 let cap = (num_blocks as usize).min(4096);
235 let mut blocks = Vec::with_capacity(cap);
236 for _ in 0..num_blocks {
237 blocks.push(Block::read_from(r, version)?);
238 }
239
240 Ok(Entry {
241 kind,
242 parent,
243 name,
244 mode,
245 mtime_secs,
246 mtime_nsec,
247 content_hash,
248 blocks,
249 symlink_target,
250 })
251 }
252}