Files
avbroot/extra/cpiotool.py
T
Andrew Gunnerson 0a1888ad94 Magisk >=25207 require a rules device ID
Newer Magisk versions no longer try to autodetect a writable ext4
partition for storing SELinux rules during boot. Instead, the block
device is detected during boot image patching and is stored in the
ramdisk's `.backup/.magisk` as `makedev(rdev_maj, rdev_min)`.

This unfortunately complicates the patching process for avbroot. Even if
we replicate Magisk's algorithm for finding a suitable partition,
there's no way to find the block device's rdev for it with the
information contained in the OTA package. This is the first change that
adds a hard dependency on information only attainable from a running
device.

For these newer Magisk versions, the user will have to patch the boot
image once in the Magisk app (must be on the target device) and then run
`avbroot magisk-info` to show the computed device ID. Then, avbroot can
use this during patching via `--magisk-rules-device`. If the user's
device is unable to run the Magisk app prior to patching (eg.
unbootable), they'll have to go through the patching process twice, once
with `--ignore-magisk-warnings` and a second time with the proper device
ID specified.

When using `--ignore-magisk-warnings`, root still works, but there will
be subtle issues, like certain modules failing to load.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-03-09 17:01:39 -05:00

120 lines
3.3 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import base64
import os
import sys
sys.path.append(os.path.join(sys.path[0], '..'))
from avbroot.formats import compression
from avbroot.formats import cpio
CONTENT_BEGIN = '----- BEGIN UTF-8 CONTENT -----'
CONTENT_END = '----- END UTF-8 CONTENT -----'
CONTENT_END_NO_NEWLINE = '----- END UTF-8 CONTENT (NO NEWLINE) -----'
BASE64_BEGIN = '----- BEGIN BASE64 CONTENT -----'
BASE64_END = '----- END BASE64 CONTENT -----'
BASE64_END_TRUNCATED = '----- END BASE64 CONTENT (TRUNCATED) -----'
NO_DATA = '----- NO DATA -----'
def print_content(data, truncate=False):
if not data:
print(NO_DATA)
return
if b'\0' not in data:
try:
data_str = data.decode('UTF-8')
if CONTENT_BEGIN not in data_str \
and CONTENT_END not in data_str \
and CONTENT_END_NO_NEWLINE not in data_str:
print(CONTENT_BEGIN)
print(data_str, end='')
if data_str[-1] != '\n':
print()
print(CONTENT_END_NO_NEWLINE)
else:
print(CONTENT_END)
return
except UnicodeDecodeError:
pass
data_base64 = base64.b64encode(data).decode('ascii')
print(BASE64_BEGIN)
for i, offset in enumerate(range(0, len(data_base64), 76)):
if truncate and i == 5:
print(BASE64_END_TRUNCATED)
return
print(data_base64[offset:offset + 76])
print(BASE64_END)
def parse_args():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', required=True,
help='Subcommands')
dump = subparsers.add_parser('dump', help='Dump cpio headers and data')
repack = subparsers.add_parser('repack', help='Repack cpio archive')
dump.add_argument('--no-truncate', action='store_true',
help='Do not truncate binary file contents')
for p in (dump, repack):
p.add_argument('input', help='Path to input cpio file')
repack.add_argument('output', help='Path to output cpio file')
return parser.parse_args()
def load_archive(path, **cpio_kwargs):
with open(path, 'rb') as f_raw:
with compression.CompressedFile(f_raw, 'rb', raw_if_unknown=True) as f:
return cpio.load(f.fp, **cpio_kwargs), f.format
def save_archive(path, entries, format):
with open(path, 'wb') as f_raw:
with compression.CompressedFile(f_raw, 'wb', format=format,
raw_if_unknown=True) as f:
cpio.save(f.fp, entries)
def main():
args = parse_args()
if args.subcommand == 'dump':
entries, format = load_archive(
args.input,
# We want to show the headers exactly as they are
include_trailer=True,
reassign_inodes=False,
)
print('Compression format:', format)
print()
for entry in entries:
print(entry)
print_content(entry.content, truncate=not args.no_truncate)
print()
elif args.subcommand == 'repack':
entries, format = load_archive(args.input)
save_archive(args.output, entries, format)
else:
raise NotImplementedError()
if __name__ == '__main__':
main()