1use std::collections::{BTreeMap, HashMap};
2use std::ffi::OsString;
3use std::io::{self, BufWriter, Seek, SeekFrom, Write};
4use std::os::unix::ffi::OsStrExt;
5use std::path::{Path, PathBuf};
6
7use crate::crc32c;
8use crate::filetree::{DirectoryNode, FileTree, InodeMetadata, TreeNode, Xattr};
9
10use super::format::{
11 self, EROFS_BLKSIZ, EROFS_BLKSIZ_BITS, EROFS_DIRENT_SIZE, EROFS_FEATURE_COMPAT_SB_CHKSUM,
12 EROFS_INODE_EXTENDED_SIZE, EROFS_INODE_FLAT_INLINE, EROFS_INODE_FLAT_PLAIN, EROFS_ISLOT_SIZE,
13 EROFS_NULL_ADDR, EROFS_SUPER_MAGIC, EROFS_SUPER_OFFSET, EROFS_SUPERBLOCK_SIZE,
14 EROFS_XATTR_IBODY_HEADER_SIZE, dirent_file_type, erofs_xattr_align, mode_type_bits,
15 new_encode_dev, xattr_prefix_index,
16};
17
18static ZEROS: [u8; 4096] = [0u8; 4096];
24
25#[derive(Debug)]
30pub enum ErofsError {
31 Io(io::Error),
32 NidOverflow,
33 UnsupportedXattrPrefix,
34}
35
36#[derive(Debug, Clone)]
41pub struct ErofsDataMap {
42 pub file_blocks: HashMap<PathBuf, (u32, u64)>,
44 pub total_blocks: u32,
46}
47
48#[allow(dead_code)]
49struct InodePlan {
50 nid: u32,
51 data_layout: u8,
52 data_block_start: u32,
53 data_block_count: u32,
54 inline_tail_size: u32,
55 xattr_ibody_size: u32,
56 total_inode_size: u32,
57 slots: u32,
58 dir_data: Option<Vec<u8>>,
59 parent_nid: u32,
60}
61
62#[allow(dead_code)]
63struct LayoutState {
64 plans: Vec<InodePlan>,
65 current_meta_offset: u64,
66 current_data_block: u32,
67 meta_blkaddr: u32,
68 root_nid: u32,
69 inode_count: u64,
70}
71
72impl LayoutState {
77 fn new() -> Self {
78 Self {
79 plans: Vec::new(),
80 current_meta_offset: EROFS_BLKSIZ as u64,
81 current_data_block: 0,
82 meta_blkaddr: 1,
83 root_nid: 0,
84 inode_count: 0,
85 }
86 }
87}
88
89impl std::fmt::Display for ErofsError {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 match self {
96 ErofsError::Io(e) => write!(f, "I/O error: {e}"),
97 ErofsError::NidOverflow => write!(f, "root NID exceeds u16::MAX"),
98 ErofsError::UnsupportedXattrPrefix => write!(f, "unsupported xattr prefix"),
99 }
100 }
101}
102
103impl std::error::Error for ErofsError {
104 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
105 match self {
106 ErofsError::Io(e) => Some(e),
107 _ => None,
108 }
109 }
110}
111
112impl From<io::Error> for ErofsError {
113 fn from(e: io::Error) -> Self {
114 ErofsError::Io(e)
115 }
116}
117
118pub fn write_erofs(tree: &FileTree, output: &Path) -> Result<ErofsDataMap, ErofsError> {
123 let mut file = BufWriter::new(std::fs::File::create(output)?);
124 let mut state = LayoutState::new();
125
126 plan_directory(&tree.root, 0, &mut state, true)?;
128 state.root_nid = state.plans[0].nid;
129
130 let mut data_map = HashMap::new();
132 write_data_blocks(&mut file, &state, tree, &mut data_map)?;
133
134 write_metadata(&mut file, &state, tree)?;
136
137 write_superblock(&mut file, &state)?;
139
140 file.flush()?;
143
144 let current_len = file.seek(SeekFrom::End(0))?;
147 let block_aligned = align_to_block(current_len);
148 let sector_aligned = block_aligned.div_ceil(512) * 512;
150 let target_len = sector_aligned.max(block_aligned);
151
152 if target_len > current_len {
153 file.seek(SeekFrom::Start(target_len - 1))?;
154 file.write_all(&[0u8])?;
155 file.flush()?;
156 }
157
158 let final_len = file.seek(SeekFrom::End(0))?;
160 let total_blocks = (final_len / EROFS_BLKSIZ as u64) as u32;
161
162 Ok(ErofsDataMap {
163 file_blocks: data_map,
164 total_blocks,
165 })
166}
167
168fn compute_xattr_ibody_size(xattrs: &[Xattr]) -> Result<u32, ErofsError> {
169 if xattrs.is_empty() {
170 return Ok(0);
171 }
172
173 let mut size = EROFS_XATTR_IBODY_HEADER_SIZE as usize;
174 for xattr in xattrs {
175 let (_, suffix) =
176 xattr_prefix_index(&xattr.name).ok_or(ErofsError::UnsupportedXattrPrefix)?;
177 let entry_size = 4 + suffix.len() + xattr.value.len();
179 size += erofs_xattr_align(entry_size);
180 }
181
182 Ok(size as u32)
183}
184
185fn compute_xattr_icount(xattr_ibody_size: u32) -> u16 {
186 if xattr_ibody_size == 0 {
187 0
188 } else {
189 ((xattr_ibody_size - EROFS_XATTR_IBODY_HEADER_SIZE) / 4 + 1) as u16
190 }
191}
192
193struct DataLayoutDecision {
195 layout: u8,
196 inline_tail_size: u32,
197 block_count: u32,
198 block_start: u32,
199}
200
201fn decide_data_layout(
213 data_size: u64,
214 inode_fixed_size: u32,
215 meta_offset: u64,
216 current_data_block: &mut u32,
217 allow_inline: bool,
218) -> DataLayoutDecision {
219 let blksiz = EROFS_BLKSIZ as u64;
220 let tail_size = data_size % blksiz;
221 let full_blocks = data_size / blksiz;
222
223 if data_size == 0 {
224 DataLayoutDecision {
225 layout: EROFS_INODE_FLAT_PLAIN,
226 inline_tail_size: 0,
227 block_count: 0,
228 block_start: EROFS_NULL_ADDR,
229 }
230 } else if tail_size == 0 {
231 let start = *current_data_block;
232 *current_data_block += full_blocks as u32;
233 DataLayoutDecision {
234 layout: EROFS_INODE_FLAT_PLAIN,
235 inline_tail_size: 0,
236 block_count: full_blocks as u32,
237 block_start: start,
238 }
239 } else if allow_inline {
240 let inode_pos_in_block = meta_offset % blksiz;
241 let remaining_in_block = blksiz - inode_pos_in_block;
242 let needed = inode_fixed_size as u64 + tail_size;
243
244 if needed <= remaining_in_block {
245 let start = if full_blocks > 0 {
246 let s = *current_data_block;
247 *current_data_block += full_blocks as u32;
248 s
249 } else {
250 EROFS_NULL_ADDR
251 };
252 DataLayoutDecision {
253 layout: EROFS_INODE_FLAT_INLINE,
254 inline_tail_size: tail_size as u32,
255 block_count: full_blocks as u32,
256 block_start: start,
257 }
258 } else {
259 let start = *current_data_block;
260 *current_data_block += (full_blocks + 1) as u32;
261 DataLayoutDecision {
262 layout: EROFS_INODE_FLAT_PLAIN,
263 inline_tail_size: 0,
264 block_count: (full_blocks + 1) as u32,
265 block_start: start,
266 }
267 }
268 } else {
269 let start = *current_data_block;
270 *current_data_block += (full_blocks + 1) as u32;
271 DataLayoutDecision {
272 layout: EROFS_INODE_FLAT_PLAIN,
273 inline_tail_size: 0,
274 block_count: (full_blocks + 1) as u32,
275 block_start: start,
276 }
277 }
278}
279
280fn compute_dir_data_size(dir: &DirectoryNode) -> u32 {
281 let entry_count = 2 + dir.entries.len();
283
284 let mut names: Vec<&[u8]> = Vec::with_capacity(entry_count);
286 names.push(b".");
287 names.push(b"..");
288 for name in dir.entries.keys() {
289 names.push(name.as_bytes());
290 }
291 names.sort();
293
294 let blksiz = EROFS_BLKSIZ as usize;
297 let mut total_size = 0usize;
298 let mut idx = 0;
299
300 while idx < names.len() {
301 let mut block_entries = 0;
303 let mut dirent_area = 0usize;
304 let mut name_area = 0usize;
305
306 for name in &names[idx..] {
307 let new_dirent_area = (block_entries + 1) * EROFS_DIRENT_SIZE as usize;
308 let new_name_area = name_area + name.len();
309 if new_dirent_area + new_name_area > blksiz {
310 break;
311 }
312 dirent_area = new_dirent_area;
313 name_area = new_name_area;
314 block_entries += 1;
315 }
316
317 if block_entries == 0 {
318 block_entries = 1;
320 name_area = names[idx].len();
321 dirent_area = EROFS_DIRENT_SIZE as usize;
322 }
323
324 let used = dirent_area + name_area;
325 if idx + block_entries < names.len() {
329 total_size += blksiz;
330 } else {
331 total_size += used;
332 }
333
334 idx += block_entries;
335 }
336
337 total_size as u32
338}
339
340fn serialize_dir_blocks(
353 dir: &DirectoryNode,
354 own_nid: u32,
355 parent_nid: u32,
356 child_nids: &BTreeMap<OsString, u32>,
357) -> Result<Vec<u8>, ErofsError> {
358 struct DirEntryInfo {
359 name: Vec<u8>,
360 nid: u64,
361 file_type: u8,
362 }
363
364 let mut entries: Vec<DirEntryInfo> = Vec::new();
365
366 entries.push(DirEntryInfo {
367 name: b".".to_vec(),
368 nid: own_nid as u64,
369 file_type: format::EROFS_FT_DIR,
370 });
371 entries.push(DirEntryInfo {
372 name: b"..".to_vec(),
373 nid: parent_nid as u64,
374 file_type: format::EROFS_FT_DIR,
375 });
376
377 for (name, child) in &dir.entries {
378 let nid = *child_nids.get(name).expect("child NID not found") as u64;
379 entries.push(DirEntryInfo {
380 name: name.as_bytes().to_vec(),
381 nid,
382 file_type: dirent_file_type(child),
383 });
384 }
385
386 entries.sort_by(|a, b| a.name.cmp(&b.name));
388
389 let blksiz = EROFS_BLKSIZ as usize;
390 let mut result = Vec::new();
391 let mut idx = 0;
392
393 while idx < entries.len() {
394 let mut block_entries = 0usize;
396 let mut name_total = 0usize;
397
398 for entry in &entries[idx..] {
399 let new_dirent_area = (block_entries + 1) * EROFS_DIRENT_SIZE as usize;
400 let new_name_total = name_total + entry.name.len();
401 if new_dirent_area + new_name_total > blksiz {
402 break;
403 }
404 name_total += entry.name.len();
405 block_entries += 1;
406 }
407
408 if block_entries == 0 {
409 block_entries = 1;
410 name_total = entries[idx].name.len();
411 }
412
413 let dirent_area_size = block_entries * EROFS_DIRENT_SIZE as usize;
414 let is_last_block = idx + block_entries >= entries.len();
415
416 let mut block = vec![
418 0u8;
419 if is_last_block {
420 dirent_area_size + name_total
421 } else {
422 blksiz
423 }
424 ];
425
426 let mut name_offset = dirent_area_size;
428 for i in 0..block_entries {
429 let e = &entries[idx + i];
430 let dirent_off = i * EROFS_DIRENT_SIZE as usize;
431
432 block[dirent_off..dirent_off + 8].copy_from_slice(&e.nid.to_le_bytes());
434 block[dirent_off + 8..dirent_off + 10]
436 .copy_from_slice(&(name_offset as u16).to_le_bytes());
437 block[dirent_off + 10] = e.file_type;
439 block[dirent_off + 11] = 0;
441
442 block[name_offset..name_offset + e.name.len()].copy_from_slice(&e.name);
444 name_offset += e.name.len();
445 }
446
447 result.extend_from_slice(&block);
448 idx += block_entries;
449 }
450
451 Ok(result)
452}
453
454fn node_data_size(node: &TreeNode) -> u64 {
455 match node {
456 TreeNode::RegularFile(f) => f.data.len() as u64,
457 TreeNode::Symlink(s) => s.target.len() as u64,
458 _ => 0,
459 }
460}
461
462fn node_xattrs(node: &TreeNode) -> &[Xattr] {
463 match node {
464 TreeNode::RegularFile(f) => &f.xattrs,
465 TreeNode::Directory(d) => &d.xattrs,
466 _ => &[],
467 }
468}
469
470fn node_metadata(node: &TreeNode) -> &InodeMetadata {
471 match node {
472 TreeNode::RegularFile(f) => &f.metadata,
473 TreeNode::Directory(d) => &d.metadata,
474 TreeNode::Symlink(s) => &s.metadata,
475 TreeNode::CharDevice(d) => &d.metadata,
476 TreeNode::BlockDevice(d) => &d.metadata,
477 TreeNode::Fifo(m) => m,
478 TreeNode::Socket(m) => m,
479 }
480}
481
482fn node_nlink(node: &TreeNode) -> u32 {
483 match node {
484 TreeNode::RegularFile(f) => f.nlink,
485 TreeNode::Directory(d) => {
486 let child_dirs = d
488 .entries
489 .values()
490 .filter(|c| matches!(c, TreeNode::Directory(_)))
491 .count();
492 2 + child_dirs as u32
493 }
494 _ => 1,
495 }
496}
497
498fn plan_directory(
501 dir: &DirectoryNode,
502 parent_nid: u32,
503 state: &mut LayoutState,
504 is_root: bool,
505) -> Result<u32, ErofsError> {
506 let blksiz = EROFS_BLKSIZ as u64;
507
508 let dir_plan_idx = state.plans.len();
510 state.plans.push(InodePlan {
511 nid: 0,
512 data_layout: 0,
513 data_block_start: 0,
514 data_block_count: 0,
515 inline_tail_size: 0,
516 xattr_ibody_size: 0,
517 total_inode_size: 0,
518 slots: 0,
519 dir_data: None,
520 parent_nid,
521 });
522 state.inode_count += 1;
523
524 let xattr_ibody_size = compute_xattr_ibody_size(&dir.xattrs)?;
526
527 let dir_data_size = compute_dir_data_size(dir) as u64;
529
530 let inode_fixed_size = EROFS_INODE_EXTENDED_SIZE + xattr_ibody_size;
532
533 let meta_base = state.meta_blkaddr as u64 * blksiz;
535 let nid_offset = state.current_meta_offset - meta_base;
536 if !nid_offset.is_multiple_of(EROFS_ISLOT_SIZE as u64) {
537 let aligned = nid_offset.div_ceil(EROFS_ISLOT_SIZE as u64) * EROFS_ISLOT_SIZE as u64;
539 state.current_meta_offset = meta_base + aligned;
540 }
541
542 let nid_offset = state.current_meta_offset - meta_base;
543 let nid = (nid_offset / EROFS_ISLOT_SIZE as u64) as u32;
544
545 let d = decide_data_layout(
546 dir_data_size,
547 inode_fixed_size,
548 state.current_meta_offset,
549 &mut state.current_data_block,
550 true, );
552 let (data_layout, inline_tail_size, data_block_count, data_block_start) =
553 (d.layout, d.inline_tail_size, d.block_count, d.block_start);
554
555 let total_inode_size = inode_fixed_size + inline_tail_size;
556 let slots = total_inode_size.div_ceil(EROFS_ISLOT_SIZE);
557
558 state.current_meta_offset += (slots * EROFS_ISLOT_SIZE) as u64;
559
560 state.plans[dir_plan_idx] = InodePlan {
562 nid,
563 data_layout,
564 data_block_start,
565 data_block_count,
566 inline_tail_size,
567 xattr_ibody_size,
568 total_inode_size,
569 slots,
570 dir_data: None,
571 parent_nid: if is_root { nid } else { parent_nid },
572 };
573
574 let dir_nid = nid;
575
576 let mut child_nids: BTreeMap<OsString, u32> = BTreeMap::new();
578
579 for (name, child) in &dir.entries {
580 let child_nid = match child {
581 TreeNode::Directory(child_dir) => plan_directory(child_dir, dir_nid, state, false)?,
582 _ => plan_leaf_node(child, state)?,
583 };
584 child_nids.insert(name.clone(), child_nid);
585 }
586
587 let dir_data = serialize_dir_blocks(
589 dir,
590 dir_nid,
591 state.plans[dir_plan_idx].parent_nid,
592 &child_nids,
593 )?;
594 state.plans[dir_plan_idx].dir_data = Some(dir_data);
595
596 Ok(dir_nid)
597}
598
599fn plan_leaf_node(node: &TreeNode, state: &mut LayoutState) -> Result<u32, ErofsError> {
600 let blksiz = EROFS_BLKSIZ as u64;
601
602 let plan_idx = state.plans.len();
603 state.plans.push(InodePlan {
604 nid: 0,
605 data_layout: 0,
606 data_block_start: 0,
607 data_block_count: 0,
608 inline_tail_size: 0,
609 xattr_ibody_size: 0,
610 total_inode_size: 0,
611 slots: 0,
612 dir_data: None,
613 parent_nid: 0,
614 });
615 state.inode_count += 1;
616
617 let xattrs = node_xattrs(node);
618 let xattr_ibody_size = compute_xattr_ibody_size(xattrs)?;
619 let data_size = node_data_size(node);
620 let inode_fixed_size = EROFS_INODE_EXTENDED_SIZE + xattr_ibody_size;
621
622 let meta_base = state.meta_blkaddr as u64 * blksiz;
624 let nid_offset = state.current_meta_offset - meta_base;
625 let aligned_offset = nid_offset.div_ceil(EROFS_ISLOT_SIZE as u64) * EROFS_ISLOT_SIZE as u64;
626 state.current_meta_offset = meta_base + aligned_offset;
627
628 let nid = (aligned_offset / EROFS_ISLOT_SIZE as u64) as u32;
629
630 let allow_inline = !matches!(node, TreeNode::RegularFile(_));
634 let d = decide_data_layout(
635 data_size,
636 inode_fixed_size,
637 state.current_meta_offset,
638 &mut state.current_data_block,
639 allow_inline,
640 );
641 let (data_layout, inline_tail_size, data_block_count, data_block_start) =
642 (d.layout, d.inline_tail_size, d.block_count, d.block_start);
643
644 let total_inode_size = inode_fixed_size + inline_tail_size;
645 let slots = total_inode_size.div_ceil(EROFS_ISLOT_SIZE);
646
647 state.current_meta_offset += (slots * EROFS_ISLOT_SIZE) as u64;
648
649 state.plans[plan_idx] = InodePlan {
650 nid,
651 data_layout,
652 data_block_start,
653 data_block_count,
654 inline_tail_size,
655 xattr_ibody_size,
656 total_inode_size,
657 slots,
658 dir_data: None,
659 parent_nid: 0,
660 };
661
662 Ok(nid)
663}
664
665fn write_data_blocks(
666 file: &mut (impl Write + Seek),
667 state: &LayoutState,
668 tree: &FileTree,
669 data_map: &mut HashMap<PathBuf, (u32, u64)>,
670) -> Result<(), ErofsError> {
671 let meta_end = align_to_block(state.current_meta_offset);
674 let data_area_start = meta_end;
675
676 let data_start_block = (data_area_start / EROFS_BLKSIZ as u64) as u32;
681
682 let current_path = PathBuf::new();
690 write_data_for_tree(
691 file,
692 state,
693 &tree.root,
694 data_start_block,
695 &mut 0,
696 ¤t_path,
697 data_map,
698 )?;
699
700 Ok(())
701}
702
703fn write_data_for_tree(
704 file: &mut (impl Write + Seek),
705 state: &LayoutState,
706 dir: &DirectoryNode,
707 data_start_block: u32,
708 plan_idx: &mut usize,
709 current_path: &Path,
710 data_map: &mut HashMap<PathBuf, (u32, u64)>,
711) -> Result<(), ErofsError> {
712 let blksiz = EROFS_BLKSIZ as u64;
713 let plan = &state.plans[*plan_idx];
714 *plan_idx += 1;
715
716 if let Some(ref dir_data) = plan.dir_data
718 && plan.data_block_count > 0
719 {
720 let abs_block = data_start_block + plan.data_block_start;
721 let offset = abs_block as u64 * blksiz;
722 file.seek(SeekFrom::Start(offset))?;
723
724 let full_block_bytes = plan.data_block_count as usize * EROFS_BLKSIZ as usize;
725 let data_to_write = &dir_data[..std::cmp::min(full_block_bytes, dir_data.len())];
726 file.write_all(data_to_write)?;
727
728 if data_to_write.len() < full_block_bytes {
730 let pad = full_block_bytes - data_to_write.len();
731 file.write_all(&ZEROS[..pad])?;
732 }
733 }
734
735 for (name, child) in &dir.entries {
737 let child_path = current_path.join(name);
738 match child {
739 TreeNode::Directory(child_dir) => {
740 write_data_for_tree(
741 file,
742 state,
743 child_dir,
744 data_start_block,
745 plan_idx,
746 &child_path,
747 data_map,
748 )?;
749 }
750 TreeNode::RegularFile(f) => {
751 let child_plan = &state.plans[*plan_idx];
752 *plan_idx += 1;
753
754 if child_plan.data_block_start != EROFS_NULL_ADDR {
756 let abs_block = data_start_block + child_plan.data_block_start;
757 data_map.insert(child_path, (abs_block, f.data.len() as u64));
758 } else {
759 data_map.insert(child_path, (EROFS_NULL_ADDR, 0));
761 }
762
763 if child_plan.data_block_count > 0 {
764 let abs_block = data_start_block + child_plan.data_block_start;
765 let offset = abs_block as u64 * blksiz;
766 file.seek(SeekFrom::Start(offset))?;
767
768 let full_block_bytes =
769 child_plan.data_block_count as usize * EROFS_BLKSIZ as usize;
770 let data_end = if child_plan.data_layout == EROFS_INODE_FLAT_INLINE {
771 full_block_bytes
772 } else {
773 std::cmp::min(f.data.len(), full_block_bytes)
774 };
775
776 f.data.write_range(0, data_end, file)?;
777
778 if child_plan.data_layout == EROFS_INODE_FLAT_PLAIN
779 && data_end < full_block_bytes
780 {
781 let pad = full_block_bytes - data_end;
782 file.write_all(&ZEROS[..pad])?;
783 }
784 }
785 }
786 TreeNode::Symlink(s) => {
787 let child_plan = &state.plans[*plan_idx];
788 *plan_idx += 1;
789
790 if child_plan.data_block_count > 0 {
791 let abs_block = data_start_block + child_plan.data_block_start;
792 let offset = abs_block as u64 * blksiz;
793 file.seek(SeekFrom::Start(offset))?;
794
795 let full_block_bytes =
796 child_plan.data_block_count as usize * EROFS_BLKSIZ as usize;
797 let data_end = if child_plan.data_layout == EROFS_INODE_FLAT_INLINE {
798 full_block_bytes
799 } else {
800 std::cmp::min(s.target.len(), full_block_bytes)
801 };
802
803 file.write_all(&s.target[..data_end])?;
804
805 if child_plan.data_layout == EROFS_INODE_FLAT_PLAIN
806 && data_end < full_block_bytes
807 {
808 let pad = full_block_bytes - data_end;
809 file.write_all(&ZEROS[..pad])?;
810 }
811 }
812 }
813 _ => {
814 *plan_idx += 1;
816 }
817 }
818 }
819
820 Ok(())
821}
822
823fn write_metadata(
824 file: &mut (impl Write + Seek),
825 state: &LayoutState,
826 tree: &FileTree,
827) -> Result<(), ErofsError> {
828 let meta_end = align_to_block(state.current_meta_offset);
829 let data_start_block = (meta_end / EROFS_BLKSIZ as u64) as u32;
830
831 write_metadata_for_tree(
832 file,
833 state,
834 &TreeNode::Directory(clone_dir_shell(&tree.root)),
835 &tree.root,
836 data_start_block,
837 &mut 0,
838 )?;
839
840 Ok(())
841}
842
843fn clone_dir_shell(dir: &DirectoryNode) -> DirectoryNode {
844 DirectoryNode {
845 metadata: InodeMetadata {
846 uid: dir.metadata.uid,
847 gid: dir.metadata.gid,
848 mode: dir.metadata.mode,
849 mtime: dir.metadata.mtime,
850 mtime_nsec: dir.metadata.mtime_nsec,
851 },
852 xattrs: dir
853 .xattrs
854 .iter()
855 .map(|x| Xattr {
856 name: x.name.clone(),
857 value: x.value.clone(),
858 })
859 .collect(),
860 entries: BTreeMap::new(),
861 }
862}
863
864fn write_metadata_for_tree(
865 file: &mut (impl Write + Seek),
866 state: &LayoutState,
867 node: &TreeNode,
868 real_dir: &DirectoryNode,
869 data_start_block: u32,
870 plan_idx: &mut usize,
871) -> Result<(), ErofsError> {
872 let blksiz = EROFS_BLKSIZ as u64;
873 let meta_base = state.meta_blkaddr as u64 * blksiz;
874 let plan = &state.plans[*plan_idx];
875 *plan_idx += 1;
876
877 let inode_offset = meta_base + plan.nid as u64 * EROFS_ISLOT_SIZE as u64;
879
880 file.seek(SeekFrom::Start(inode_offset))?;
882
883 let mut inode = [0u8; 64];
885
886 let i_format: u16 = 1 | ((plan.data_layout as u16) << 1);
888 inode[0..2].copy_from_slice(&i_format.to_le_bytes());
889
890 let i_xattr_icount = compute_xattr_icount(plan.xattr_ibody_size);
892 inode[2..4].copy_from_slice(&i_xattr_icount.to_le_bytes());
893
894 let mode_bits = mode_type_bits(node);
896 let meta = node_metadata(node);
897 let i_mode = mode_bits | meta.mode;
898 inode[4..6].copy_from_slice(&i_mode.to_le_bytes());
899
900 inode[6..8].copy_from_slice(&0u16.to_le_bytes());
902
903 let i_size: u64 = match node {
905 TreeNode::Directory(_) => {
906 if let Some(ref dd) = plan.dir_data {
907 dd.len() as u64
908 } else {
909 0
910 }
911 }
912 _ => node_data_size(node),
913 };
914 inode[8..16].copy_from_slice(&i_size.to_le_bytes());
915
916 let i_u: u32 = match node {
918 TreeNode::CharDevice(d) | TreeNode::BlockDevice(d) => new_encode_dev(d.major, d.minor),
919 TreeNode::Fifo(_) | TreeNode::Socket(_) => 0,
920 _ => {
921 if plan.data_block_start == EROFS_NULL_ADDR {
922 EROFS_NULL_ADDR
923 } else {
924 data_start_block + plan.data_block_start
925 }
926 }
927 };
928 inode[16..20].copy_from_slice(&i_u.to_le_bytes());
929
930 inode[20..24].copy_from_slice(&plan.nid.to_le_bytes());
932
933 inode[24..28].copy_from_slice(&meta.uid.to_le_bytes());
935
936 inode[28..32].copy_from_slice(&meta.gid.to_le_bytes());
938
939 inode[32..40].copy_from_slice(&meta.mtime.to_le_bytes());
941
942 inode[40..44].copy_from_slice(&meta.mtime_nsec.to_le_bytes());
944
945 let nlink = node_nlink(node);
947 inode[44..48].copy_from_slice(&nlink.to_le_bytes());
948
949 file.write_all(&inode)?;
952
953 let xattrs = node_xattrs(node);
955 if plan.xattr_ibody_size > 0 {
956 write_xattr_ibody(file, xattrs)?;
957 }
958
959 if plan.inline_tail_size > 0 {
961 match node {
962 TreeNode::Directory(_) => {
963 if let Some(ref dir_data) = plan.dir_data {
964 let full_block_bytes = plan.data_block_count as usize * EROFS_BLKSIZ as usize;
965 let tail = &dir_data[full_block_bytes..];
966 file.write_all(tail)?;
967 }
968 }
969 TreeNode::RegularFile(f) => {
970 let full_block_bytes = plan.data_block_count as usize * EROFS_BLKSIZ as usize;
971 let tail_len = f.data.len() - full_block_bytes;
972 f.data.write_range(full_block_bytes, tail_len, file)?;
973 }
974 TreeNode::Symlink(s) => {
975 let full_block_bytes = plan.data_block_count as usize * EROFS_BLKSIZ as usize;
976 let tail = &s.target[full_block_bytes..];
977 file.write_all(tail)?;
978 }
979 _ => {}
980 }
981 }
982
983 if let TreeNode::Directory(_) = node {
985 for child in real_dir.entries.values() {
986 match child {
987 TreeNode::Directory(child_dir) => {
988 write_metadata_for_tree(
989 file,
990 state,
991 child,
992 child_dir,
993 data_start_block,
994 plan_idx,
995 )?;
996 }
997 _ => {
998 write_metadata_for_leaf(file, state, child, data_start_block, plan_idx)?;
999 }
1000 }
1001 }
1002 }
1003
1004 Ok(())
1005}
1006
1007fn write_metadata_for_leaf(
1008 file: &mut (impl Write + Seek),
1009 state: &LayoutState,
1010 node: &TreeNode,
1011 data_start_block: u32,
1012 plan_idx: &mut usize,
1013) -> Result<(), ErofsError> {
1014 let blksiz = EROFS_BLKSIZ as u64;
1015 let meta_base = state.meta_blkaddr as u64 * blksiz;
1016 let plan = &state.plans[*plan_idx];
1017 *plan_idx += 1;
1018
1019 let inode_offset = meta_base + plan.nid as u64 * EROFS_ISLOT_SIZE as u64;
1020 file.seek(SeekFrom::Start(inode_offset))?;
1021
1022 let mut inode = [0u8; 64];
1023
1024 let i_format: u16 = 1 | ((plan.data_layout as u16) << 1);
1025 inode[0..2].copy_from_slice(&i_format.to_le_bytes());
1026
1027 let i_xattr_icount = compute_xattr_icount(plan.xattr_ibody_size);
1028 inode[2..4].copy_from_slice(&i_xattr_icount.to_le_bytes());
1029
1030 let mode_bits = mode_type_bits(node);
1031 let meta = node_metadata(node);
1032 let i_mode = mode_bits | meta.mode;
1033 inode[4..6].copy_from_slice(&i_mode.to_le_bytes());
1034
1035 inode[6..8].copy_from_slice(&0u16.to_le_bytes());
1036
1037 let i_size = node_data_size(node);
1038 inode[8..16].copy_from_slice(&i_size.to_le_bytes());
1039
1040 let i_u: u32 = match node {
1041 TreeNode::CharDevice(d) | TreeNode::BlockDevice(d) => new_encode_dev(d.major, d.minor),
1042 TreeNode::Fifo(_) | TreeNode::Socket(_) => 0,
1043 _ => {
1044 if plan.data_block_start == EROFS_NULL_ADDR {
1045 EROFS_NULL_ADDR
1046 } else {
1047 data_start_block + plan.data_block_start
1048 }
1049 }
1050 };
1051 inode[16..20].copy_from_slice(&i_u.to_le_bytes());
1052
1053 inode[20..24].copy_from_slice(&plan.nid.to_le_bytes());
1054 inode[24..28].copy_from_slice(&meta.uid.to_le_bytes());
1055 inode[28..32].copy_from_slice(&meta.gid.to_le_bytes());
1056 inode[32..40].copy_from_slice(&meta.mtime.to_le_bytes());
1057 inode[40..44].copy_from_slice(&meta.mtime_nsec.to_le_bytes());
1058
1059 let nlink = node_nlink(node);
1060 inode[44..48].copy_from_slice(&nlink.to_le_bytes());
1061
1062 file.write_all(&inode)?;
1063
1064 let xattrs = node_xattrs(node);
1065 if plan.xattr_ibody_size > 0 {
1066 write_xattr_ibody(file, xattrs)?;
1067 }
1068
1069 if plan.inline_tail_size > 0 {
1070 match node {
1071 TreeNode::RegularFile(f) => {
1072 let full_block_bytes = plan.data_block_count as usize * EROFS_BLKSIZ as usize;
1073 let tail_len = f.data.len() - full_block_bytes;
1074 f.data.write_range(full_block_bytes, tail_len, file)?;
1075 }
1076 TreeNode::Symlink(s) => {
1077 let full_block_bytes = plan.data_block_count as usize * EROFS_BLKSIZ as usize;
1078 let tail = &s.target[full_block_bytes..];
1079 file.write_all(tail)?;
1080 }
1081 _ => {}
1082 }
1083 }
1084
1085 Ok(())
1086}
1087
1088fn write_xattr_ibody(file: &mut (impl Write + Seek), xattrs: &[Xattr]) -> Result<(), ErofsError> {
1089 let header = [0u8; EROFS_XATTR_IBODY_HEADER_SIZE as usize];
1092 file.write_all(&header)?;
1093
1094 for xattr in xattrs {
1095 let (prefix_idx, suffix) =
1096 xattr_prefix_index(&xattr.name).ok_or(ErofsError::UnsupportedXattrPrefix)?;
1097
1098 let mut entry = [0u8; 4];
1100 entry[0] = suffix.len() as u8; entry[1] = prefix_idx; entry[2..4].copy_from_slice(&(xattr.value.len() as u16).to_le_bytes()); file.write_all(&entry)?;
1104
1105 file.write_all(suffix)?;
1107
1108 file.write_all(&xattr.value)?;
1110
1111 let entry_size = 4 + suffix.len() + xattr.value.len();
1113 let aligned = erofs_xattr_align(entry_size);
1114 let pad = aligned - entry_size;
1115 if pad > 0 {
1116 file.write_all(&ZEROS[..pad])?;
1117 }
1118 }
1119
1120 Ok(())
1121}
1122
1123fn write_superblock(file: &mut (impl Write + Seek), state: &LayoutState) -> Result<(), ErofsError> {
1124 let blksiz = EROFS_BLKSIZ as u64;
1125
1126 file.seek(SeekFrom::Start(0))?;
1128 file.write_all(&[0u8; EROFS_SUPER_OFFSET as usize])?;
1129
1130 let meta_end = align_to_block(state.current_meta_offset);
1132 let data_start_block = (meta_end / blksiz) as u32;
1133 let total_blocks = data_start_block + state.current_data_block;
1134
1135 let mut sb = [0u8; EROFS_SUPERBLOCK_SIZE as usize];
1137
1138 sb[0x00..0x04].copy_from_slice(&EROFS_SUPER_MAGIC.to_le_bytes());
1140
1141 sb[0x04..0x08].copy_from_slice(&0u32.to_le_bytes());
1143
1144 sb[0x08..0x0C].copy_from_slice(&EROFS_FEATURE_COMPAT_SB_CHKSUM.to_le_bytes());
1146
1147 sb[0x0C] = EROFS_BLKSIZ_BITS;
1149
1150 sb[0x0D] = 0;
1152
1153 if state.root_nid > u16::MAX as u32 {
1155 return Err(ErofsError::NidOverflow);
1156 }
1157 sb[0x0E..0x10].copy_from_slice(&(state.root_nid as u16).to_le_bytes());
1158
1159 sb[0x10..0x18].copy_from_slice(&state.inode_count.to_le_bytes());
1161
1162 sb[0x18..0x20].copy_from_slice(&0u64.to_le_bytes());
1164
1165 sb[0x20..0x24].copy_from_slice(&0u32.to_le_bytes());
1167
1168 sb[0x24..0x28].copy_from_slice(&total_blocks.to_le_bytes());
1170
1171 sb[0x28..0x2C].copy_from_slice(&state.meta_blkaddr.to_le_bytes());
1173
1174 sb[0x2C..0x30].copy_from_slice(&0u32.to_le_bytes());
1176
1177 sb[0x50..0x54].copy_from_slice(&0u32.to_le_bytes());
1185
1186 sb[0x5A] = 0;
1188
1189 let mut block = vec![0u8; EROFS_BLKSIZ as usize];
1196 block
1197 [EROFS_SUPER_OFFSET as usize..EROFS_SUPER_OFFSET as usize + EROFS_SUPERBLOCK_SIZE as usize]
1198 .copy_from_slice(&sb);
1199
1200 let crc_data = &block[EROFS_SUPER_OFFSET as usize..EROFS_BLKSIZ as usize];
1203 let checksum = crc32c::crc32c_raw(0xFFFF_FFFF, crc_data);
1204
1205 sb[0x04..0x08].copy_from_slice(&checksum.to_le_bytes());
1207
1208 file.seek(SeekFrom::Start(EROFS_SUPER_OFFSET))?;
1210 file.write_all(&sb)?;
1211
1212 let remaining = EROFS_BLKSIZ as u64 - EROFS_SUPER_OFFSET - EROFS_SUPERBLOCK_SIZE as u64;
1214 file.write_all(&vec![0u8; remaining as usize])?;
1215
1216 Ok(())
1217}
1218
1219fn align_to_block(offset: u64) -> u64 {
1220 let blksiz = EROFS_BLKSIZ as u64;
1221 offset.div_ceil(blksiz) * blksiz
1222}