Skip to content

Commit 27366dc

Browse files
committed
feat(handler): add EROFS filesystem handler
EROFS is a high-performance read-only filesystem originally developed by Huawei and now used extensively in AOSP. It offers features like compression (LZ4, LZMA), inline data, and reduced storage overhead, making it ideal for read-only system images. Create erofs file : > mkfs.erofs -z[compressor, level (optional)] foo.erofs.img foo/ Extract erofs file: > fsck.erofs --extract=foo_extracted/ foo.erofs.img Key notes: - First 1024 bytes are reserved for the boot loader - Header starts at 0x400 with E0F5E1E2 - Header can be more than 128 bytes if extra slots provided - File format in little endian - There is no standarized file extension. The official documentation uses ".erofs.img" - Checksum is differ form verion to version Unblob struct the header until the feature_incompact. The rest is labeled as "reserved". Unblob parser the end offset by multiplying the block size in bit shift with the block count. Since the checksum is not consistent troughout versions, unblob matched on block count (at least 1), printable volume name and if there is a build time. [Sources] https://elixir.bootlin.com/linux/v6.14-rc6/source/fs/erofs/erofs_fs.h https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/erofs/erofs_fs.h?h=v6.6 https://erofs.docs.kernel.org/en/latest/core_ondisk.html https://www.kernel.org/doc/html/latest/filesystems/erofs.html
1 parent 3b471f9 commit 27366dc

File tree

7 files changed

+81
-1
lines changed

7 files changed

+81
-1
lines changed

install-deps.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ apt-get update
55
apt-get install --no-install-recommends -y \
66
android-sdk-libsparse-utils \
77
curl \
8+
erofs-utils \
89
lz4 \
910
lziprecover \
1011
lzop \

package.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
fetchFromGitHub,
55
makeWrapper,
66
e2fsprogs-nofortify,
7+
erofs-utils,
78
jefferson,
89
lz4,
910
lziprecover,
@@ -24,6 +25,7 @@ let
2425
# These dependencies are only added to PATH
2526
runtimeDeps = [
2627
e2fsprogs-nofortify
28+
erofs-utils
2729
jefferson
2830
lziprecover
2931
lzop

python/unblob/handlers/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
ubi,
5050
yaffs,
5151
)
52-
from .filesystem.android import sparse
52+
from .filesystem.android import erofs, sparse
5353

5454
BUILTIN_HANDLERS: Handlers = (
5555
cramfs.CramFSHandler,
@@ -118,6 +118,7 @@
118118
engenius.EngeniusHandler,
119119
ecc.AutelECCHandler,
120120
uzip.UZIPHandler,
121+
erofs.EROFSHandler,
121122
)
122123

123124
BUILTIN_DIR_HANDLERS: DirectoryHandlers = (
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import io
2+
from typing import Optional
3+
4+
from unblob.extractors import Command
5+
from unblob.file_utils import (
6+
Endian,
7+
InvalidInputFormat,
8+
)
9+
from unblob.models import (
10+
File,
11+
HexString,
12+
StructHandler,
13+
ValidChunk,
14+
)
15+
16+
C_DEFINITIONS = r"""
17+
typedef struct erofs_handler{
18+
uint32_t magic;
19+
uint32_t crc32c;
20+
uint32_t feature_compact;
21+
uint8_t block_size_bs;
22+
uint8_t sb_extslots;
23+
uint16_t root_nid;
24+
uint64_t inos;
25+
uint64_t build_time;
26+
uint32_t build_time_nsec;
27+
uint32_t block_count;
28+
uint32_t meta_blkaddr;
29+
uint32_t xattr_blkaddr;
30+
uint8_t uuid[16];
31+
char volume_name[16];
32+
uint32_t feature_incompact;
33+
char reserved[44];
34+
} erofs_handler_t;
35+
"""
36+
37+
SUPERBLOCK_OFFSET = 0x400
38+
39+
40+
class EROFSHandler(StructHandler):
41+
NAME = "erofs"
42+
PATTERNS = [HexString("e2 e1 f5 e0")] # Magic in little endian
43+
HEADER_STRUCT = "erofs_handler_t"
44+
C_DEFINITIONS = C_DEFINITIONS
45+
EXTRACTOR = Command(
46+
"fsck.erofs",
47+
"--no-preserve",
48+
"--extract={outdir}",
49+
"{inpath}",
50+
)
51+
PATTERN_MATCH_OFFSET = -SUPERBLOCK_OFFSET
52+
53+
def is_valid_header(self, header) -> bool:
54+
return (
55+
header.block_count >= 1
56+
and header.build_time > 0
57+
and str(header.volume_name).isprintable()
58+
)
59+
60+
def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
61+
file.seek(SUPERBLOCK_OFFSET, io.SEEK_CUR)
62+
header = self.parse_header(file, Endian.LITTLE)
63+
if not self.is_valid_header(header):
64+
raise InvalidInputFormat("Invalid erofs header.")
65+
66+
end_offset = (1 << header.block_size_bs) * header.block_count
67+
return ValidChunk(
68+
start_offset=start_offset,
69+
end_offset=end_offset,
70+
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:7f400d3497501c6e68324868d88207aa9398888902414045f4ea498ff26bcdcd
3+
size 4096
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:0bfc3200f0152ab9e91e662afd75add3306131f670a1d71539f680c7acdb0a9f
3+
size 13
127 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)