Skip to content

Commit 3ccfa37

Browse files
committed
extract_utils: implement recovery extraction
Change-Id: I6d25936c1377e7bdabae85965a43aed9e4bdb4ce
1 parent dc15bc9 commit 3ccfa37

3 files changed

Lines changed: 170 additions & 1 deletion

File tree

extract_utils/extract.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from extract_utils.ext4 import EXT4_MAGIC, EXT4_MAGIC_OFFSET
1717
from extract_utils.extract_moto_piv import MOTO_PIV_MAGIC, extract_moto_piv
18+
from extract_utils.extract_recovery import extract_recovery_partition
1819
from extract_utils.file import File
1920
from extract_utils.lp import LpImage
2021
from extract_utils.sparse_img import SPARSE_HEADER_MAGIC, unsparse_images
@@ -482,7 +483,9 @@ def extract_all_partitions(dump_dir: str, ctx: ExtractCtx):
482483

483484
while partitions:
484485
for partition in partitions:
485-
if partition in firmware_partitions:
486+
if partition == 'recovery':
487+
extract_recovery_partition(partition, dump_dir)
488+
elif partition in firmware_partitions:
486489
extract_firmware_partition(partition, dump_dir)
487490
else:
488491
extract_partition(partition, dump_dir)

extract_utils/extract_recovery.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#
2+
# SPDX-FileCopyrightText: The LineageOS Project
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
6+
from __future__ import annotations
7+
8+
import shlex
9+
from dataclasses import dataclass
10+
from pathlib import Path
11+
from tempfile import TemporaryDirectory
12+
from typing import List, Optional
13+
14+
from extract_utils.tools import unpack_bootimg_path
15+
from extract_utils.utils import find_files, run_cmd, run_cmd_bytes
16+
17+
BOOT_MAGIC = b'ANDROID!'
18+
19+
VENDOR_BOOT_MAGIC = b'VNDRBOOT'
20+
21+
22+
@dataclass(frozen=True)
23+
class VendorRamdiskFragment:
24+
path: Path
25+
ramdisk_type: int
26+
name: str = ''
27+
28+
29+
def parse_mkbootimg_fragments(data: str):
30+
argv = shlex.split(data, posix=True)
31+
32+
fragments: List[VendorRamdiskFragment] = []
33+
fragment_type: Optional[int] = None
34+
fragment_name: Optional[str] = None
35+
36+
i = 0
37+
n = len(argv)
38+
while i < n:
39+
assert argv[i].startswith('--')
40+
41+
match argv[i]:
42+
case '--ramdisk_type':
43+
assert i + 1 < n
44+
fragment_type = int(argv[i + 1], 0)
45+
case '--ramdisk_name':
46+
assert i + 1 < n
47+
fragment_name = argv[i + 1]
48+
case '--vendor_ramdisk_fragment':
49+
assert i + 1 < n
50+
assert fragment_type is not None
51+
assert fragment_name is not None
52+
fragment = VendorRamdiskFragment(
53+
Path(argv[i + 1]),
54+
fragment_type,
55+
fragment_name,
56+
)
57+
fragments.append(fragment)
58+
fragment_type = None
59+
fragment_name = None
60+
case _:
61+
pass
62+
63+
i += 2
64+
65+
return fragments
66+
67+
68+
def unpack_bootimg(file_path: str, output_path: str):
69+
output = run_cmd(
70+
[
71+
unpack_bootimg_path,
72+
'--boot_img',
73+
file_path,
74+
'--out',
75+
output_path,
76+
'--format',
77+
'mkbootimg',
78+
]
79+
)
80+
return output
81+
82+
83+
def extract_ramdisk(ramdisk: Path, out_dir: Path) -> None:
84+
out_dir.mkdir(parents=True, exist_ok=True)
85+
86+
with ramdisk.open('rb') as f:
87+
head6 = f.read(6)
88+
head4 = head6[:4]
89+
90+
CPIO_NEWC = b'070701'
91+
CPIO_CRC = b'070702'
92+
GZIP_MAGIC = 0x8B1F.to_bytes(2, 'little')
93+
LZ4F_MAGIC_MODERN = 0x184D2204.to_bytes(4, 'little')
94+
LZ4F_MAGIC_LEGACY = 0x184C2102.to_bytes(4, 'little')
95+
96+
decompressor: Optional[List[str]] = None
97+
if head4.startswith(GZIP_MAGIC):
98+
decompressor = [
99+
'gzip',
100+
'-dc',
101+
str(ramdisk),
102+
]
103+
elif head4 == LZ4F_MAGIC_MODERN or head4 == LZ4F_MAGIC_LEGACY:
104+
decompressor = [
105+
'lz4',
106+
'-dc',
107+
str(ramdisk),
108+
]
109+
elif head6 in (CPIO_NEWC, CPIO_CRC):
110+
decompressor = None
111+
else:
112+
assert False, head6
113+
114+
cpio_cmd = ['cpio', '-id', '--no-absolute-filenames']
115+
116+
if decompressor is not None:
117+
output_data = run_cmd_bytes(decompressor)
118+
else:
119+
output_data = ramdisk.read_bytes()
120+
121+
run_cmd_bytes(cpio_cmd, data=output_data, cwd=out_dir)
122+
123+
124+
def find_android_paths(input_path: str):
125+
return find_files(
126+
input_path,
127+
magic=BOOT_MAGIC,
128+
)
129+
130+
131+
def find_vndrboot_paths(input_path: str):
132+
return find_files(
133+
input_path,
134+
magic=VENDOR_BOOT_MAGIC,
135+
)
136+
137+
138+
def extract_recovery_paths(
139+
file_paths: List[str],
140+
partition: str,
141+
dump_dir: str,
142+
):
143+
recovery_path = Path(dump_dir, partition)
144+
145+
for file_path in file_paths:
146+
with TemporaryDirectory() as tmp_dir:
147+
output = unpack_bootimg(file_path, tmp_dir)
148+
fragments = parse_mkbootimg_fragments(output)
149+
for fragment in fragments:
150+
# TODO: we probably need more checks here, as vendor_kernel_boot
151+
# seems to contain a ramdisk with ramdisk_type=1 too
152+
if fragment.ramdisk_type == 1:
153+
extract_ramdisk(fragment.path, recovery_path)
154+
break
155+
156+
157+
def extract_recovery_partition(partition: str, dump_dir: str):
158+
vndrboot_paths = find_vndrboot_paths(dump_dir)
159+
extract_recovery_paths(vndrboot_paths, partition, dump_dir)
160+
161+
android_paths = find_android_paths(dump_dir)
162+
extract_recovery_paths(android_paths, partition, dump_dir)

extract_utils/tools.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,7 @@
5050
'carriersettings-extractor/carriersettings_extractor.py',
5151
)
5252
fbpacktool_path = path.join(lineage_scripts_dir, 'fbpacktool/fbpacktool.py')
53+
54+
system_tools_dir = path.join(android_root, 'system/tools')
55+
mkbootimg_dir = path.join(system_tools_dir, 'mkbootimg')
56+
unpack_bootimg_path = path.join(mkbootimg_dir, 'unpack_bootimg.py')

0 commit comments

Comments
 (0)