1import io
2from typing import Optional
3
4from unblob.extractors import Command
5from unblob.file_utils import (
6 Endian,
7 InvalidInputFormat,
8 snull,
9)
10from unblob.models import (
11 File,
12 HandlerDoc,
13 HandlerType,
14 HexString,
15 Reference,
16 StructHandler,
17 ValidChunk,
18)
19
20C_DEFINITIONS = r"""
21 typedef struct erofs_handler{
22 uint32_t magic;
23 uint32_t crc32c;
24 uint32_t feature_compact;
25 uint8_t block_size_bs;
26 uint8_t sb_extslots;
27 uint16_t root_nid;
28 uint64_t inos;
29 uint64_t build_time;
30 uint32_t build_time_nsec;
31 uint32_t block_count;
32 uint32_t meta_blkaddr;
33 uint32_t xattr_blkaddr;
34 uint8_t uuid[16];
35 char volume_name[16];
36 uint32_t feature_incompact;
37 char reserved[44];
38 } erofs_handler_t;
39"""
40
41SUPERBLOCK_OFFSET = 0x400
42
43
44class EROFSHandler(StructHandler):
45 NAME = "erofs"
46 PATTERNS = [HexString("e2 e1 f5 e0")] # Magic in little endian
47 HEADER_STRUCT = "erofs_handler_t"
48 C_DEFINITIONS = C_DEFINITIONS
49 EXTRACTOR = Command(
50 "fsck.erofs",
51 "--no-preserve",
52 "--extract={outdir}",
53 "{inpath}",
54 )
55 PATTERN_MATCH_OFFSET = -SUPERBLOCK_OFFSET
56
57 DOC = HandlerDoc(
58 name="Android EROFS",
59 description="EROFS (Enhanced Read-Only File System) is a lightweight, high-performance file system designed for read-only use cases, commonly used in Android and Linux. It features compression support, metadata efficiency, and a fixed superblock structure.",
60 handler_type=HandlerType.FILESYSTEM,
61 vendor="Google",
62 references=[
63 Reference(
64 title="EROFS Documentation",
65 url="https://www.kernel.org/doc/html/latest/filesystems/erofs.html",
66 ),
67 Reference(
68 title="EROFS Wikipedia",
69 url="https://en.wikipedia.org/wiki/Enhanced_Read-Only_File_System",
70 ),
71 ],
72 limitations=[],
73 )
74
75 def is_valid_header(self, header) -> bool:
76 try:
77 snull(header.volume_name).decode("utf-8")
78 except UnicodeDecodeError:
79 return False
80 return (
81 header.block_count >= 1
82 and header.build_time > 0
83 and header.build_time_nsec > 0
84 and header.block_size_bs >= 9
85 )
86
87 def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
88 file.seek(start_offset + SUPERBLOCK_OFFSET, io.SEEK_SET)
89 header = self.parse_header(file, Endian.LITTLE)
90 if not self.is_valid_header(header):
91 raise InvalidInputFormat("Invalid erofs header.")
92
93 end_offset = (1 << header.block_size_bs) * header.block_count
94 return ValidChunk(
95 start_offset=start_offset,
96 end_offset=end_offset,
97 )