From bacf0a178e8e52e26b30fadc448f9262643f5392 Mon Sep 17 00:00:00 2001 From: Michal Zegan Date: Fri, 22 Mar 2019 14:16:04 +0100 Subject: Rewrite the run_chroot.sh script This change heavily changes run_chroot.sh script. Major change is that most of the real script logic is now run in a namespace, so that all mounts are done in namespace. Also, all the cleanup code and some checks are removed, also the shell wrapper is removed. This change is for simplifying the run_chroot.sh. It will function the same as before from the user's point of view, except that now the script does not have to explicitly unmount filesystems that it mounted before, so no cleanup code isneeded. Also, all processes run from inside a chroot should be killed immediately when the chroot command finishes. Change-Id: I45a3dc7c6583cb9693746cc04f42b7af325724dd Signed-off-by: Michal Zegan Issue-ID: OOM-1744 --- ansible/docker/run_chroot.sh | 418 ++++++++++++++++--------------------------- 1 file changed, 156 insertions(+), 262 deletions(-) (limited to 'ansible') diff --git a/ansible/docker/run_chroot.sh b/ansible/docker/run_chroot.sh index 3359fdcd..268143ed 100755 --- a/ansible/docker/run_chroot.sh +++ b/ansible/docker/run_chroot.sh @@ -22,7 +22,6 @@ set -e CMD=$(basename "$0") -UMOUNT_TIMEOUT=120 # 2mins # @@ -106,31 +105,18 @@ EXAMPLE: " } -# arg: -is_mounted() -{ - mountpoint=$(echo "$1" | sed 's#//*#/#g') - - LANG=C mount | grep -q "^[^[:space:]]\+[[:space:]]\+on[[:space:]]\+${mountpoint}[[:space:]]\+type[[:space:]]\+" -} - # layers are right to left! First is on the right, top/last is on the left do_overlay_mount() { - if [ -d "$overlay" ] && is_mounted "$overlay" ; then - echo ERROR: "The overlay directory is already mounted: $overlay" >&2 - echo ERROR: "Fix the issue - cannot proceed" >&2 - exit 1 - fi - # prepare dirs - rm -rf "$overlay" "$upperdir" "$workdir" +mkdir -p $ovtempdir +mount -t tmpfs -o mode=0755 tmpfs $ovtempdir mkdir -p "$overlay" mkdir -p "$upperdir" mkdir -p "$workdir" # finally overlay mount - if ! mount -t overlay --make-rprivate \ + if ! mount -t overlay \ -o lowerdir="$lowerdir",upperdir="$upperdir",workdir="$workdir" \ overlay "$overlay" ; then @@ -147,40 +133,16 @@ do_overlay_mount() return 0 } -cleanup() -{ - case "$OVERLAY_MOUNT" in - yes) - echo INFO: "Umounting overlay..." >&2 - if ! umount_retry "$CHROOT_DIR" ; then - echo ERROR: "Cannot umount chroot: $CHROOT_DIR" >&2 - return 1 - fi - - ;; - no) - echo INFO: "No overlay to umount" >&2 - ;; - esac - - if ! is_mounted "$overlay" ; then - echo INFO: "Deleting of temp directories..." >&2 - rm -rf "$overlay" "$upperdir" "$workdir" - else - echo ERROR: "Overlay is still mounted: $CHROOT_DIR" >&2 - echo ERROR: "Cannot delete: $overlay" >&2 - echo ERROR: "Cannot delete: $upperdir" >&2 - echo ERROR: "Cannot delete: $workdir" >&2 - return 1 - fi -} - check_external_mounts() { - echo "$EXTERNAL_MOUNTS" | sed '/^[[:space:]]*$/d' | while read -r mountexpr ; do + echo "$EXTERNAL_MOUNTS" | while read -r mountexpr ; do + #Skip empty lines, done with if for readability. + if [ -z $mountexpr ]; then + continue + fi mount_type=$(echo "$mountexpr" | awk 'BEGIN{FS=":"}{print $1;}') external=$(echo "$mountexpr" | awk 'BEGIN{FS=":"}{print $2;}') - internal=$(echo "$mountexpr" | awk 'BEGIN{FS=":"}{print $3;}' | sed -e 's#^/*##' -e 's#//*#/#g') + internal=$(echo "$mountexpr" | awk 'BEGIN{FS=":"}{print $3;}') case "$mount_type" in ro|rw) @@ -203,16 +165,13 @@ check_external_mounts() do_external_mounts() { echo INFO: "Bind mounting of external mounts..." >&2 - echo "$EXTERNAL_MOUNTS" | sed '/^[[:space:]]*$/d' | while read -r mountexpr ; do + echo "$EXTERNAL_MOUNTS" | while read -r mountexpr ; do + if [ -z $mountexpr ]; then + continue + fi mount_type=$(echo "$mountexpr" | awk 'BEGIN{FS=":"}{print $1;}') external=$(echo "$mountexpr" | awk 'BEGIN{FS=":"}{print $2;}') - internal=$(echo "$mountexpr" | awk 'BEGIN{FS=":"}{print $3;}' | sed -e 's#^/*##' -e 's#//*#/#g') - - if is_mounted "${CHROOT_DIR}/${internal}" ; then - echo ERROR: "Mountpoint is already mounted: ${CHROOT_DIR}/${internal}" >&2 - echo ERROR: "Fix the issue - cannot proceed" >&2 - exit 1 - fi + internal=$(echo "$mountexpr" | awk 'BEGIN{FS=":"}{print $3;}') # trying to follow the behaviour of docker if ! [ -e "$external" ] || [ -d "$external" ] ; then @@ -242,7 +201,9 @@ do_external_mounts() exit 1 fi - if ! mount --make-rprivate -o bind,${mount_type} "$external" "${CHROOT_DIR}/${internal}" ; then +#Note, this double mounting is needed to support older util-linux. + if ! mount -o bind "${external}" "${CHROOT_DIR}/${internal}" || + ! mount -o remount,bind,${mount_type} "${CHROOT_DIR}/${internal}" ; then echo ERROR: "Failed to mount: ${external} -> ${internal}" >&2 exit 1 else @@ -251,231 +212,164 @@ do_external_mounts() done } -# arg: -umount_retry() -{ - mountpoint=$(echo "$1" | sed 's#//*#/#g') - timeout=${UMOUNT_TIMEOUT} - - umount "$mountpoint" 2>/dev/null - while is_mounted "$mountpoint" && [ $timeout -gt 0 ] ; do - umount "$mountpoint" 2>/dev/null - sleep 1 - timeout=$(( timeout - 1 )) - done - - if ! is_mounted "$mountpoint" ; then - return 0 - fi - - return 1 -} - -undo_external_mounts() -{ - echo INFO: "Umount external mount points..." >&2 - echo "$EXTERNAL_MOUNTS" | tac | sed '/^[[:space:]]*$/d' | while read -r mountexpr ; do - mount_type=$(echo "$mountexpr" | awk 'BEGIN{FS=":"}{print $1;}') - external=$(echo "$mountexpr" | awk 'BEGIN{FS=":"}{print $2;}') - internal=$(echo "$mountexpr" | awk 'BEGIN{FS=":"}{print $3;}' | sed -e 's#^/*##' -e 's#//*#/#g') - if umount_retry "${CHROOT_DIR}/${internal}" ; then - echo INFO: "Unmounted: ${CHROOT_DIR}/${internal}" >&2 - else - echo ERROR: "Failed to umount: ${CHROOT_DIR}/${internal}" >&2 - fi - done -} - -install_wrapper() -{ - cat > "$CHROOT_DIR"/usr/local/bin/fakeshell.sh <&2 + ;; + --workdir) + if [ -z "$CHROOT_WORKDIR" ] ; then + CHROOT_WORKDIR="$2" + state=next + else + echo ERROR: "Multiple working directory argument" >&2 + help >&2 + exit 1 + fi + ;; + execute) + action=execute + state=execute + ;; + *) + echo ERROR: "Bad usage" >&2 help >&2 exit 1 - fi - ;; - execute) - action=execute - state=execute - ;; - *) - echo ERROR: "Bad usage" >&2 - help >&2 - exit 1 - ;; - esac - ;; - next) - state=nil - ;; - execute) - CHROOT_METADIR="$1" - shift - break - ;; - esac - shift -done - - -case "$action" in - ''|nil) + ;; + esac + ;; + next) + state=nil + ;; + execute) + CHROOT_METADIR="$1" + shift + break + ;; + esac + shift + done + + + if [ $action = "nil" ]; then echo ERROR: "Nothing to do - missing command" >&2 help >&2 exit 1 - ;; - execute) - # firstly do sanity checking ... - - if [ -z "$CHROOT_METADIR" ] ; then - echo ERROR: "Missing argument" >&2 - help >&2 - exit 1 - fi - - # making sure that CHROOT_METADIR is absolute path - CHROOT_METADIR=$(readlink -f "$CHROOT_METADIR") - - if ! [ -d "$CHROOT_METADIR"/chroot ] ; then - echo ERROR: "Filepath does not exist: ${CHROOT_METADIR}/chroot" >&2 - exit 1 - fi - - # check external mounts if there are any - check_external_mounts - - # check workdir - if [ -n "$CHROOT_WORKDIR" ] ; then - CHROOT_WORKDIR=$(echo "$CHROOT_WORKDIR" | sed -e 's#^/*##' -e 's#//*#/#g') - fi - - # we must be root - if [ "$(id -u)" -ne 0 ] ; then - echo ERROR: "Need to be root and you are not: $(id -nu)" >&2 - exit 1 - fi + fi - if ! which unshare >/dev/null 2>/dev/null ; then - echo ERROR: "'unshare' system command is missing - ABORT" >&2 - echo INFO: "Try to install 'util-linux' package" >&2 - exit 1 - fi + # do sanity checking ... - # ... sanity checking done + if [ -z "$CHROOT_METADIR" ] ; then + echo ERROR: "Missing argument" >&2 + help >&2 + exit 1 + fi - # setup paths - lowerdir="$CHROOT_METADIR"/chroot - upperdir="$CHROOT_METADIR"/.overlay - workdir="$CHROOT_METADIR"/.workdir - overlay="$CHROOT_METADIR"/.merged + # making sure that CHROOT_METADIR is absolute path + CHROOT_METADIR=$(readlink -f "$CHROOT_METADIR") - # set trap - trap on_exit QUIT TERM EXIT + if ! [ -d "$CHROOT_METADIR"/chroot ] ; then + echo ERROR: "Filepath does not exist: ${CHROOT_METADIR}/chroot" >&2 + exit 1 + fi - # mount overlay - OVERLAY_MOUNT='' - if do_overlay_mount ; then - # overlay chroot - OVERLAY_MOUNT=yes - else - # non overlay mount - OVERLAY_MOUNT=no - fi + # check external mounts if there are any + check_external_mounts - # do the user-specific mounts - do_external_mounts + # we must be root + if [ "$(id -u)" -ne 0 ] ; then + echo ERROR: "Need to be root and you are not: $(id -nu)" >&2 + exit 1 + fi - # I need this wrapper to do some setup inside the chroot... - install_wrapper + if ! which unshare >/dev/null 2>/dev/null ; then + echo ERROR: "'unshare' system command is missing - ABORT" >&2 + echo INFO: "Try to install 'util-linux' package" >&2 + exit 1 + fi - # execute chroot - if [ -n "$1" ] ; then - : - else - set -- /bin/sh -l - fi - unshare -mfpi --propagation private \ - chroot "$CHROOT_DIR" /usr/local/bin/fakeshell.sh "${CHROOT_WORKDIR:-/}" "$@" - ;; -esac + # ... sanity checking done + + #Reexec ourselves in new pid and mount namespace (isolate!). + #Note: newly executed shell will be pid1 in a new namespace. Killing it will kill + #every other process in the whole process tree with sigkill. That will in turn + #destroy namespaces and undo all mounts done previously. + IN_NAMESPACE=1 exec unshare -mpf "$0" "$@" +fi + +#We are namespaced. +# setup paths +lowerdir="$CHROOT_METADIR"/chroot +ovtempdir="$CHROOT_METADIR"/tmp +upperdir="$ovtempdir"/.overlay +workdir="$ovtempdir"/.workdir +overlay="$CHROOT_METADIR"/.merged + +#In case we are using a realy old unshare, make the whole tree into private mounts manually. +mount --make-rprivate / +#New mounts are private always from now on. + +do_overlay_mount + +# do the user-specific mounts +do_external_mounts + +#And setup api filesystems. +mount -t proc proc "${CHROOT_DIR}/proc" +mount -t sysfs none "${CHROOT_DIR}/sys" +mount -t tmpfs none "${CHROOT_DIR}/dev" + +mkdir -p "${CHROOT_DIR}/dev/shm" +mkdir -p "${CHROOT_DIR}/dev/pts" +mount -t devpts none "${CHROOT_DIR}/dev/pts" + +mknod -m 666 "${CHROOT_DIR}/dev/full" c 1 7 +mknod -m 666 "${CHROOT_DIR}/dev/ptmx" c 5 2 +mknod -m 644 "${CHROOT_DIR}/dev/random" c 1 8 +mknod -m 644 "${CHROOT_DIR}/dev/urandom" c 1 9 +mknod -m 666 "${CHROOT_DIR}/dev/zero" c 1 5 +mknod -m 666 "${CHROOT_DIR}/dev/tty" c 5 0 +mknod -m 622 "${CHROOT_DIR}/dev/console" c 5 1 +mknod -m 666 "${CHROOT_DIR}/dev/null" c 1 3 +ln -s /proc/self/fd/0 "$CHROOT_DIR/dev/stdin" +ln -s /proc/self/fd/1 "$CHROOT_DIR/dev/stdout" +ln -s /proc/self/fd/2 "$CHROOT_DIR/dev/stderr" + +# execute chroot +if [ -z "$1" ] ; then + set -- /bin/sh -l +fi + +#The redirection is to save our stdin, because we use it to pipe commands and we +#may want interactivity. +exec chroot "${CHROOT_DIR}" /bin/sh /dev/stdin "${CHROOT_WORKDIR:-/}" "$@" 3<&0 << "EOF" +mkdir -p $1 +cd $1 +shift +#I intend to reset stdin back *and* close the copy. +exec "$@" <&3 3<&- +EOF exit 0 -- cgit 1.2.3-korg