#!/bin/sh

#
# Copyright (C) 2024 NVIDIA Corporation.
# Copyright (C) 2017, 2020, 2020 Cumulus Networks, Inc.
# All rights reserved
#

# Built upon the debian-installer build system, portitions of which
# are Copyright 2001-2009 by Joey Hess <joeyh@debian.org> and the d-i
# team, and is licensed under the terms of the GNU GPL 2 or any later
# version, which can be found in /usr/share/common-licenses/GPL-2 on
# Debian systems.

# Runs a command in a target chroot directory.

# Step 1 - verify arguments
# Step 2 - setup chroot environment, mount /proc, /sys, etc.
# Step 3 - execute chroot command
# step 4 - tear down chroot environment

set -e

TARGET_DIR="$1"
if [ "$TARGET_DIR" = "" ]; then
    echo "usage: target-dir command"
    exit 1
fi
shift
[ -d "$TARGET_DIR" ] || {
    echo "ERROR: Target directory does not exist: $TARGET_DIR"
    exit 1
}

STATE_FILE="${TARGET_DIR}/tmp/in-target-running"
[ -f "$STATE_FILE" ] && {
    echo "ERROR: Target directory is already configured for chroot: $TARGET_DIR"
    echo "Clean up required before continuing"
    exit 1
}

# Helper function definitions start here.  These are lifted from the
# Debian installer's in-target and chroot-setup.sh functions
mountpoints () {
	cut -d" " -f2 /proc/mounts | sort | uniq
}

# Make sure mtab in the chroot reflects the currently mounted partitions.
update_mtab() {
	mtab=${TARGET_DIR}/etc/mtab

	if [ -h "$mtab" ]; then
		# mtab won't be updated since it is a symlink
		return 0
	fi

	egrep "^[^ ]+ ${TARGET_DIR}" /proc/mounts | (
	while read devpath mountpoint fstype options n1 n2 ; do
		# devpath=`mapdevfs $devpath || echo $devpath`
		mountpoint="${mountpoint#${TARGET_DIR}}"
		# mountpoint for root will be empty
		if [ -z "$mountpoint" ] ; then
			mountpoint="/"
		fi
		echo $devpath $mountpoint $fstype $options $n1 $n2
	done ) > $mtab
}

divert () {
	# Old style divert throws warnings because the useage of it has changed
	# in Buster. It demands --no-rename, and does not rename the files, handling only the
	# database aspects of the divert. Thus file manipulation is handled manually.

	chroot ${TARGET_DIR} dpkg-divert --quiet --no-rename --add "$1"
	mv "${TARGET_DIR}$1" "${TARGET_DIR}$1.REAL"


}

undivert () {
	# See divert() note about manually handling files
	rm -f "${TARGET_DIR}$1"
	chroot ${TARGET_DIR} dpkg-divert --quiet --no-rename  --remove "$1"
	mv "${TARGET_DIR}$1.REAL" "${TARGET_DIR}$1"

}

chroot_setup () {
	# Bail out if directories we need are not there
	if [ ! -d ${TARGET_DIR}/sbin ] || [ ! -d ${TARGET_DIR}/usr/sbin ] || \
           [ ! -d ${TARGET_DIR}/proc ]; then
		return 1
	fi
	if [ -d /sys/devices ] && [ ! -d ${TARGET_DIR}/sys ]; then
		return 1
	fi

	# Create a policy-rc.d to stop maintainer scripts using invoke-rc.d 
	# from running init scripts. In case of maintainer scripts that don't
	# use invoke-rc.d, add a dummy start-stop-daemon.
	cat > ${TARGET_DIR}/usr/sbin/policy-rc.d <<EOF
#!/bin/sh
exit 101
EOF
	chmod a+rx ${TARGET_DIR}/usr/sbin/policy-rc.d

	if [ -e ${TARGET_DIR}/sbin/start-stop-daemon ]; then
		divert /sbin/start-stop-daemon
	fi
	cat > ${TARGET_DIR}/sbin/start-stop-daemon <<EOF
#!/bin/sh
echo 1>&2
echo 'Warning: Fake start-stop-daemon called, doing nothing.' 1>&2
exit 0
EOF
	chmod a+rx ${TARGET_DIR}/sbin/start-stop-daemon

	# If Upstart is in use, add a dummy initctl to stop it starting jobs.
	if [ -x ${TARGET_DIR}/sbin/initctl ]; then
		divert /sbin/initctl
		cat > ${TARGET_DIR}/sbin/initctl <<EOF
#!/bin/sh
if [ "\$1" = version ]; then exec /sbin/initctl.REAL "\$@"; fi
echo 1>&2
echo 'Warning: Fake initctl called, doing nothing.' 1>&2
exit 0
EOF
		chmod a+rx ${TARGET_DIR}/sbin/initctl
	fi

	# Record the current mounts
	mountpoints > ${TARGET_DIR}/tmp/mount.pre

	# Some packages (eg. the kernel-image package) require a mounted
	# /proc/. Only mount it if not mounted already
	if [ ! -f ${TARGET_DIR}/proc/cmdline ]; then
            mount -t proc proc-install ${TARGET_DIR}/proc
	fi

	# We also need sysfs mounted.  Only mount it if not mounted
	# already
	if [ ! -d ${TARGET_DIR}/sys/devices ]; then
            mount -t sysfs sysfs-install ${TARGET_DIR}/sys
	fi

        # /dev from the host in the target
        mount --bind /dev ${TARGET_DIR}/dev

	# /dev/ lacks the pty devices, so we need devpts mounted
	if [ ! -e ${TARGET_DIR}/dev/pts/0 ]; then
            mkdir -p ${TARGET_DIR}/dev/pts
            mount -t devpts devpts-install -o noexec,nosuid,gid=5,mode=620 \
                  ${TARGET_DIR}/dev/pts
	fi

        # put a tmpfs on /run
        mount -t tmpfs tmpfs-install -o rw,nosuid,relatime,size=5M,mode=755 \
              ${TARGET_DIR}/run
        mkdir -p ${TARGET_DIR}/run/lock

	mountpoints > ${TARGET_DIR}/tmp/mount.post

	update_mtab

	return 0
}

chroot_cleanup () {
	rm -f ${TARGET_DIR}/usr/sbin/policy-rc.d
	undivert /sbin/start-stop-daemon
	if [ -x ${TARGET_DIR}/sbin/initctl.REAL ]; then
		undivert /sbin/initctl
	fi

	# Undo the mounts done by the packages during installation.
	# Reverse sorting to umount the deepest mount points first.
	# Items with count of 1 are new.
        for dir in $( (cat ${TARGET_DIR}/tmp/mount.pre ${TARGET_DIR}/tmp/mount.pre; mountpoints ) | \
                          sort -r | uniq -c | grep "^[[:space:]]*1[[:space:]]" | \
                          sed "s/^[[:space:]]*[0-9][[:space:]]//"); do
            # Make sure directories are sub-directories of the TARGET_DIR
            if [ "${dir#${TARGET_DIR}}" != "$dir" ] ; then
                if ! umount $dir; then
                    echo "warning: Unable to umount '$dir'"
                fi
            fi
        done
	rm -f ${TARGET_DIR}/tmp/mount.pre ${TARGET_DIR}/tmp/mount.post ${TARGET_DIR}/tmp/run-cmd
	rm -f $STATE_FILE
}

trap chroot_cleanup INT EXIT QUIT

# Main script starts here
touch $STATE_FILE

chroot_setup

# The remaining args are passed to chroot
cat - <<EOF > ${TARGET_DIR}/tmp/run-cmd
#!/bin/sh

# Quiet debconf
export DEBIAN_FRONTEND=noninteractive
export LC_ALL=C
export LANGUAGE=C
export LANG=C
# export DEBIAN_SCRIPT_DEBUG=1

# The command passed to us
$@

EOF
chmod +x ${TARGET_DIR}/tmp/run-cmd

LC_ALL=C chroot $TARGET_DIR /tmp/run-cmd
rc=$?

rm -f $TARGET_DIR/tmp/run-cmd

chroot_cleanup
trap - INT EXIT QUIT

if [ "$rc" != 0 ]; then
    exit $rc
else
    exit 0
fi
