# Copyright 2023 Oliver Smith # SPDX-License-Identifier: GPL-3.0-or-later from pathlib import Path from typing import Optional from pmb.helpers import logging import os import time import pmb.chroot import pmb.config from pmb.core.types import PmbArgs import pmb.install.losetup from pmb.core import Chroot # FIXME (#2324): this function drops disk to a string because it's easier # to manipulate, this is probably bad. def partitions_mount(args: PmbArgs, layout, disk: Optional[Path]): """ Mount blockdevices of partitions inside native chroot :param layout: partition layout from get_partition_layout() :param disk: path to disk block device (e.g. /dev/mmcblk0) or None """ if not disk: img_path = Path("/home/pmos/rootfs") / f"{args.device}.img" disk = pmb.install.losetup.device_by_back_file(args, img_path) logging.info(f"Mounting partitions of {disk} inside the chroot") tries = 20 # Devices ending with a number have a "p" before the partition number, # /dev/sda1 has no "p", but /dev/mmcblk0p1 has. See add_partition() in # block/partitions/core.c of linux.git. partition_prefix = str(disk) if str.isdigit(disk.name[-1:]): partition_prefix = f"{disk}p" found = False for i in range(tries): if os.path.exists(f"{partition_prefix}1"): found = True break logging.debug(f"NOTE: ({i + 1}/{tries}) failed to find the install " "partition. Retrying...") time.sleep(0.1) if not found: raise RuntimeError(f"Unable to find the first partition of {disk}, " f"expected it to be at {partition_prefix}1!") partitions = [layout["boot"], layout["root"]] if layout["kernel"]: partitions += [layout["kernel"]] for i in partitions: source = Path(f"{partition_prefix}{i}") target = Chroot.native() / "dev" / f"installp{i}" pmb.helpers.mount.bind_file(source, target) def partition(args: PmbArgs, layout, size_boot, size_reserve): """ Partition /dev/install and create /dev/install{p1,p2,p3}: * /dev/installp1: boot * /dev/installp2: root (or reserved space) * /dev/installp3: (root, if reserved space > 0) When adjusting this function, make sure to also adjust ondev-prepare-internal-storage.sh in postmarketos-ondev.git! :param layout: partition layout from get_partition_layout() :param size_boot: size of the boot partition in MiB :param size_reserve: empty partition between root and boot in MiB (pma#463) """ # Convert to MB and print info mb_boot = f"{round(size_boot)}M" mb_reserved = f"{round(size_reserve)}M" mb_root_start = f"{round(size_boot) + round(size_reserve)}M" logging.info(f"(native) partition /dev/install (boot: {mb_boot}," f" reserved: {mb_reserved}, root: the rest)") filesystem = args.deviceinfo["boot_filesystem"] or "ext2" # Actual partitioning with 'parted'. Using check=False, because parted # sometimes "fails to inform the kernel". In case it really failed with # partitioning, the follow-up mounting/formatting will not work, so it # will stop there (see #463). boot_part_start = args.deviceinfo["boot_part_start"] or "2048" partition_type = args.deviceinfo["partition_type"] or "msdos" commands = [ ["mktable", partition_type], ["mkpart", "primary", filesystem, boot_part_start + 's', mb_boot], ] if size_reserve: mb_reserved_end = f"{round(size_reserve + size_boot)}M" commands += [["mkpart", "primary", mb_boot, mb_reserved_end]] commands += [ ["mkpart", "primary", mb_root_start, "100%"], ["set", str(layout["boot"]), "boot", "on"] ] # Not strictly necessary if the device doesn't use EFI boot, but marking # it as an ESP will cover all situations where the device does use EFI # boot. Marking it as ESP is helpful for EFI fw when it's looking for EFI # system partitions. It's assumed that setting this bit is unlikely to # cause problems for other situations, like when using Legacy BIOS boot # or u-boot. if partition_type.lower() == "gpt": commands += [["set", str(layout["boot"]), "esp", "on"]] for command in commands: pmb.chroot.root(args, ["parted", "-s", "/dev/install"] + command, check=False) def partition_cgpt(args: PmbArgs, layout, size_boot, size_reserve): """ This function does similar functionality to partition(), but this one is for ChromeOS devices which use special GPT. :param layout: partition layout from get_partition_layout() :param size_boot: size of the boot partition in MiB :param size_reserve: empty partition between root and boot in MiB (pma#463) """ pmb.chroot.apk.install(args, ["cgpt"], build=False) cgpt = { 'kpart_start': args.deviceinfo["cgpt_kpart_start"], 'kpart_size': args.deviceinfo["cgpt_kpart_size"], } # Convert to MB and print info mb_boot = f"{round(size_boot)}M" mb_reserved = f"{round(size_reserve)}M" logging.info(f"(native) partition /dev/install (boot: {mb_boot}," f" reserved: {mb_reserved}, root: the rest)") boot_part_start = str(int(cgpt['kpart_start']) + int(cgpt['kpart_size'])) # Convert to sectors s_boot = str(int(size_boot * 1024 * 1024 / 512)) s_root_start = str(int( int(boot_part_start) + int(s_boot) + size_reserve * 1024 * 1024 / 512 )) commands = [ ["parted", "-s", "/dev/install", "mktable", "gpt"], ["cgpt", "create", "/dev/install"], [ "cgpt", "add", "-i", str(layout["kernel"]), "-t", "kernel", "-b", cgpt['kpart_start'], "-s", cgpt['kpart_size'], "-l", "pmOS_kernel", "-S", "1", # Successful flag "-T", "5", # Tries flag "-P", "10", # Priority flag "/dev/install" ], [ "cgpt", "add", # pmOS_boot is second partition, the first will be ChromeOS kernel # partition "-i", str(layout["boot"]), # Partition number "-t", "efi", # Mark this partition as bootable for u-boot "-b", boot_part_start, "-s", s_boot, "-l", "pmOS_boot", "/dev/install" ], ] dev_size = pmb.chroot.root( args, ["blockdev", "--getsz", "/dev/install"], output_return=True) # 33: Sec GPT table (32) + Sec GPT header (1) root_size = str(int(dev_size) - int(s_root_start) - 33) commands += [ [ "cgpt", "add", "-i", str(layout["root"]), "-t", "data", "-b", s_root_start, "-s", root_size, "-l", "pmOS_root", "/dev/install" ], ["partx", "-a", "/dev/install"] ] for command in commands: pmb.chroot.root(args, command, check=False)