#!/bin/sh

# Based upon the 'ltsp-chroot' script and 'ltsp-server-functions', both shipped
# with package 'ltsp-server 5.18.12-3' (Debian Buster).

# Original information about authors and copyright:
# copyright 2009 Vagrant Cascadian <vagrant@freegeek.org>,
# 2010 Alkis Georgopoulos <alkisg@gmail.com>,
# 2011 Wim Muskee <wimmuskee@gmail.com>, distributed under the
# terms of the GNU General Public License v2 or any later version.

# Reduced to the bare minimum and adjusted to be useful for Debian Edu Bullseye.
# Also, integrated everything into one tool to be usable standalone.
# 2021 Wolfgang Schweer <wschweer@arcor.de>, distributed under the
# terms of the GNU General Public License v2 or any later version.
# first edited:	2020-09-28

version=2021-10-02

# generic functions

usage() {
cat <<EOF
Usage: $0 [OPTION] command

Chroots into a specified LTSP chroot. Optional arguments can be provided to customize
the chroot location as well as extra mounts.

Options:
  -b, --base[=PATH]             Sets base of target chroot. Defaults to /srv/ltsp/dlw if unspecified.
  -d, --mount-dev               If set, mounts the server dev and devpts directories to the chroot
                                previous to chrooting.
  -h, --help                    Displays the debian-edu-ltsp-chroot help message.
  -p, --mount-proc              If set, mounts the server /proc to the chroot previous to chrooting.
  -m, --mount-all               If set, does both --mount-dev and --mount-proc previous to chrooting.
      --version                 Output version information and exit.
EOF
}

default_options() {
    BASE=${BASE:-/srv/ltsp/dlw}
    # If $BASE contains a terminating /, remove it
    BASE=${BASE%/}
}

pre_chroot() {
    test -d "$BASE" || die "ERROR: ltsp chroot not found: $BASE"

    if boolean_is_true "$MOUNT_ALL"; then
        MOUNT_DEV=true
        MOUNT_PROC=true
    fi
    if boolean_is_true "$MOUNT_DEV"; then
        mark_mount --bind "/dev" "$BASE/dev"
        mark_mount -t devpts -o rw,noexec,nosuid,gid=5,mode=620 devpts "$BASE/dev/pts"
    fi
    if boolean_is_true "$MOUNT_PROC"; then
        mark_mount -t proc proc "$BASE/proc"
    fi
}

post_chroot() {
    # Stop trapping
    trap - 0 HUP INT QUIT KILL SEGV PIPE TERM
    umount_marked
}

# Next functions are taken from ltsp-server-functions).

warn()
{
    printf "%s\n" "$*" >&2
}

die() {
    warn "$@"
    exit 1
}

boolean_is_true(){
    case $1 in
       # match all cases of true|y|yes
       [Tt][Rr][Uu][Ee]|[Yy]|[Yy][Ee][Ss]) return 0 ;;
       *) return 1 ;;
    esac
}

require_root()
{
    if [ ${UID:-$(id -u)} -ne 0 ]; then
        die "Superuser privileges are needed."
    fi
}

# Remember mounted dirs so that it's easier to unmount them with a single call
# to umount_marked. They'll be unmounted in reverse order.
# Use the normal mount syntax, e.g.
#   mark_mount -t proc proc "$ROOT/proc"
mark_mount() {
    local dir old_marked_mounts

    # The last parameter is the dir we need to remember to unmount
    dir=$(eval "echo \$$#")
    # If the user presses Ctrl+C while mount is still running, there's
    # a possibility that it will succeed but it won't go inside the if.
    # So mark the dir before mount.
    # Use newlines to separate dirs, in case they contain spaces
    old_marked_mounts="$MARKED_MOUNTS"
    if [ -z "$MARKED_MOUNTS" ]; then
        MARKED_MOUNTS="$dir"
    else
        MARKED_MOUNTS="$dir
$MARKED_MOUNTS"
    fi
    if ! mount "$@"; then
        MARKED_MOUNTS="$old_marked_mounts"
        die "Could not mount $dir."
    fi
}

umount_marked() {
    [ -z "$MARKED_MOUNTS" ] && return 0

    # Wait until all buffers are flushed, otherwise umount might fail
    sync
    echo "$MARKED_MOUNTS" | while read -r dir; do
        if ! umount "$dir"; then
            warn "Couldn't unmount $dir."
        fi
    done
    unset MARKED_MOUNTS
}

umount_marked() {
    [ -z "$MARKED_MOUNTS" ] && return

    # Wait until all buffers are flushed, otherwise umount might fail
    sync
    echo "$MARKED_MOUNTS" | while read -r dir; do
        # binfmt_misc might need to be unmounted manually, see LP #534211
        if [ "$dir%/proc}" != "$dir" ] && 
            [ -d "$dir/sys/fs/binfmt_misc" ] && [ -f "$dir/mounts" ] &&
            grep -q "^binfmt_misc $dir/sys/fs/binfmt_misc" "$dir/mounts"; then
            if ! umount "$dir/sys/fs/binfmt_misc"; then
                echo "Couldn't unmount $dir/sys/fs/binfmt_misc." >&2
            fi
        fi
        if ! umount "$dir"; then
            echo "Couldn't unmount $dir." >&2
        fi
    done
    unset MARKED_MOUNTS
}

# The command line parameters override the configuration file settings
if ! args=$(getopt -n "$0" -o +a:b:dhmp -l \
    'base:,mount-dev,help,mount-all,mount-proc,version' -- "$@"); then
    exit 1
fi
eval "set -- $args"
while true; do
    case "$1" in
        -b|--base) shift; BASE=$1 ;;
        -d|--mount-dev) MOUNT_DEV=true ;;
        -h|--help) usage; exit 0 ;;
        -m|--mount-all) MOUNT_ALL=true ;;
        -p|--mount-proc) MOUNT_PROC=true ;;
        --version) echo $version; exit 0 ;;
        --) shift; break ;;
        *) die "$0: Internal error!" ;;
    esac
    shift
done

# Finally, fall back to using default values for any unset options
default_options

require_root

trap "post_chroot" 0 HUP INT QUIT SEGV PIPE TERM
pre_chroot

# Unset temporary directory variables to avoid problems when directory
# is missing inside the chroot. https://bugs.debian.org/765443
unset TMPDIR TEMP TEMPDIR TMP

chroot "$BASE" "$@"
