Skip to content

Commit

Permalink
Use mount namespace instead of chroot
Browse files Browse the repository at this point in the history
This allow to not worry about mounts done within the namespace.  We
can now bind mount files into the sysroot instead of copying them.
  • Loading branch information
valentindavid committed Jul 18, 2022
1 parent 93afceb commit 0382fde
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 20 deletions.
14 changes: 3 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,22 @@ install:
fi
rm -rf $(DESTDIR)
cp -aT $(CRAFT_STAGE)/base $(DESTDIR)
# ensure resolving works inside the chroot
cat /etc/resolv.conf > $(DESTDIR)/etc/resolv.conf
# copy-in launchpad's build archive
if grep -q ftpmaster.internal /etc/apt/sources.list; then \
cp /etc/apt/sources.list $(DESTDIR)/etc/apt/sources.list; \
cp /etc/apt/trusted.gpg $(DESTDIR)/etc/apt/ || true; \
cp -r /etc/apt/trusted.gpg.d $(DESTDIR)/etc/apt/ || true; \
fi
# since recently we're also missing some /dev files that might be
# useful during build - make sure they're there
[ -e $(DESTDIR)/dev/null ] || mknod -m 666 $(DESTDIR)/dev/null c 1 3
[ -e $(DESTDIR)/dev/zero ] || mknod -m 666 $(DESTDIR)/dev/zero c 1 5
[ -e $(DESTDIR)/dev/random ] || mknod -m 666 $(DESTDIR)/dev/random c 1 8
[ -e $(DESTDIR)/dev/urandom ] || \
mknod -m 666 $(DESTDIR)/dev/urandom c 1 9
# copy static files verbatim
/bin/cp -a static/* $(DESTDIR)
mkdir -p $(DESTDIR)/install-data
/bin/cp -r $(CRAFT_STAGE)/local-debs $(DESTDIR)/install-data/local-debs
# customize
set -eux; for f in ./hooks/[0-9]*.chroot; do \
base="$$(basename "$${f}")"; \
cp -a "$${f}" $(DESTDIR)/install-data/; \
chroot $(DESTDIR) "/install-data/$${base}"; \
./mount-ns.sh spawn $(DESTDIR) \
--ro-bind $$f "/install-data/$${base}" \
-- "/install-data/$${base}"; \
rm "$(DESTDIR)/install-data/$${base}"; \
done
rm -rf $(DESTDIR)/install-data
Expand Down
4 changes: 0 additions & 4 deletions hooks/001-extra-packages.chroot
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ export DEBIAN_FRONTEND=noninteractive
rm -f /etc/apt/sources.list.d/proposed.list


# ensure we have /proc or systemd will fail
mount -t proc proc /proc
trap 'umount /proc' EXIT

# systemd postinst needs this
mkdir -p /var/log/journal

Expand Down
5 changes: 0 additions & 5 deletions hooks/999-clean-resolv-conf.chroot

This file was deleted.

125 changes: 125 additions & 0 deletions mount-ns.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/bin/bash

set -eu

# FIXME: This should be replace by a mount of devtmpfs when
# it is supported in user namespaces
bind_dev() {
dev="${1}/dev"

for device in null zero full random urandom tty; do
touch "${dev}/${device}"
mount --bind "/dev/${device}" "${dev}/${device}"
done
}

# Because systemd debian package post-install script is not very
# robust, we have to create a symlink and bind mount resolv.conf
# there.
bind_resolv() {
sysroot="${1}"

resolv_real_path="${sysroot}/run/fake-resolv.conf"
create_symlink=yes
if [ -L "${sysroot}/etc/resolv.conf" ]; then
resolv="$(readlink "${sysroot}/etc/resolv.conf")"
if [ "${resolv}" = "../run/systemd/resolve/stub-resolv.conf" ]; then
resolv_real_path="${sysroot}/run/systemd/resolve/stub-resolv.conf"
create_symlink=no
fi
fi
mkdir -p "$(dirname "${resolv_real_path}")"
touch "${resolv_real_path}"
mount --bind -o ro /etc/resolv.conf "${resolv_real_path}"
if [ "${create_symlink}" = yes ]; then
ln -srf "${resolv_real_path}" "${sysroot}/etc/resolv.conf"
fi
}

if [ $# -lt 3 ]; then
echo "Expected at least 3 arguments" 1>&2
exit 1
fi

command="${1}"
sysroot="${2}"
shift 2

case "${command}" in
spawn)
# This is the first phase. This will re-spawn the script with
# `init` subcommand. There are limitations of what can be
# done within a LXD container. So in the first phase we mount
# a tmpfs filesytem which cannot always be done in a mount
# namespace. Because it is outside of the mount namespace,
# it has to be removed from manually.
# Then we spawn the second phase into a namespace,
# but without changing the root.

tmpdir="$(mktemp -d --tmpdir mount-ns.XXXXXXXXXX)"
cleanup() {
umount "${tmpdir}" || true
rm -rf "${tmpdir}"
}
mount -t tmpfs tmpfs "${tmpdir}"
mkdir -m 0755 -p "${tmpdir}/dev"
mkdir -m 1777 -p "${tmpdir}/tmp"
mkdir -m 0755 -p "${tmpdir}/run"
options=(
--bind "${tmpdir}/dev" /dev
--bind "${tmpdir}/tmp" /tmp
--bind "${tmpdir}/run" /run
)
trap cleanup EXIT
unshare --pid --fork --mount -- "${0}" init "${sysroot}" "${options[@]}" "${@}"
;;
init)
# This is the second phase. Here we are in a mount namespace,
# spawned from the `spawn` subcommand. But we still have the
# same root directory. So we can bind mount all we need in the
# sysroot. Then we can change the root to that sysroot.

mount -t proc proc "${sysroot}/proc"
while [ $# -gt 1 ]; do
case "${1}" in
--)
shift
break
;;
--bind|--ro-bind)
if [ -d "$2" ]; then
if ! [ -d "${sysroot}/$3" ]; then
mkdir -p "${sysroot}/$3"
fi
else
if ! [ -e "${sysroot}/$3" ]; then
dir="$(dirname "${sysroot}/$3")"
if ! [ -d "${dir}" ]; then
mkdir -p "${dir}"
fi
touch "${sysroot}/$3"
fi
fi
extra_args=()
case "$1" in
--ro-bind)
extra_args=("-o" "ro")
;;
esac
mount --bind "${extra_args[@]}" "$2" "${sysroot}/$3"
shift 3
;;
*)
break
;;
esac
done
bind_dev "${sysroot}"
bind_resolv "${sysroot}"
exec unshare --mount --root="${sysroot}" -- "${@}"
;;
*)
echo "Unknown command" 1>&2
exit 1
;;
esac

0 comments on commit 0382fde

Please sign in to comment.