#!/usr/bin/env python3 """Build an independent, read-only recovery filesystem from its own rootfs.""" import argparse import json from pathlib import Path import subprocess import sys import tarfile import tempfile import uuid project = Path(__file__).resolve().parents[1] sys.path.insert(0, str(project / 'tools')) from image_formats import digest, NAMESPACE parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--rootfs', type=Path, default=project / 'out/rootfs-recovery.tar') parser.add_argument('--output-directory', type=Path) args = parser.parse_args() rootfs = args.rootfs.resolve(strict=True) with tarfile.open(rootfs) as archive: profile = archive.extractfile('usr/share/fds/image-profile').read(64).decode().strip() try: root = archive.getmember('.') except KeyError: parser.error('rootfs omits root directory metadata; rebuild the rootfs') if not root.isdir() or (root.uid, root.gid, root.mode) != (0, 0, 0o755): parser.error('rootfs must explicitly record / as root:root mode 0755; rebuild the rootfs') if profile != 'recovery': parser.error('input must be a separately built recovery rootfs') work = args.output_directory or Path(tempfile.mkdtemp(prefix='recovery-build.', dir=project / 'out')) if not work.is_dir() or any(work.iterdir()): parser.error('output directory must exist and be empty') epoch = int(subprocess.check_output(['git', '-C', str(project / 'vendor/void-packages'), 'show', '-s', '--format=%ct', 'HEAD'])) identity = uuid.uuid5(NAMESPACE, 'recovery:' + digest(rootfs)) runner = str(project / 'tools/in-image-tools') image = work / 'recovery.erofs' subprocess.run([runner, 'mkfs.erofs', '-T', str(epoch), '-U', str(identity), '-b4096', '-L', 'FDS_RECOVERY', '--tar=f', str(image), str(rootfs)], check=True) subprocess.run([runner, 'fsck.erofs', '--extract', str(image)], check=True) (work / 'manifest.json').write_text(json.dumps({ 'format': 1, 'profile': profile, 'rootfs_sha256': digest(rootfs), 'image_sha256': digest(image), 'bytes': image.stat().st_size, 'partition_name': 'FDS_RECOVERY', 'filesystem': 'erofs', }, indent=2) + '\n') if args.output_directory is None: target = project / 'out/fds-recovery.img.next' target.symlink_to(work.name + '/recovery.erofs') target.replace(project / 'out/fds-recovery.img') print(f'PASS: independent recovery EROFS (partition payload): {work}')