chroot: allow mounting the device rootfs (MR 2252)

Add a new flag --image which can be used to mount the rootfs generated
with "pmbootstrap install".

For now this is quite limited in scope. But it's enough to allow for
building a package, updating it in the QEMU image, and then booting it.

The major "gotcha" with this is that the QEMU uses the kernel and
initramfs from the device chroot unless you run it with --efi.

Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
This commit is contained in:
Caleb Connolly 2024-06-14 07:25:49 +02:00 committed by Oliver Smith
parent d1a86fe702
commit f331b95824
No known key found for this signature in database
GPG key ID: 5AE7F5513E0885CB
8 changed files with 55 additions and 11 deletions

View file

@ -1,5 +1,6 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
from pmb.core.chroot import ChrootType
from pmb.core.pkgrepo import pkgrepo_default_path
from pmb.helpers import logging
import os
@ -9,11 +10,36 @@ import pmb.chroot.binfmt
import pmb.config
import pmb.helpers.run
import pmb.parse
import pmb.install.losetup
import pmb.helpers.mount
from pmb.core import Chroot
from pmb.core.context import get_context
def mount_chroot_image(chroot: Chroot):
"""Mount an IMAGE type chroot, to modify an existing rootfs image. This
doesn't support split images yet!"""
# Make sure everything is nicely unmounted just to be super safe
# this is definitely overkill
pmb.chroot.shutdown()
pmb.install.losetup.detach_all()
chroot_native = Chroot.native()
pmb.chroot.init(chroot_native)
loopdev = pmb.install.losetup.mount(Path("/") / Path(chroot.name).relative_to(chroot_native.path))
pmb.helpers.mount.bind_file(loopdev, chroot_native / "dev/install")
# Set up device mapper bits
pmb.chroot.root(["kpartx", "-u", "/dev/install"], chroot_native)
chroot.path.mkdir(exist_ok=True)
# # The name of the IMAGE chroot is the path to the rootfs image
pmb.helpers.run.root(["mount", "/dev/mapper/install2", chroot.path])
pmb.helpers.run.root(["mount", "/dev/mapper/install1", chroot.path / "boot"])
pmb.config.workdir.chroot_save_init(chroot)
logging.info(f"({chroot}) mounted {chroot.name}")
def create_device_nodes(chroot: Chroot):
"""
Create device nodes for null, zero, full, random, urandom in the chroot.
@ -80,6 +106,9 @@ def mount_dev_tmpfs(chroot: Chroot=Chroot.native()):
def mount(chroot: Chroot):
if chroot.type == ChrootType.IMAGE and not pmb.mount.ismount(chroot.path):
mount_chroot_image(chroot)
# Mount tmpfs as the chroot's /dev
mount_dev_tmpfs(chroot)

View file

@ -14,6 +14,7 @@ class ChrootType(enum.Enum):
BUILDROOT = "buildroot"
INSTALLER = "installer"
NATIVE = "native"
IMAGE = "image"
def __str__(self) -> str:
return self.name
@ -65,9 +66,12 @@ class Chroot:
raise ValueError(f"The native suffix can't have a name but got: "
f"'{self.__name}'")
if self.__type == ChrootType.IMAGE and not Path(self.__name).exists():
raise ValueError(f"Image file '{self.__name}' does not exist")
def __str__(self) -> str:
if len(self.__name) > 0:
if len(self.__name) > 0 and self.type != ChrootType.IMAGE:
return f"{self.__type.value}_{self.__name}"
else:
return self.__type.value

View file

@ -63,11 +63,15 @@ def _parse_flavor(device: str, autoinstall=True):
def _parse_suffix(args: PmbArgs) -> Chroot:
deviceinfo = pmb.parse.deviceinfo()
if args.image:
rootfs = Chroot.native() / f"home/pmos/rootfs/{deviceinfo.codename}.img"
return Chroot(ChrootType.IMAGE, str(rootfs))
if "rootfs" in args and args.rootfs:
return Chroot(ChrootType.ROOTFS, get_context().config.device)
elif args.buildroot:
if args.buildroot == "device":
return Chroot.buildroot(pmb.parse.deviceinfo().arch)
return Chroot.buildroot(deviceinfo.arch)
else:
return Chroot.buildroot(Arch.from_str(args.buildroot))
elif args.suffix:
@ -168,12 +172,15 @@ def chroot(args: PmbArgs):
chroot = _parse_suffix(args)
user = args.user
if (user and chroot != Chroot.native() and
not chroot.type == ChrootType.BUILDROOT):
chroot.type not in [ChrootType.BUILDROOT, ChrootType.IMAGE]):
raise RuntimeError("--user is only supported for native or"
" buildroot_* chroots.")
if args.xauth and chroot != Chroot.native():
raise RuntimeError("--xauth is only supported for native chroot.")
if chroot.type == ChrootType.IMAGE:
pmb.chroot.mount(chroot)
# apk: check minimum version, install packages
pmb.chroot.apk.check_min_version(chroot)
if args.add:

View file

@ -1,6 +1,7 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
from typing import Optional
from pmb.core.config import Config
from pmb.helpers import logging
import os
from pathlib import Path

View file

@ -46,14 +46,8 @@ def mount(img_path: Path):
losetup_cmd += ["-b", str(int(sector_size))]
pmb.chroot.root(losetup_cmd, check=False)
try:
device_by_back_file(img_path)
return
except RuntimeError:
pass
# Failure: raise exception
raise RuntimeError(f"Failed to mount loop device: {img_path}")
return device_by_back_file(img_path)
def device_by_back_file(back_file: Path) -> Path:
@ -100,5 +94,6 @@ def detach_all():
for loopdevice in losetup["loopdevices"]:
print(loopdevice["back-file"])
if Path(loopdevice["back-file"]).is_relative_to(work):
pmb.chroot.root(["losetup", "-d", loopdevice["name"]])
pmb.helpers.run.root(["kpartx", "-d", loopdevice["name"]], check=False)
pmb.helpers.run.root(["losetup", "-d", loopdevice["name"]])
return

View file

@ -812,6 +812,8 @@ def get_parser():
" 'stdout', 'interactive', 'tui' (default),"
" 'background'. Details: pmb/helpers/run_core.py",
default="tui")
chroot.add_argument("--image", help="Mount the rootfs image and treat"
" it like a normal chroot.", action="store_true")
chroot.add_argument("command", default=["sh", "-i"], help="command"
" to execute inside the chroot. default: sh",
nargs='*')

View file

@ -20,6 +20,7 @@ import pmb.chroot.other
import pmb.chroot.initfs
import pmb.config
import pmb.config.pmaports
import pmb.install.losetup
from pmb.types import PathString, PmbArgs
import pmb.helpers.run
import pmb.parse.cpuinfo
@ -345,6 +346,10 @@ def run(args: PmbArgs):
"and select the 'qemu' vendor.")
arch = pmb.parse.deviceinfo().arch
# Make sure the rootfs image isn't mounted
pmb.mount.umount_all(Chroot(ChrootType.IMAGE, "").path)
pmb.install.losetup.detach_all()
img_path = system_image(device)
img_path_2nd = None
if args.second_storage:

View file

@ -78,6 +78,7 @@ class PmbArgs(Namespace):
host: str
host_qemu: str
image_size: str
image: bool
install_base: str
install_blockdev: str
install_cgpt: str