1use erofs::format;
10use zerocopy::IntoBytes;
11use zerocopy::byteorder::little_endian::{U16 as LEU16, U32 as LEU32, U64 as LEU64};
12
13#[derive(Debug, Clone)]
15pub enum SerializerNode {
16 Directory { name: String, entries: Vec<SerializerNode> },
18 File { name: String, data: Vec<u8> },
20 Symlink { name: String, target: Vec<u8> },
22}
23
24impl SerializerNode {
25 pub fn name(&self) -> &str {
26 match self {
27 Self::Directory { name, .. } => name,
28 Self::File { name, .. } => name,
29 Self::Symlink { name, .. } => name,
30 }
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35enum NodeType {
36 Dir,
37 File,
38 Symlink,
39}
40
41struct FlatNode {
42 nid: u64,
43 node_type: NodeType,
44 contents: FlatNodeContents,
45 data_block: u32,
46 size: u64,
47}
48
49enum FlatNodeContents {
50 Directory {
51 entries: Vec<(String, u64, NodeType)>,
53 },
54 File {
55 data: Vec<u8>,
56 },
57 Symlink {
58 target: Vec<u8>,
59 },
60}
61
62fn add_node(node: &SerializerNode, nodes: &mut Vec<FlatNode>, parent_nid: u64) -> u64 {
63 let nid = nodes.len() as u64;
64 match node {
65 SerializerNode::Directory { name: _, entries } => {
66 nodes.push(FlatNode {
68 nid,
69 node_type: NodeType::Dir,
70 contents: FlatNodeContents::Directory { entries: Vec::new() },
71 data_block: 0,
72 size: 0,
73 });
74
75 let mut child_entries = Vec::new();
76 child_entries.push((".".to_string(), nid, NodeType::Dir));
77 child_entries.push(("..".to_string(), parent_nid, NodeType::Dir));
78
79 for child in entries {
80 let child_name = child.name().to_string();
81 let child_nid = add_node(child, nodes, nid);
82 let child_type = nodes[child_nid as usize].node_type;
83 child_entries.push((child_name, child_nid, child_type));
84 }
85
86 child_entries.sort_by(|a, b| a.0.cmp(&b.0));
87 nodes[nid as usize].contents = FlatNodeContents::Directory { entries: child_entries };
88 }
89 SerializerNode::File { name: _, data } => {
90 nodes.push(FlatNode {
91 nid,
92 node_type: NodeType::File,
93 contents: FlatNodeContents::File { data: data.clone() },
94 data_block: 0,
95 size: 0,
96 });
97 }
98 SerializerNode::Symlink { name: _, target } => {
99 nodes.push(FlatNode {
100 nid,
101 node_type: NodeType::Symlink,
102 contents: FlatNodeContents::Symlink { target: target.clone() },
103 data_block: 0,
104 size: 0,
105 });
106 }
107 }
108 nid
109}
110
111pub fn serialize(root_entries: &[SerializerNode]) -> Vec<u8> {
115 let mut nodes = Vec::<FlatNode>::new();
116
117 nodes.push(FlatNode {
119 nid: 0,
120 node_type: NodeType::Dir,
121 contents: FlatNodeContents::Directory { entries: Vec::new() },
122 data_block: 0,
123 size: 0,
124 });
125
126 let mut root_child_entries = Vec::new();
127 root_child_entries.push((".".to_string(), 0, NodeType::Dir));
128 root_child_entries.push(("..".to_string(), 0, NodeType::Dir));
129
130 for child in root_entries {
131 let child_name = child.name().to_string();
132 let child_nid = add_node(child, &mut nodes, 0);
133 let child_type = nodes[child_nid as usize].node_type;
134 root_child_entries.push((child_name, child_nid, child_type));
135 }
136
137 root_child_entries.sort_by(|a, b| a.0.cmp(&b.0));
138 nodes[0].contents = FlatNodeContents::Directory { entries: root_child_entries };
139
140 let inode_blocks = (nodes.len() * 32).div_ceil(4096);
142 let mut next_free_block = 1 + inode_blocks as u32;
143
144 for i in 0..nodes.len() {
145 match &nodes[i].contents {
146 FlatNodeContents::Directory { .. } => {
147 nodes[i].data_block = next_free_block;
148 nodes[i].size = 4096;
149 next_free_block += 1;
150 }
151 FlatNodeContents::File { data } => {
152 let len = data.len() as u64;
153 nodes[i].size = len;
154 if len > 0 {
155 nodes[i].data_block = next_free_block;
156 let blocks_needed = len.div_ceil(4096);
157 next_free_block += blocks_needed as u32;
158 } else {
159 nodes[i].data_block = 0;
160 }
161 }
162 FlatNodeContents::Symlink { target } => {
163 let len = target.len() as u64;
164 nodes[i].size = len;
165 if len > 0 {
166 nodes[i].data_block = next_free_block;
167 let blocks_needed = len.div_ceil(4096);
168 next_free_block += blocks_needed as u32;
169 } else {
170 nodes[i].data_block = 0;
171 }
172 }
173 }
174 }
175
176 let total_blocks = next_free_block;
177 let mut image = vec![0u8; total_blocks as usize * 4096];
178
179 let sb = format::SuperBlock {
181 magic: LEU32::new(format::EROFS_MAGIC),
182 checksum: LEU32::new(0),
183 feature_compat: LEU32::new(0),
184 block_size_bits: 12, sb_ext_slots: 0,
186 root_nid: LEU16::new(0),
187 inode_count: LEU64::new(nodes.len() as u64),
188 epoch: LEU64::new(1700000000),
189 fixed_nsec: LEU32::new(0),
190 blocks: LEU32::new(total_blocks),
191 meta_block_addr: LEU32::new(1),
192 xattr_block_addr: LEU32::new(0),
193 uuid: [0; 16],
194 volume_name: [0; 16],
195 feature_incompat: LEU32::new(0),
196 available_compr_algs: LEU16::new(0),
197 extra_devices: LEU32::new(0),
198 dirblkbits: 0,
199 reserved: [0; 37],
200 };
201 image[1024..1024 + 128].copy_from_slice(sb.as_bytes());
202
203 for i in 0..nodes.len() {
205 let mode = match nodes[i].node_type {
206 NodeType::Dir => 0o040000 | 0o755, NodeType::File => 0o100000 | 0o644, NodeType::Symlink => 0o120000 | 0o777, };
210
211 let link_count = if nodes[i].node_type == NodeType::Dir { 2 } else { 1 };
212 let i_u = nodes[i].data_block.to_le_bytes();
213
214 let inode = format::InodeCompact {
215 format: LEU16::new(0), xattr_icount: LEU16::new(0),
217 mode: LEU16::new(mode),
218 link_count: LEU16::new(link_count),
219 size: LEU32::new(nodes[i].size as u32),
220 reserved_1: [0; 4],
221 i_u,
222 ino: LEU32::new(nodes[i].nid as u32),
223 uid: LEU16::new(100),
224 gid: LEU16::new(200),
225 reserved_2: [0; 4],
226 };
227 let offset = 4096 + i * 32;
228 image[offset..offset + 32].copy_from_slice(inode.as_bytes());
229 }
230
231 for i in 0..nodes.len() {
233 match &nodes[i].contents {
234 FlatNodeContents::Directory { entries } => {
235 let mut dir_block = vec![0u8; 4096];
236 let k = entries.len();
237 let mut current_nameoff = (k * 12) as u16;
238
239 let mut dirents = Vec::new();
240 let mut name_bytes = Vec::new();
241
242 for (name, nid, node_type) in entries {
243 let file_type = match node_type {
244 NodeType::Dir => 2,
245 NodeType::File => 1,
246 NodeType::Symlink => 7,
247 };
248 dirents.push(format::Dirent {
249 nid: LEU64::new(*nid),
250 nameoff: LEU16::new(current_nameoff),
251 file_type,
252 reserved: 0,
253 });
254
255 name_bytes.extend_from_slice(name.as_bytes());
256 current_nameoff += name.as_bytes().len() as u16;
257 }
258
259 let dirents_bytes = dirents.as_slice().as_bytes();
260 dir_block[..dirents_bytes.len()].copy_from_slice(dirents_bytes);
261
262 let names_start = dirents_bytes.len();
263 dir_block[names_start..names_start + name_bytes.len()].copy_from_slice(&name_bytes);
264
265 let offset = nodes[i].data_block as usize * 4096;
266 image[offset..offset + 4096].copy_from_slice(&dir_block);
267 }
268 FlatNodeContents::File { data } => {
269 if !data.is_empty() {
270 let offset = nodes[i].data_block as usize * 4096;
271 image[offset..offset + data.len()].copy_from_slice(data);
272 }
273 }
274 FlatNodeContents::Symlink { target } => {
275 if !target.is_empty() {
276 let offset = nodes[i].data_block as usize * 4096;
277 image[offset..offset + target.len()].copy_from_slice(target);
278 }
279 }
280 }
281 }
282
283 image
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use erofs::readers::VecReader;
290 use erofs::{ErofsFilesystem, Node};
291 use std::sync::Arc;
292
293 fn assert_dir_recursive(
294 fs: &ErofsFilesystem,
295 expected_entries: &[SerializerNode],
296 actual_dir: &erofs::DirectoryNode,
297 ) {
298 let mut actual_entries_buf = vec![erofs::DirectoryEntry::default(); 100];
299 let filled = fs.read_directory(actual_dir, 0, &mut actual_entries_buf).unwrap();
300 let mut actual_entries = actual_entries_buf[..filled].to_vec();
301
302 actual_entries.retain(|e| e.name != "." && e.name != "..");
304
305 assert_eq!(actual_entries.len(), expected_entries.len(), "Directory entry count mismatch");
306
307 let mut sorted_expected: Vec<&SerializerNode> = expected_entries.iter().collect();
308 sorted_expected.sort_by(|a, b| a.name().cmp(b.name()));
309
310 for (i, expected_node) in sorted_expected.iter().enumerate() {
311 let actual_entry = &actual_entries[i];
312 assert_eq!(actual_entry.name, expected_node.name());
313
314 let child_node = fs.node(actual_entry.nid).expect("failed to read child node");
315
316 match expected_node {
317 SerializerNode::Directory { entries, .. } => {
318 let actual_child_dir = match child_node {
319 Node::Directory(d) => d,
320 _ => panic!("Expected directory node for {}", expected_node.name()),
321 };
322 assert_dir_recursive(fs, entries, &actual_child_dir);
323 }
324 SerializerNode::File { data, .. } => {
325 let actual_child_file = match child_node {
326 Node::File(f) => f,
327 _ => panic!("Expected file node for {}", expected_node.name()),
328 };
329 assert_eq!(actual_child_file.size(), data.len() as u64);
330 let mut file_buf = vec![0u8; data.len()];
331 fs.read_file_range(&actual_child_file, 0, &mut file_buf).unwrap();
332 assert_eq!(&file_buf, data);
333 }
334 SerializerNode::Symlink { target, .. } => {
335 let actual_child_symlink = match child_node {
336 Node::Symlink(s) => s,
337 _ => panic!("Expected symlink node for {}", expected_node.name()),
338 };
339 assert_eq!(actual_child_symlink.size(), target.len() as u64);
340 let actual_target = fs.read_symlink(&actual_child_symlink).unwrap();
341 assert_eq!(&actual_target, target);
342 }
343 }
344 }
345 }
346
347 #[fuchsia::test]
348 fn test_serialize_and_parse() {
349 let tree = vec![
350 SerializerNode::File { name: "file1".to_string(), data: b"hello world".to_vec() },
351 SerializerNode::Symlink { name: "symlink1".to_string(), target: b"file1".to_vec() },
352 SerializerNode::Directory {
353 name: "dir1".to_string(),
354 entries: vec![
355 SerializerNode::File {
356 name: "file2".to_string(),
357 data: b"another file!".to_vec(),
358 },
359 SerializerNode::Directory {
360 name: "subdir".to_string(),
361 entries: vec![
362 SerializerNode::File {
363 name: "file3".to_string(),
364 data: b"nested file".to_vec(),
365 },
366 SerializerNode::Symlink {
367 name: "nested_symlink".to_string(),
368 target: b"../../file1".to_vec(),
369 },
370 ],
371 },
372 ],
373 },
374 ];
375
376 let image = serialize(&tree);
377 let reader = Arc::new(VecReader::new(image));
378 let fs = ErofsFilesystem::new(reader).expect("Failed to parse serialized EROFS image");
379
380 let root = fs.root_node();
381 assert_eq!(root.ino(), 0);
382
383 assert_dir_recursive(&fs, &tree, &root);
384 }
385}