|
| 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