image/flash: implement fastboot rootfs image flashing, add --no-shrink

Use fastboot by default instead of jumpdrive, respecting the deviceinfo
This commit is contained in:
InsanePrawn 2023-01-06 04:38:05 +01:00
parent 8a266f9149
commit 69b7ea9db2

View file

@ -4,7 +4,7 @@ import click
from typing import Optional from typing import Optional
from constants import FLASH_PARTS, LOCATIONS from constants import FLASH_PARTS, LOCATIONS, FASTBOOT, JUMPDRIVE
from exec.cmd import run_root_cmd from exec.cmd import run_root_cmd
from exec.file import get_temp_dir from exec.file import get_temp_dir
from devices.device import get_profile_device from devices.device import get_profile_device
@ -13,50 +13,30 @@ from flavours.cli import profile_option
from wrapper import enforce_wrap from wrapper import enforce_wrap
from .fastboot import fastboot_flash from .fastboot import fastboot_flash
from .image import dd_image, partprobe, shrink_fs, losetup_rootfs_image, losetup_destroy, dump_aboot, dump_lk2nd, dump_qhypstub, get_image_name, get_image_path from .image import dd_image, dump_aboot, dump_lk2nd, dump_qhypstub, get_image_path, losetup_destroy, losetup_rootfs_image, partprobe, shrink_fs
ABOOT = FLASH_PARTS['ABOOT'] ABOOT = FLASH_PARTS['ABOOT']
LK2ND = FLASH_PARTS['LK2ND'] LK2ND = FLASH_PARTS['LK2ND']
QHYPSTUB = FLASH_PARTS['QHYPSTUB'] QHYPSTUB = FLASH_PARTS['QHYPSTUB']
ROOTFS = FLASH_PARTS['ROOTFS'] ROOTFS = FLASH_PARTS['ROOTFS']
DD = 'dd'
@click.command(name='flash') FLASH_METHODS = [FASTBOOT, JUMPDRIVE, DD]
@profile_option
@click.argument('what', type=click.Choice(list(FLASH_PARTS.values())))
@click.argument('location', type=str, required=False)
def cmd_flash(what: str, location: str, profile: Optional[str] = None):
"""Flash a partition onto a device. `location` takes either a path to a block device or one of emmc, sdcard"""
enforce_wrap()
device = get_profile_device(profile)
flavour = get_profile_flavour(profile).name
device_image_name = get_image_name(device, flavour)
device_image_path = get_image_path(device, flavour)
deviceinfo = device.parse_deviceinfo()
sector_size = deviceinfo.flash_pagesize
if not sector_size:
raise Exception(f"Device {device.name} has no flash_pagesize specified")
if what not in FLASH_PARTS.values(): def find_jumpdrive(location: str) -> str:
raise Exception(f'Unknown what "{what}", must be one of {", ".join(FLASH_PARTS.values())}')
if what == ROOTFS:
if location is None:
raise Exception(f'You need to specify a location to flash {what} to')
path = ''
if location.startswith("/dev/"):
path = location
else:
if location not in LOCATIONS: if location not in LOCATIONS:
raise Exception(f'Invalid location {location}. Choose one of {", ".join(LOCATIONS)}') raise Exception(f'Invalid location {location}. Choose one of {", ".join(LOCATIONS)}')
dir = '/dev/disk/by-id' dir = '/dev/disk/by-id'
for file in os.listdir(dir): for file in os.listdir(dir):
sanitized_file = file.replace('-', '').replace('_', '').lower() sanitized_file = file.replace('-', '').replace('_', '').lower()
if f'jumpdrive{location.split("-")[0]}' in sanitized_file: if f'jumpdrive{location.split("-")[0]}' in sanitized_file:
path = os.path.realpath(os.path.join(dir, file)) return os.path.realpath(os.path.join(dir, file))
raise Exception('Unable to discover Jumpdrive')
def test_blockdev(path: str):
partprobe(path) partprobe(path)
result = run_root_cmd(['lsblk', path, '-o', 'SIZE'], capture_output=True) result = run_root_cmd(['lsblk', path, '-o', 'SIZE'], capture_output=True)
if result.returncode != 0: if result.returncode != 0:
@ -64,24 +44,78 @@ def cmd_flash(what: str, location: str, profile: Optional[str] = None):
if result.stdout == b'SIZE\n 0B\n': if result.stdout == b'SIZE\n 0B\n':
raise Exception(f'Disk {path} has a size of 0B. That probably means it is not available (e.g. no' raise Exception(f'Disk {path} has a size of 0B. That probably means it is not available (e.g. no'
'microSD inserted or no microSD card slot installed in the device) or corrupt or defect') 'microSD inserted or no microSD card slot installed in the device) or corrupt or defect')
if path == '':
raise Exception('Unable to discover Jumpdrive')
def prepare_minimal_image(source_path: str, sector_size: int) -> str:
minimal_image_dir = get_temp_dir(register_cleanup=True) minimal_image_dir = get_temp_dir(register_cleanup=True)
minimal_image_path = os.path.join(minimal_image_dir, f'minimal-{device_image_name}') minimal_image_path = os.path.join(minimal_image_dir, f'minimal-{os.path.basename(source_path)}')
shutil.copyfile(device_image_path, minimal_image_path) shutil.copyfile(source_path, minimal_image_path)
loop_device = losetup_rootfs_image(minimal_image_path, sector_size) loop_device = losetup_rootfs_image(minimal_image_path, sector_size)
partprobe(loop_device) partprobe(loop_device)
shrink_fs(loop_device, minimal_image_path, sector_size) shrink_fs(loop_device, minimal_image_path, sector_size)
losetup_destroy(loop_device) losetup_destroy(loop_device)
return minimal_image_path
result = dd_image(input=minimal_image_path, output=path)
if result.returncode != 0: @click.command(name='flash')
raise Exception(f'Failed to flash {minimal_image_path} to {path}') @profile_option
@click.option('-m', '--method', type=click.Choice(FLASH_METHODS))
@click.option('--split-size', help='Chunk size when splitting the image into sparse files via fastboot')
@click.option('--shrink/--no-shrink', is_flag=True, default=True, help="Don't copy and shrink the image file to minimal size")
@click.argument('what', type=click.Choice(list(FLASH_PARTS.values())))
@click.argument('location', type=str, required=False)
def cmd_flash(
what: str,
location: str,
method: Optional[str] = None,
split_size: Optional[str] = None,
profile: Optional[str] = None,
shrink: bool = True,
):
"""Flash a partition onto a device. `location` takes either a path to a block device or one of emmc, sdcard"""
enforce_wrap()
device = get_profile_device(profile)
flavour = get_profile_flavour(profile).name
device_image_path = get_image_path(device, flavour)
deviceinfo = device.parse_deviceinfo()
sector_size = deviceinfo.flash_pagesize
method = method or deviceinfo.flash_method
if not sector_size:
raise Exception(f"Device {device.name} has no flash_pagesize specified")
if what not in FLASH_PARTS.values():
raise Exception(f'Unknown what "{what}", must be one of {", ".join(FLASH_PARTS.values())}')
if what == ROOTFS:
path = ''
if method not in FLASH_METHODS:
raise Exception(f"Flash method {method} not supported!")
if not location:
raise Exception(f'You need to specify a location to flash {what} to')
path = ''
image_path = prepare_minimal_image(device_image_path, sector_size) if shrink else device_image_path
if method == FASTBOOT:
fastboot_flash(
partition=location,
file=image_path,
sparse_size='100M', # TODO: make configurable
)
elif method in [JUMPDRIVE, DD]:
if method == DD or location.startswith("/") or (location not in LOCATIONS and os.path.exists(location)):
path = location
elif method == JUMPDRIVE:
path = find_jumpdrive(location)
test_blockdev(path)
if dd_image(input=image_path, output=path).returncode != 0:
raise Exception(f'Failed to flash {image_path} to {path}')
else: else:
raise Exception(f'Unhandled flash method "{method}" for "{what}"')
else:
if method and method != FASTBOOT:
raise Exception(f'Flashing "{what}" with method "{method}" not supported, try no parameter or "{FASTBOOT}"')
loop_device = losetup_rootfs_image(device_image_path, sector_size) loop_device = losetup_rootfs_image(device_image_path, sector_size)
if what == ABOOT: if what == ABOOT:
path = dump_aboot(f'{loop_device}p1') path = dump_aboot(f'{loop_device}p1')