diff --git a/pmb/chroot/__init__.py b/pmb/chroot/__init__.py index 01ff116b..01b50a01 100644 --- a/pmb/chroot/__init__.py +++ b/pmb/chroot/__init__.py @@ -1,7 +1,7 @@ # Copyright 2023 Oliver Smith # SPDX-License-Identifier: GPL-3.0-or-later from pmb.chroot.init import init, init_keys -from pmb.chroot.mount import mount, mount_native_into_foreign +from pmb.chroot.mount import mount, mount_native_into_foreign, remove_mnt_pmbootstrap from pmb.chroot.root import root from pmb.chroot.user import user from pmb.chroot.user import exists as user_exists diff --git a/pmb/chroot/mount.py b/pmb/chroot/mount.py index 65f00a7b..45053c0d 100644 --- a/pmb/chroot/mount.py +++ b/pmb/chroot/mount.py @@ -106,3 +106,18 @@ def mount_native_into_foreign(args, suffix): if not os.path.lexists(musl_link): pmb.helpers.run.root(args, ["ln", "-s", "/native/lib/" + musl, musl_link]) + +def remove_mnt_pmbootstrap(args, suffix): + """ Safely remove /mnt/pmbootstrap directories from the chroot, without + running rm -r as root and potentially removing data inside the + mountpoint in case it was still mounted (bug in pmbootstrap, or user + ran pmbootstrap 2x in parallel). This is similar to running 'rm -r -d', + but we don't assume that the host's rm has the -d flag (busybox does + not). """ + mnt_dir = f"{args.work}/chroot_{suffix}/mnt/pmbootstrap" + + if not os.path.exists(mnt_dir): + return + + for path in glob.glob(f"{mnt_dir}/*") + [mnt_dir]: + pmb.helpers.run.root(args, ["rmdir", path]) diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py index 2ded467a..12be7b1a 100644 --- a/pmb/config/__init__.py +++ b/pmb/config/__init__.py @@ -206,6 +206,7 @@ chroot_host_path = os.environ["PATH"] + ":/usr/sbin/" # $WORK gets replaced with args.work # $ARCH gets replaced with the chroot architecture (eg. x86_64, armhf) # $CHANNEL gets replaced with the release channel (e.g. edge, v21.03) +# Use no more than one dir after /mnt/pmbootstrap, see remove_mnt_pmbootstrap. chroot_mount_bind = { "/proc": "/proc", "$WORK/cache_apk_$ARCH": "/var/cache/apk", diff --git a/pmb/install/_install.py b/pmb/install/_install.py index 4b7ff47c..98a9127e 100644 --- a/pmb/install/_install.py +++ b/pmb/install/_install.py @@ -816,6 +816,7 @@ def install_system_image(args, size_reserve, suffix, step, steps, # Clean up after running mkinitfs in chroot pmb.helpers.mount.umount_all(args, f"{args.work}/chroot_{suffix}") pmb.helpers.run.root(args, ["rm", f"{args.work}/chroot_{suffix}/in-pmbootstrap"]) + pmb.chroot.remove_mnt_pmbootstrap(args, suffix) # Just copy all the files logging.info(f"*** ({step + 1}/{steps}) FILL INSTALL BLOCKDEVICE ***") diff --git a/test/test_chroot_mount.py b/test/test_chroot_mount.py new file mode 100644 index 00000000..2f4b66db --- /dev/null +++ b/test/test_chroot_mount.py @@ -0,0 +1,40 @@ +# Copyright 2023 Oliver Smith +# SPDX-License-Identifier: GPL-3.0-or-later +""" Test pmb/chroot/mount.py """ +import os +import pytest +import sys + +import pmb_test # noqa +import pmb.chroot + + +@pytest.fixture +def args(tmpdir, request): + import pmb.parse + sys.argv = ["pmbootstrap", "init"] + args = pmb.parse.arguments() + args.log = args.work + "/log_testsuite.txt" + pmb.helpers.logging.init(args) + request.addfinalizer(pmb.helpers.logging.logfd.close) + return args + + +def test_chroot_mount(args): + suffix = "native" + mnt_dir = f"{args.work}/chroot_native/mnt/pmbootstrap" + + # Run something in the chroot to have the dirs created + pmb.chroot.root(args, ["true"]) + assert os.path.exists(mnt_dir) + assert os.path.exists(f"{mnt_dir}/packages") + + # Umount everything, like in pmb.install.install_system_image + pmb.helpers.mount.umount_all(args, f"{args.work}/chroot_{suffix}") + + # Remove all /mnt/pmbootstrap dirs + pmb.chroot.remove_mnt_pmbootstrap(args, suffix) + assert not os.path.exists(mnt_dir) + + # Run again: it should not crash + pmb.chroot.remove_mnt_pmbootstrap(args, suffix)