1
0
Fork 1
mirror of https://gitlab.postmarketos.org/postmarketOS/pmbootstrap.git synced 2025-07-23 20:45:08 +03:00
pmbootstrap/pmb/install/blockdevice.py
Casey Connolly 9f8edf539d treewide: split chroots from workdir
Introduce a new "cache" subdirectory in the pmbootstrap workdir, all the
cache and config bits go in here, anything that needs to be accessible
from inside a chroot. The whole dir is then bind-mounted into the chroot
as /cache with appropriate symlinks.

This dir is in the config as config.cache.

In addition, all the cache_* and other config dirs are renamed to
be closer to the names of the equivalent dirs in the chroot (e.g.
abuild-config) and to avoid redundant naming since they are now under a
"cache" dir.

Signed-off-by: Casey Connolly <kcxt@postmarketos.org>
2025-07-11 19:36:23 +02:00

150 lines
6 KiB
Python

# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
from pmb.helpers import logging
import os
from pathlib import Path
from pmb.types import PmbArgs, PartitionLayout
import pmb.helpers.mount
import pmb.helpers.cli
import pmb.helpers.run
import pmb.config
from pmb.core import Chroot
from pmb.core.context import get_context
def previous_install(path: Path) -> bool:
"""
Search the disk for possible existence of a previous installation of
pmOS. We temporarily mount the possible pmOS_boot partition as
/dev/diskp1 inside the native chroot to check the label from there.
:param path: path to disk block device (e.g. /dev/mmcblk0)
"""
label = ""
for blockdevice_outside in [path.with_stem(f"{path.name}1"), path.with_stem(f"{path.name}p1")]:
if not os.path.exists(blockdevice_outside):
continue
blockdevice_inside = "/dev/diskp1"
pmb.helpers.mount.bind_file(blockdevice_outside, Chroot.native() / blockdevice_inside)
try:
label = pmb.chroot.root(
["blkid", "-s", "LABEL", "-o", "value", blockdevice_inside], output_return=True
)
except RuntimeError:
logging.info(
"WARNING: Could not get block device label,"
" assume no previous installation on that partition"
)
pmb.helpers.run.root(["umount", Chroot.native() / blockdevice_inside])
return "pmOS_boot" in label
def mount_disk(path: Path) -> None:
"""
:param path: path to disk block device (e.g. /dev/mmcblk0)
"""
# Sanity checks
if not os.path.exists(path):
raise RuntimeError(f"The disk block device does not exist: {path}")
for path_mount in path.parent.glob(f"{path.name}*"):
if pmb.helpers.mount.ismount(path_mount):
raise RuntimeError(f"{path_mount} is mounted! Will not attempt to format this!")
logging.info(f"(native) mount /dev/install (host: {path})")
pmb.helpers.mount.bind_file(path, Chroot.native() / "dev/install")
if previous_install(path):
if not pmb.helpers.cli.confirm(
"WARNING: This device has a previous installation of pmOS. CONTINUE?"
):
raise RuntimeError("Aborted.")
else:
if not pmb.helpers.cli.confirm(f"EVERYTHING ON {path} WILL BE ERASED! CONTINUE?"):
raise RuntimeError("Aborted.")
def create_and_mount_image(
args: PmbArgs,
layout: PartitionLayout,
split: bool = False,
) -> None:
"""
Create a new image file, and mount it as /dev/install.
:param size_boot: size of the boot partition in bytes
:param size_root: size of the root partition in bytes
:param split: create separate images for boot and root partitions
"""
# Short variables for paths
chroot = Chroot.native()
config = get_context().config
img_path_prefix = chroot / "home/pmos/rootfs"
img_path_full = img_path_prefix / f"{config.device}.img"
img_path_boot = img_path_prefix / f"{config.device}-boot.img"
img_path_root = img_path_prefix / f"{config.device}-root.img"
# Umount and delete existing images
for img_path in [img_path_full, img_path_boot, img_path_root]:
outside = chroot / img_path
if os.path.exists(outside):
pmb.helpers.mount.umount_all(chroot / "mnt")
pmb.chroot.root(["rm", img_path])
# Make sure there is enough free space
size_full = round(layout.boot.size + layout.root.size)
disk_data = os.statvfs(get_context().config.cache)
free = disk_data.f_bsize * disk_data.f_bavail
if size_full > free:
raise RuntimeError(
f"Not enough free space to create rootfs image! (free: {round(free / (1024**2))}M, required: {round(size_full / (1024**2))}M)"
)
# Create empty image files
rootfs_dir = chroot / "home/pmos/rootfs"
rootfs_dir.mkdir(exist_ok=True)
os.chown(rootfs_dir, int(pmb.config.chroot_uid_user), int(pmb.config.chroot_uid_user))
if split:
images = {img_path_boot: layout.boot.size, img_path_root: layout.root.size}
else:
# Account for the partition table
size_full += pmb.parse.deviceinfo().boot_part_start * pmb.config.block_size
# Add 4 sectors for alignment and the backup header
size_full += pmb.config.block_size * 4
# Round to sector size
size_full = int((size_full + 512) / pmb.config.block_size) * pmb.config.block_size
images = {img_path_full: size_full}
for img_path, image_size in images.items():
img_path.unlink(missing_ok=True)
logging.info(f"(native) create {img_path.name} ({round(image_size / (1024**2))}M)")
pmb.helpers.run.user(["truncate", "-s", f"{image_size}", f"{img_path}"])
os.chown(img_path, int(pmb.config.chroot_uid_user), int(pmb.config.chroot_uid_user))
# pmb.helpers.run.root(["dd", "if=/dev/zero", f"of={img_path}", f"bs={image_size}", "count=1"])
# Mount to /dev/install
if not split:
layout.boot.path = layout.root.path = layout.path = "/dev/install"
mount_image_paths = {img_path_full: layout.path}
else:
layout.boot.path = "/dev/installp1"
layout.root.path = "/dev/installp2"
mount_image_paths = {img_path_boot: layout.boot.path, img_path_root: layout.root.path}
for img_path, mount_point in mount_image_paths.items():
# logging.info(f"(native) mount {mount_point} ({img_path.name})")
pmb.helpers.mount.bind_file(img_path, chroot / mount_point)
def create(args: PmbArgs, layout: PartitionLayout, split: bool, disk: Path | None) -> None:
"""
Create /dev/install (the "install blockdevice").
:param size_boot: size of the boot partition in MiB
:param size_root: size of the root partition in MiB
:param split: create separate images for boot and root partitions
:param disk: path to disk block device (e.g. /dev/mmcblk0) or None
"""
pmb.helpers.mount.umount_all(Chroot.native() / "dev/install")
if disk:
mount_disk(disk)
else:
create_and_mount_image(args, layout, split)