#!/bin/bash

# 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:
#
# 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
#
# Updated by 2026, Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
# to be released with Debian Edu Trixie.

version=2026-11-11

# 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.
  -i, --mount-isoimage          If set (and Debian Edu BD ISO image is inserted into the optical drive),
                                this mounts the installation medium to /media/cdrom and bind-mounts
                                this into the chroot.
  -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
        MOUNT_ISOIMAGE=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

    # If the BD ISO image was used and it is still inserted, ensure that it is mounted.
    if boolean_is_true "$MOUNT_ISOIMAGE"; then
        # FIXME: this does not honour metal box installations from a USB iso9660 medium!
        if grep -q BD /etc/apt/sources.list && ! mountpoint -q /media/cdrom && blkid /dev/sr0 1>/dev/null; then
            if mark_mount -o ro /media/cdrom 2>/dev/null; then
                bd_iso_was_mounted="yes"
            fi
        fi

        # If the BD ISO image is used, make it available inside the chroot, so that as many
        # packages as possible are obtained from the BD image instead of downloading them.
        if grep -q BD /etc/apt/sources.list && [ -e /media/cdrom/.disk/cd_type ] ; then
            mkdir -p "${BASE}/media/cdrom"
            mark_mount --bind /media/cdrom "${BASE}/media/cdrom"
        fi
    fi

    if [ ! -e "${BASE}/etc/resolv.conf" ]; then
        RESOLVCONF_NOTFOUND=true
        cp /etc/resolv.conf "${BASE}/etc/resolv.conf"
    fi

}

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

# 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"
        warn "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
}

cleanup_resolvconf() {
    if boolean_is_true "${RESOLVCONF_NOTFOUND}"; then
        rm "${BASE}/etc/resolv.conf"
    fi
}

# 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" "$@"
