Skip to content

Commit a931362

Browse files
committed
Use mount namespace instead of chroot
This allow to not worry about mounts done within the namespace. We can now bind mount files into the sysroot instead of copying them.
1 parent 173f328 commit a931362

4 files changed

Lines changed: 128 additions & 20 deletions

File tree

Makefile

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,22 @@ install:
1616
fi
1717
rm -rf $(DESTDIR)
1818
cp -aT $(CRAFT_STAGE)/base $(DESTDIR)
19-
# ensure resolving works inside the chroot
20-
cat /etc/resolv.conf > $(DESTDIR)/etc/resolv.conf
2119
# copy-in launchpad's build archive
2220
if grep -q ftpmaster.internal /etc/apt/sources.list; then \
2321
cp /etc/apt/sources.list $(DESTDIR)/etc/apt/sources.list; \
2422
cp /etc/apt/trusted.gpg $(DESTDIR)/etc/apt/ || true; \
2523
cp -r /etc/apt/trusted.gpg.d $(DESTDIR)/etc/apt/ || true; \
2624
fi
27-
# since recently we're also missing some /dev files that might be
28-
# useful during build - make sure they're there
29-
[ -e $(DESTDIR)/dev/null ] || mknod -m 666 $(DESTDIR)/dev/null c 1 3
30-
[ -e $(DESTDIR)/dev/zero ] || mknod -m 666 $(DESTDIR)/dev/zero c 1 5
31-
[ -e $(DESTDIR)/dev/random ] || mknod -m 666 $(DESTDIR)/dev/random c 1 8
32-
[ -e $(DESTDIR)/dev/urandom ] || \
33-
mknod -m 666 $(DESTDIR)/dev/urandom c 1 9
3425
# copy static files verbatim
3526
/bin/cp -a static/* $(DESTDIR)
3627
mkdir -p $(DESTDIR)/install-data
3728
/bin/cp -r $(CRAFT_STAGE)/local-debs $(DESTDIR)/install-data/local-debs
3829
# customize
3930
set -eux; for f in ./hooks/[0-9]*.chroot; do \
4031
base="$$(basename "$${f}")"; \
41-
cp -a "$${f}" $(DESTDIR)/install-data/; \
42-
chroot $(DESTDIR) "/install-data/$${base}"; \
32+
./mount-ns.sh spawn $(DESTDIR) \
33+
--ro-bind $$f "/install-data/$${base}" \
34+
-- "/install-data/$${base}"; \
4335
rm "$(DESTDIR)/install-data/$${base}"; \
4436
done
4537
rm -rf $(DESTDIR)/install-data

hooks/001-extra-packages.chroot

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ export DEBIAN_FRONTEND=noninteractive
1212
rm -f /etc/apt/sources.list.d/proposed.list
1313

1414

15-
# ensure we have /proc or systemd will fail
16-
mount -t proc proc /proc
17-
trap 'umount /proc' EXIT
18-
1915
# systemd postinst needs this
2016
mkdir -p /var/log/journal
2117

hooks/999-clean-resolv-conf.chroot

Lines changed: 0 additions & 5 deletions
This file was deleted.

mount-ns.sh

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/bin/bash
2+
3+
set -eu
4+
5+
# FIXME: This should be replace by a mount of devtmpfs when
6+
# it is supported in user namespaces
7+
bind_dev() {
8+
dev="${1}/dev"
9+
10+
for device in null zero full random urandom tty; do
11+
touch "${dev}/${device}"
12+
mount --bind "/dev/${device}" "${dev}/${device}"
13+
done
14+
}
15+
16+
# Because systemd debian package post-install script is not very
17+
# robust, we have to create a symlink and bind mount resolv.conf
18+
# there.
19+
bind_resolv() {
20+
sysroot="${1}"
21+
22+
resolv_real_path="${sysroot}/run/fake-resolv.conf"
23+
create_symlink=yes
24+
if [ -L "${sysroot}/etc/resolv.conf" ]; then
25+
resolv="$(readlink "${sysroot}/etc/resolv.conf")"
26+
if [ "${resolv}" = "../run/systemd/resolve/stub-resolv.conf" ]; then
27+
resolv_real_path="${sysroot}/run/systemd/resolve/stub-resolv.conf"
28+
create_symlink=no
29+
fi
30+
fi
31+
mkdir -p "$(dirname "${resolv_real_path}")"
32+
touch "${resolv_real_path}"
33+
mount --bind -o ro /etc/resolv.conf "${resolv_real_path}"
34+
if [ "${create_symlink}" = yes ]; then
35+
ln -srf "${resolv_real_path}" "${sysroot}/etc/resolv.conf"
36+
fi
37+
}
38+
39+
if [ $# -lt 3 ]; then
40+
echo "Expected at least 3 arguments" 1>&2
41+
exit 1
42+
fi
43+
44+
command="${1}"
45+
sysroot="${2}"
46+
shift 2
47+
48+
case "${command}" in
49+
spawn)
50+
# This is the first phase. This will re-spawn the script with
51+
# `init` subcommand. There are limitations of what can be
52+
# done within a LXD container. So in the first phase we mount
53+
# a tmpfs filesytem which cannot always be done in a mount
54+
# namespace. Because it is outside of the mount namespace,
55+
# it has to be removed from manually.
56+
# Then we spawn the second phase into a namespace,
57+
# but without changing the root.
58+
59+
tmpdir="$(mktemp -d --tmpdir mount-ns.XXXXXXXXXX)"
60+
cleanup() {
61+
umount "${tmpdir}" || true
62+
rm -rf "${tmpdir}"
63+
}
64+
mount -t tmpfs tmpfs "${tmpdir}"
65+
mkdir -m 0755 -p "${tmpdir}/dev"
66+
mkdir -m 1777 -p "${tmpdir}/tmp"
67+
mkdir -m 0755 -p "${tmpdir}/run"
68+
options=(
69+
--bind "${tmpdir}/dev" /dev
70+
--bind "${tmpdir}/tmp" /tmp
71+
--bind "${tmpdir}/run" /run
72+
)
73+
trap cleanup EXIT
74+
unshare --pid --fork --mount -- "${0}" init "${sysroot}" "${options[@]}" "${@}"
75+
;;
76+
init)
77+
# This is the second phase. Here we are in a mount namespace,
78+
# spawned from the `spawn` subcommand. But we still have the
79+
# same root directory. So we can bind mount all we need in the
80+
# sysroot. Then we can change the root to that sysroot.
81+
82+
mount -t proc proc "${sysroot}/proc"
83+
while [ $# -gt 1 ]; do
84+
case "${1}" in
85+
--)
86+
shift
87+
break
88+
;;
89+
--bind|--ro-bind)
90+
if [ -d "$2" ]; then
91+
if ! [ -d "${sysroot}/$3" ]; then
92+
mkdir -p "${sysroot}/$3"
93+
fi
94+
else
95+
if ! [ -e "${sysroot}/$3" ]; then
96+
dir="$(dirname "${sysroot}/$3")"
97+
if ! [ -d "${dir}" ]; then
98+
mkdir -p "${dir}"
99+
fi
100+
touch "${sysroot}/$3"
101+
fi
102+
fi
103+
extra_args=()
104+
case "$1" in
105+
--ro-bind)
106+
extra_args=("-o" "ro")
107+
;;
108+
esac
109+
mount --bind "${extra_args[@]}" "$2" "${sysroot}/$3"
110+
shift 3
111+
;;
112+
*)
113+
break
114+
;;
115+
esac
116+
done
117+
bind_dev "${sysroot}"
118+
bind_resolv "${sysroot}"
119+
exec unshare --mount --root="${sysroot}" -- "${@}"
120+
;;
121+
*)
122+
echo "Unknown command" 1>&2
123+
exit 1
124+
;;
125+
esac

0 commit comments

Comments
 (0)