From: Łukasz Stelmach Date: Mon, 4 Sep 2023 10:23:12 +0000 (+0200) Subject: scripts: rewrite fusing scirpts in python X-Git-Tag: accepted/tizen/unified/20231106.171643~15 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=ac303b1cfe3f7ec923c752a514de0ae00663ea80;p=platform%2Fkernel%2Fu-boot.git scripts: rewrite fusing scirpts in python Replace target specific shell fusing scirpts with a single extensible Python script. To add a new target add a new class inheriting from SdFusingTarget or SdFusingTargetAB and provide necessary information in the part_table class variable. Change-Id: I98cc732d78a8e71b423bc7ec42c1a6a890ffb41b Signed-off-by: Łukasz Stelmach --- diff --git a/scripts/tizen/sd_fusing.py b/scripts/tizen/sd_fusing.py new file mode 100755 index 0000000000..162a1cb90b --- /dev/null +++ b/scripts/tizen/sd_fusing.py @@ -0,0 +1,754 @@ +#!/usr/bin/env python3 + +from functools import reduce + +import argparse +import atexit +import errno +import logging +import os +import re +import shutil +import stat +import subprocess +import sys +import tarfile +import tempfile + +__version__ = "1.0.0" + +Format = False +Device = "" +File = "" +Yes = False + +class DebugFormatter(logging.Formatter): + def format(self, record): + if record.levelno == logging.DEBUG: + record.debuginfo = "[{}:{}] ".format(os.path.basename(record.pathname), record.lineno) + else: + record.debuginfo = '' + return logging.Formatter.format(self, record) + +class ColorFormatter(DebugFormatter): + _levelToColor = { + logging.CRITICAL: "\x1b[35;1m", + logging.ERROR: "\x1b[33;1m", + logging.WARNING: "\x1b[33;1m", + logging.INFO: "\x1b[0m", + logging.DEBUG: "\x1b[30;1m", + logging.NOTSET: "\x1b[30;1m" + } + def format(self, record): + record.levelcolor = self._levelToColor[record.levelno] + record.msg = record.msg + return super().format(record) + +class ColorStreamHandler(logging.StreamHandler): + def __init__(self, stream=None, format=None, datefmt=None, style='%', cformat=None): + logging.StreamHandler.__init__(self, stream) + if os.isatty(self.stream.fileno()): + self.formatter = ColorFormatter(cformat, datefmt, style) + self.terminator = "\x1b[0m\n" + else: + self.formatter = DebugFormatter(format, datefmt, style) + +class Partition: + def __init__(self, name, size, start=None, ptype=None, fstype="raw", bootable=False): + self.name = name + self.size = size + self.start = start + self.ptype = ptype + self.bootable = bootable + def __str__(self): + output = [] + if self.start: + output.append(f"start={self.start}MiB") + if type(self.size) == int and self.size >= 0: + output.append(f"size={self.size}MiB") + if self.name: + output.append(f"name={self.name}") + output.append(f"type={self.ptype}") + if self.bootable: + output.append("bootable") + return ", ".join(output) + "\n" + +class Label: + def __init__(self, part_table, ltype): + self.ltype = ltype + if ltype == 'gpt': + ptype = "0FC63DAF-8483-4772-8E79-3D69D8477DE4" + elif ltype == 'dos': + ptype = '83' + self.part_table = [] + for part in part_table: + part["ptype"] = part.get("ptype", ptype) + self.part_table.append(Partition(**part)) + def __str__(self): + output = f"label: {self.ltype}\n" + for part in self.part_table: + output += str(part) + return output + +class SdFusingTarget: + def __init__(self, device, ltype): + # TODO: make a copy of a sublcass part_table + self.with_super = False + self.device = device + total_size = device_size(device) + self.user_size = total_size - self.reserved_space - \ + reduce(lambda x, y: x + (y["size"] or 0), self.part_table, 0) + if self.user_size < 100: + logging.error(f"Not enough space for user data ({self.user_size}). Use larger storage.") + raise OSError(errno.ENOSPC, os.strerror(errno.ENOSPC), device) + self.part_table[self.user_partition]["size"] = self.user_size + self.label = Label(self.part_table, ltype) + + def get_partition_index(self, binary): + return self.binaries.get(binary, None) + + params = () + def initialize_parameters(self): + pass + +class SdFusingTargetAB(SdFusingTarget): + def get_partition_index(self, binary): + if self.update == 'b': + return self.binaries_b.get(binary, None) + return self.binaries.get(binary, None) + +class RpiInitParams: + def initialize_parameters(self): + logging.debug("Initializing parameterss") + n = None + for i, p in enumerate(self.part_table): + if p['name'] == 'inform': + n = i + 1; + break + d = "/dev/" + get_partition_device(self.device, n) + + argv = ['tune2fs', '-O', '^metadata_csum', d] + logging.debug(" ".join(argv)) + subprocess.run(argv, + stdin=subprocess.DEVNULL, + stdout=None, stderr=None) + + with tempfile.TemporaryDirectory() as mnt: + argv = ['mount', '-t', 'ext4', d, mnt] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv, + stdin=subprocess.DEVNULL, + stdout=None, stderr=None) + if proc.returncode != 0: + logging.error("Failed to mount {d} in {mnt}") + return + for param, value in self.params: + with open(os.path.join(mnt, param), 'w') as f: + f.write(value + '\n') + argv = ['umount', d] + logging.debug(" ".join(argv)) + subprocess.run(argv, + stdin=subprocess.DEVNULL, + stdout=None, stderr=None) + +class Rpi3(RpiInitParams, SdFusingTarget): + long_name = "Raspberry Pi 3" + part_table = [ + {"size": 64, "fstype": "vfat", "name": "boot", "start": 4, "ptype": "0xe", "bootable": True}, + {"size": 3072, "fstype": "ext4", "name": "rootfs"}, + {"size": 1344, "fstype": "ext4", "name": "system-data"}, + {"size": None, "ptype": "5", "name": "extended", "start": 4484}, + {"size": None, "fstype": "ext4", "name": "user"}, + {"size": 32, "fstype": "ext4", "name": "modules"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery"}, + {"size": 8, "fstype": "ext4", "name": "inform"}, + {"size": 256, "fstype": "ext4", "name": "hal"}, + {"size": 125, "fstype": "ext4", "name": "reserved2"}, + ] + binaries = { + "boot.img": 1, + "rootfs.img": 2, + "system-data.img": 3, + "user.img": 5, + "modules.img": 6, + "ramdisk.img": 7, + "ramdisk-recovery.img": 8, + "hal.img": 10, + } + params = (('reboot-param.bin', ''),) + + def __init__(self, device, args): + self.reserved_space = 12 + self.user_partition = 4 + super().__init__(device, "dos") + +class Rpi4Super(RpiInitParams, SdFusingTargetAB): + long_name = "Raspberry Pi 4 w/ super partition" + part_table = [ + {"size": 64, "fstype": "vfat", "name": "boot_a","start": 4, + "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"}, + {"size": 6656, "fstype": "ext4", "name": "super"}, + {"size": 1344, "fstype": "ext4", "name": "system-data"}, + {"size": 36, "fstype": "raw", "name": "none"}, + {"size": None, "fstype": "ext4", "name": "user"}, + {"size": 32, "fstype": "ext4", "name": "module_a"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk_a"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a"}, + {"size": 8, "fstype": "ext4", "name": "inform"}, + {"size": 64, "fstype": "vfat", "name": "boot_b", + "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"}, + {"size": 32, "fstype": "ext4", "name": "module_b"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk_b"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b"}, + {"size": 4, "fstype": "ext4", "name": "reserved0"}, + {"size": 64, "fstype": "ext4", "name": "reserved1"}, + {"size": 125, "fstype": "ext4", "name": "reserved2"} + ] + binaries = { + "boot.img": 1, + "super.img": 2, + "system-data.img": 3, + "user.img": 5, + "modules.img": 6, + "ramdisk.img": 7, + "ramdisk-recovery.img": 8, + } + binaries_b = { + "boot.img": 10, + "modules.img": 11, + "ramdisk.img": 12, + "ramdisk-recovery.img": 13, + } + params = (('reboot-param.bin', 'norm'), + ('reboot-param.info', 'norm'), + ('partition-ab.info', 'a'), + ('partition-ab-cloned.info', '1'), + ('upgrade-status.info', '0'), + ('partition-a-status.info', 'ok'), + ('partition-b-status.info', 'ok')) + + def __init__(self, device, args): + self.reserved_space = 8 + self.user_partition = 4 + self.update = args.update + super().__init__(device, "gpt") + self.with_super = True + self.super_alignment = 1048576 + +class Rpi4(RpiInitParams, SdFusingTargetAB): + long_name = "Raspberry Pi 4" + part_table = [ + {"size": 64, "fstype": "vfat", "name": "boot_a", "start": 4, + "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"}, + {"size": 3072, "fstype": "ext4", "name": "rootfs_a"}, + {"size": 1344, "fstype": "ext4", "name": "system-data"}, + {"size": 36, "fstype": "raw", "name": "none"}, + {"size": None, "fstype": "ext4", "name": "user"}, + {"size": 32, "fstype": "ext4", "name": "module_a"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk_a"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a"}, + {"size": 8, "fstype": "ext4", "name": "inform"}, + {"size": 256, "fstype": "ext4", "name": "hal_a"}, + {"size": 64, "fstype": "vfat", "name": "boot_b", + "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"}, + {"size": 3072, "fstype": "ext4", "name": "rootfs_b"}, + {"size": 32, "fstype": "ext4", "name": "module_b"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk_b"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b"}, + {"size": 256, "fstype": "ext4", "name": "hal_b"}, + {"size": 4, "fstype": "ext4", "name": "param"}, + {"size": 64, "fstype": "ext4", "name": "reserved1"}, + {"size": 125, "fstype": "ext4", "name": "reserved2"}, + ] + binaries = { + "boot.img": 1, + "rootfs.img": 2, + "system-data.img": 3, + "user.img": 5, + "modules.img": 6, + "ramdisk.img": 7, + "ramdisk-recovery.img": 8, + "hal.img": 10, + } + binaries_b = { + "boot.img": 11, + "rootfs.img": 12, + "modules.img": 13, + "ramdisk.img": 14, + "ramdisk-recovery.img": 15, + "hal.img": 16, + } + params = (('reboot-param.bin', 'norm'), + ('reboot-param.info', 'norm'), + ('partition-ab.info', 'a'), + ('partition-ab-cloned.info', '1'), + ('upgrade-status.info', '0'), + ('partition-a-status.info', 'ok'), + ('partition-b-status.info', 'ok')) + + def __init__(self, device, args): + self.reserved_space = 5 + self.user_partition = 4 + self.update = args.update + super().__init__(device, "gpt") + +class RV64(SdFusingTarget): + long_name = "QEMU RISC-V 64-bit" + part_table = [ + {"size": 2, "fstype": "raw", "name": "SPL", "start": 4, + "ptype": "2E54B353-1271-4842-806F-E436D6AF6985"}, + {"size": 4, "fstype": "raw", "name": "u-boot", + "ptype": "5B193300-FC78-40CD-8002-E86C45580B47"}, + {"size": 292, "fstype": "vfat", "name": "boot_a", + "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"}, + {"size": 36, "fstype": "raw", "name": "none"}, + {"size": 3072, "fstype": "ext4", "name": "rootfs_a"}, + {"size": 1344, "fstype": "ext4", "name": "system-data"}, + {"size": None, "fstype": "ext4", "name": "user"}, + {"size": 32, "fstype": "ext4", "name": "module_a"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk_a"}, + {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a"}, + {"size": 8, "fstype": "raw", "name": "inform"}, + {"size": 256, "fstype": "ext4", "name": "hal_a"}, + {"size": 4, "fstype": "raw", "name": "reserved0"}, + {"size": 64, "fstype": "raw", "name": "reserved1"}, + {"size": 125, "fstype": "raw", "name": "reserved2"}, + ] + binaries = { + "u-boot-spl.bin.normal.out": 1, + "u-boot.img": 2, + "u-boot.itb": 2, + "boot.img": 3, + "rootfs.img": 5, + "system-data.img": 6, + "user.img": 7, + "modules.img": 8, + "ramdisk.img": 9, + "ramdisk-recovery.img": 10, + "hal.img": 12, + } + + def __init__(self, device, args): + self.user_partition = 6 + self.reserved_space = 5 + super().__init__(device, 'gpt') + +class VF2(RV64): + long_name = "VisionFive2" + +TARGETS = { + 'rpi3': Rpi3, + 'rpi4': Rpi4, + 'rpi4s': Rpi4Super, + 'vf2': VF2, + 'rv64': RV64 +} + +def device_size(device): + argv = ["sfdisk", "-s", device] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv, + stdout=subprocess.PIPE) + size = int(proc.stdout.decode('utf-8').strip()) >> 10 + logging.debug(f"{device} size {size}MiB") + return size + +def check_sfdisk(): + proc = subprocess.run(['sfdisk', '-v'], + stdout=subprocess.PIPE) + version = proc.stdout.decode('utf-8').strip() + logging.debug(f"Found {version}") + major, minor = [int(x) for x in re.findall('[0-9]+', version)][0:2] + support_delete = False + + if major < 2 or major == 2 and minor < 26: + log.error(f"Your sfdisk {major}.{minor}.{patch} is too old.") + return False,False + elif major == 2 and minor >= 28: + support_delete = True + + return True, support_delete + +def mkpart(args, target): + global Device + new, support_delete = check_sfdisk() + + if not new: + logging.error('sfdisk too old') + sys.exit(1) + + with open('/proc/self/mounts') as mounts: + device_kname = '/dev/' + get_device_kname(Device) + device_re = re.compile(device_kname + '[^ ]*') + logging.debug(f"Checking for mounted partitions on {device_kname}") + for m in mounts: + match = device_re.match(m) + if match: + logging.warning('Found mounted device: ' + match[0]) + argv = ['umount', match[0]] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv) + if proc.returncode != 0: + logging.error(f"Failed to unmount {match[0]}") + sys.exit(1) + + if support_delete: + logging.info("Removing old partitions") + argv = ['sfdisk', '--delete', Device] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv) + if proc.returncode != 0: + logging.error(f"Failed to remove the old partitions from {Device}") + else: + logging.info("Removing old partition table") + argv = ['dd', 'if=/dev/zero', 'of=' + Device, + 'bs=512', 'count=32', 'conv=notrunc'] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv) + if proc.returncode != 0: + logging.error(f"Failed to clear the old partition table on {Device}") + sys.exit(1) + + logging.debug("New partition table:\n" + str(target.label)) + argv = ['sfdisk', '--wipe-partitions', 'always', Device] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv, + stdout=None, + stderr=None, + input=str(target.label).encode()) + if proc.returncode != 0: + logging.error(f"Failed to create partition a new table on {Device}") + logging.error(f"New partition table:\n" + str(target.label)) + sys.exit(1) + + for i, part in enumerate(target.part_table): + d = "/dev/" + get_partition_device(target.device, i+1) + if not 'fstype' in part: + logging.debug(f"Filesystem not defined for {d}, skipping") + continue + logging.debug(f"Formatting {d} as {part['fstype']}") + if part['fstype'] == 'vfat': + argv = ['mkfs.vfat', '-F', '16', '-n', part['name'], d] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv, + stdin=subprocess.DEVNULL, + stdout=None, stderr=None) + if proc.returncode != 0: + log.error(f"Failed to create FAT filesystem on {d}") + sys.exit(1) + elif part['fstype'] == 'ext4': + argv = ['mkfs.ext4', '-q', '-L', part['name'], d] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv, + stdin=subprocess.DEVNULL, + stdout=None, stderr=None) + if proc.returncode != 0: + log.error(f"Failed to create ext4 filesystem on {d}") + sys.exit(1) + elif part['fstype'] == 'raw': + pass + target.initialize_parameters() + +def check_args(args): + global Format + global Yes + + logging.info(f"Device: {args.device}") + + if args.binaries and len(args.binaries) > 0: + logging.info("Fusing binar{}: {}".format("y" if len(args.binaries) == 1 else "ies", + ", ".join(args.binaries))) + + if args.YES: + Yes = True + + if args.create: + Format = True + Yes = True + + if args.format: + if Yes: + Format = True + else: + response = input(f"{args.device} will be formatted. Continue? [y/N] ") + if response.lower() in ('y', 'yes'): + Format = True + else: + Format = False + +def check_device(args): + global Format + global Device + Device = args.device + + if args.create: + if os.path.exists(Device): + logging.error(f"Failed to create '{Device}', the file alread exists") + sys.exit(1) + else: + argv = ["dd", "if=/dev/zero", f"of={Device}", + "conv=sparse", "bs=1M", f"count={args.size}"] + logging.debug(" ".join(argv)) + rc = subprocess.run(argv) + if rc.returncode != 0: + logging.error("Failed to create the backing file") + sys.exit(1) + + if os.path.isfile(Device): + global File + File = Device + + argv = ["losetup", "--show", "--partscan", "--find", f"{File}"] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv, + stdout=subprocess.PIPE) + Device = proc.stdout.decode('utf-8').strip() + if proc.returncode != 0: + logging.error(f"Failed to attach {File} to a loopback device") + sys.exit(1) + logging.debug(f"Loop device found: {Device}") + atexit.register(lambda: subprocess.run(["losetup", "-d", Device])) + + try: + s = os.stat(Device) + if not stat.S_ISBLK(s.st_mode): + raise TypeError + except FileNotFoundError: + logging.error(f"No such device: {Device}") + sys.exit(1) + except TypeError: + logging.error(f"{Device} is not a block device") + sys.exit(1) + +def check_partition_format(args, target): + global Format + global Device + + if not Format: + logging.info(f"Skip formatting of {Device}".format(Device)) + return + logging.info(f"Start formatting of {Device}") + mkpart(args, target) + logging.info(f"{Device} formatted") + +def check_ddversion(): + proc = subprocess.run(["dd", "--version"], + stdout=subprocess.PIPE) + version = proc.stdout.decode('utf-8').split('\n')[0].strip() + logging.debug(f"Found {version}") + major, minor = (int(x) for x in re.findall('[0-9]+', version)) + + if major < 8 or major == 8 and minor < 24: + return False + + return True + +def get_partition_device(device, idx): + argv = ['lsblk', device, '-o', 'TYPE,KNAME'] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv, + stdout=subprocess.PIPE) + if proc.returncode != 0: + logging.error("lsblk has failed") + return None + part_re = re.compile(f"^part\s+(.*[^0-9]{idx})$") + for l in proc.stdout.decode('utf-8').splitlines(): + match = part_re.match(l) + if match: + return match[1] + return None + +def get_device_kname(device): + argv = ['lsblk', device, '-o', 'TYPE,KNAME'] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv, + stdout=subprocess.PIPE) + for l in proc.stdout.decode('utf-8').splitlines(): + match = re.search(f"^(disk|loop)\s+(.*)", l) + if match: + return match[2] + return None + +def do_fuse_file(f, name, target): + idx = target.get_partition_index(name) + if idx is None: + logging.info(f"No partition defined for {name}, skipping.") + return + pdevice = "/dev/" + get_partition_device(Device, idx) + argv = ['dd', 'bs=4M', + 'oflag=direct', + 'iflag=fullblock', + 'conv=nocreat', + f"of={pdevice}"] + logging.debug(" ".join(argv)) + proc_dd = subprocess.Popen(argv, + bufsize=(4 << 20), + stdin=subprocess.PIPE, + stdout=None, stderr=None) + logging.info(f"Writing {name} to {pdevice}") + buf = f.read(4 << 20) + while len(buf) > 0: + #TODO: progress + proc_dd.stdin.write(buf) + buf = f.read(4 << 20) + proc_dd.communicate() + logging.info("Done") + #TODO: verification + +#TODO: functions with the target argument should probably +# be part of some class + +def get_aligned_size(size, target): + return target.super_alignment*int(1+(size-1)/target.super_alignment) + +def do_fuse_image_super(tmpd, target): + metadata_slots = 2 + metadata_size = 65536 + metadata_aligned_size = get_aligned_size(metadata_size, target) + + hal_path = os.path.join(tmpd, 'hal.img') + rootfs_path = os.path.join(tmpd, 'rootfs.img') + super_path = os.path.join(tmpd, 'super.img') + + try: + hal_size = os.stat(hal_path).st_size + rootfs_size = os.stat(rootfs_path).st_size + except FileNotFoundError as e: + fn = os.path.split(e.filename)[-1] + logging.warning(f"{fn} is missing, skipping super partition image") + return + + hal_aligned_size = get_aligned_size(hal_size, target) + rootfs_aligned_size = get_aligned_size(rootfs_size, target) + group_size = hal_aligned_size + rootfs_aligned_size + super_size = metadata_aligned_size + 2 * group_size + + argv = ["lpmake", "-F", + f"-o={super_path}", + f"--device-size={super_size}", + f"--metadata-size={metadata_size}", + f"--metadata-slots={metadata_slots}", + "-g", f"tizen_a:{group_size}", + "-p", f"rootfs_a:none:{rootfs_aligned_size}:tizen_a", + "-p", f"hal_a:none:{hal_aligned_size}:tizen_a", + "-g", f"tizen_b:{group_size}", + "-p", f"rootfs_b:none:{rootfs_aligned_size}:tizen_b", + "-p", f"hal_b:none:{hal_aligned_size}:tizen_b", + "-i", f"rootfs_a={rootfs_path}", + "-i", f"rootfs_b={rootfs_path}", + "-i", f"hal_a={hal_path}", + "-i", f"hal_b={hal_path}"] + logging.debug(" ".join(argv)) + proc = subprocess.run(argv, + stdin=subprocess.DEVNULL, + stdout=None, stderr=None) + + if proc.returncode != 0: + logging.error("Failed to create super.img") + do_fuse_image(super_path, target) + +def do_fuse_image_tarball(tarball, tmpd, target): + with tarfile.open(tarball) as tf: + for entry in tf: + if target.with_super: + if entry.name in('hal.img', 'rootfs.img'): + tf.extract(entry, path=tmpd) + continue + f = tf.extractfile(entry) + do_fuse_file(f, entry.name, target) + +def do_fuse_image(img, target): + with open(img, 'rb') as f: + do_fuse_file(f, os.path.basename(img), target) + +def fuse_image(args, target): + global Yes + + if args.binaries is None or len(args.binaries) == 0: + return + + if not Yes and not Format: + print(f"The following images will be written to {args.device} and the " + "existing data will be lost.\n") + for b in args.binaries: + print(" " + b) + response = input("\nContinue? [y/N] ") + if not response.lower() in ('y', 'yes'): + return + + with tempfile.TemporaryDirectory() as tmpd: + for b in args.binaries: + if re.search('\.(tar|tar\.gz|tgz)$', b): + do_fuse_image_tarball(b, tmpd, target) + else: + fn = os.path.split(b)[-1] + if target.with_super and fn in ('rootfs.img', 'hal.img'): + shutil.copy(b, os.path.join(tmpd, fn)) + else: + do_fuse_image(b, target) + + if target.with_super: + do_fuse_image_super(tmpd, target) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="For {}, version {}".format( + ", ".join([v.long_name for k,v in TARGETS.items()]), + __version__ + )) + parser.add_argument("-b", "--binary", action="extend", dest="binaries", + nargs='+', + help="binary to flash, may be used multiple times") + parser.add_argument("--create", action="store_true", + help="create the backing file and format the loopback device") + parser.add_argument("--debug", action="store_const", const="debug", + default="warning", dest="log_level", + help="set log level to DEBUG") + parser.add_argument("-d", "--device", + help="device node or loopback backing file") + parser.add_argument("--format", action="store_true", + help="create new partition table on the target device") + parser.add_argument("--log-level", dest="log_level", default="warning", + help="Verbosity, possible values: debug, info, warning, " + "error, critical (default: warning)") + parser.add_argument("--size", type=int, default=8192, + help="size of the backing file to create (in MiB)") + parser.add_argument("-t", "--target", required=True, + help="Target device model. Use `--target list`" + " to show supported devices.") + parser.add_argument("--update", choices=['a', 'b'], default=None, + help="Choose partition set to update: a or b.") + parser.add_argument("--version", action="version", + version=f"%(prog)s {__version__}") + parser.add_argument("--YES", action="store_true", + help="agree to destroy data on the DEVICE") + args = parser.parse_args() + + if args.target == 'list': + print("\nSupported devices:\n") + for k,v in TARGETS.items(): + print(f" {k:6} {v.long_name}") + sys.exit(0) + + if args.device is None: + parser.error('-d/--device argument is required for normal operation') + + conh = ColorStreamHandler(format='%(asctime)s.%(msecs)d %(debuginfo)s%(levelname)-8s %(message)s', + cformat='%(asctime)s.%(msecs)d %(debuginfo)s%(levelcolor)s%(message)s', + datefmt='%Y-%m-%dT%H:%M:%S') + log_handlers = [conh] + logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s', + handlers=log_handlers, + level=args.log_level.upper()) + + logging.debug(" ".join(sys.argv)) + check_args(args) + check_device(args) + + target = TARGETS[args.target](Device, args) + + check_partition_format(args, target) + fuse_image(args, target) + subprocess.run(['sync'], + stdin=subprocess.DEVNULL, + stdout=None, stderr=None )