#!/bin/sh
# James Budiono 2011, 2013, 2015, 2017, 2018
# puppy test/compilation sandbox
# this version uses tmpfs instead of an rw image, 
# and you can also choose which SFS to use
# run this from terminal.
# version 4 - replace sed with awk - more powerful and more correct, will handle all oddball cases
# 		      where loop-N and pup_ro-N numbers don't match
# version 5 - add compatibility when running with pup_rw=tmpfs (step 2.a)
# version 6 - (2012) adapted to be more flexible - for Fatdog64 600
# version 7 - (2012) cleanup mounts if if we are killed
# version 8 - (2013) re-launch in terminal if we aren't in terminal
# version 9 - (2013) enable running multiple sandboxes
# version 10 - (2015) use pid/mount namespaces if available
# version 11 - (2017) use dumb-init if available, support command from arguments

# 0. directory locations
. $BOOTSTATE_PATH # AUFS_ROOT_ID
[ "$NOT_USING_AUFS" ] && exit 1
XTERM="defaultterm"
SANDBOX_ROOT=/mnt/sb
FAKEROOT=$SANDBOX_ROOT/fakeroot   # mounted chroot location of sandbox - ie, the fake root
SANDBOX_TMPFS=$SANDBOX_ROOT/sandbox # mounted rw location of tmpfs used for sandbox
SANDBOX_ID=
TMPFILE=$(mktemp -p /tmp)
# use namespaces if available
[ -e /proc/1/ns/pid ] && [ -e /proc/1/ns/mnt ] && type unshare >/dev/null && USE_NS=1

# umount all if we are accidentally killed
trap 'umountall' 1
umountall() {
	{
	umount -l $FAKEROOT/$SANDBOX_TMPFS
	umount -l $FAKEROOT/tmp
	umount -l $FAKEROOT/proc
	umount -l $FAKEROOT/sys
	umount -l $FAKEROOT/dev/pts $FAKEROOT/dev/shm $FAKEROOT/dev
	
	umount -l $FAKEROOT
	umount -l $SANDBOX_TMPFS 
	rmdir $FAKEROOT
	rmdir $SANDBOX_TMPFS
	} 2> /dev/null
}

die() {
	echo "$1"
	exit 1
}

# 0.1 must be root
[ $(id -u) -ne 0 ] && die "You must be root to use sandbox."

# 0.2 cannot launch sandbox within sandbox
grep -q $SANDBOX_ROOT /sys/fs/aufs/$AUFS_ROOT_ID/br0 &&
die "Cannot launch sandbox within sandbox."

# 0.3 help
case "$1" in
	--help|-h)
	echo "Usage: ${0##*/} [cmd [args]]"
	echo "Starts an in-memory (throwaway) sandbox. Type 'exit' to leave."
	exit
esac

# 0.4 if not running from terminal but in Xorg, then launch via terminal
! [ -t 0 ] && [ -n "$DISPLAY" ] && exec $XTERM -e "$0" "$@"
! [ -t 0 ] && exit

# 0.5 is this the first sandbox? If not, then create another name for mountpoints
if grep -q $FAKEROOT /proc/mounts; then
	FAKEROOT=$(mktemp -d -p $SANDBOX_ROOT ${FAKEROOT##*/}.XXXXXXX)
	SANDBOX_ID=".${FAKEROOT##*.}"
	SANDBOX_TMPFS=$SANDBOX_ROOT/${SANDBOX_TMPFS##*/}${SANDBOX_ID}
	rmdir $FAKEROOT
fi

# 1. get aufs system-id for the root filesystem
[ -z "$AUFS_ROOT_ID" ] && AUFS_ROOT_ID=$(
awk '{ if ($2 == "/" && $3 == "aufs") { match($4,/si=[0-9a-f]*/); print "si_" substr($4,RSTART+3,RLENGTH-3) } }' /proc/mounts
)

# 2. get branches, then map branches to mount types or loop devices 
items=$(
{ echo ==mount==; cat /proc/mounts; 
  echo ==losetup==; losetup-FULL -a; 
  echo ==branches==; ls -v /sys/fs/aufs/$AUFS_ROOT_ID/br[0-9]* | xargs sed 's/=.*//'; } | awk '
  /==mount==/ { mode=1 }
  /==losetup==/ { mode=2 }
  /==branches==/ { mode=3 }
  {
	if (mode == 1) {
		# get list of mount points, types, and devices - index is $3 (mount points)
		mountdev[$2]=$1
		mounttypes[$2]=$3
	} else if (mode == 2) {
		# get list of loop devices and files - index is $1 (loop devs)
		sub(/:/,"",$1)
		sub(/.*\//,"",$3); sub(/)/,"",$3)
		loopdev[$1]=$3
	} else if (mode == 3) {
		# map mount types to loop files if mount devices is a loop
		for (m in mountdev) {
			if ( loopdev[mountdev[m]] != "" ) mounttypes[m]=loopdev[mountdev[m]]
		}
		# for (m in mountdev) print m " on " mountdev[m] " type " mounttypes[m]
		mode=4
	} else if (mode == 4) {
		# print the branches and its mappings
		print $0, mounttypes[$0], "on"
	}
  }  
'
)
# '
# 3. Ask user to choose the SFS
[ -z "$items" ] && die "No layered filesystem found."
dialog --separate-output --backtitle "tmpfs sandbox" --title "sandbox config" \
	--checklist "Choose which SFS you want to use" 0 60 10 $items 2> $TMPFILE
chosen="$(cat $TMPFILE)"

clear
[ -z "$chosen" ] && die "Cancelled or no SFS is chosen, exiting."

# 4. convert chosen SFS to robranches
robranches=""
for a in $(cat $TMPFILE) ; do
	robranches=$robranches:$a=ro
done
rm $TMPFILE

# 5. make the mountpoints if not exist  yet
mkdir -p $FAKEROOT $SANDBOX_TMPFS

# 6. do the magic - mount the tmpfs first, and then the rest with aufs
if mount -t tmpfs none $SANDBOX_TMPFS; then
	if mount -t aufs -o "br:$SANDBOX_TMPFS=rw$robranches" aufs $FAKEROOT; then
		# 7. record our new aufs-root-id so tools don't hack real filesystem	
		SANDBOX_AUFS_ID=$(grep $FAKEROOT /proc/mounts | sed 's/.*si=/si_/; s/ .*//') #'
		sed -i -e '/AUFS_ROOT_ID/ d' $FAKEROOT/etc/BOOTSTATE 2> /dev/null
		echo AUFS_ROOT_ID=$SANDBOX_AUFS_ID >> $FAKEROOT/etc/BOOTSTATE
		
		# 8. sandbox is ready, now just need to mount other supports - pts, proc, sysfs, usb and tmp
		mount -o rbind /dev $FAKEROOT/dev
		mount -t sysfs none $FAKEROOT/sys
		mount -t proc none $FAKEROOT/proc
		mount -o bind /tmp $FAKEROOT/tmp
		mkdir -p $FAKEROOT/$SANDBOX_TMPFS
		mount -o bind $SANDBOX_TMPFS $FAKEROOT/$SANDBOX_TMPFS	# so we can access it within sandbox
		
		# 9. optional copy, to enable running sandbox-ed xwin 
		cp /usr/share/sandbox/* $FAKEROOT/usr/bin 2> /dev/null
		
		# 10. make sure we identify ourself as in sandbox - and we're good to go!
		echo -e '\nexport PS1="sandbox'${SANDBOX_ID}'# "' >> $FAKEROOT/etc/shinit #fatdog 600
		sed -i -e '/^PS1/ s/^.*$/PS1="sandbox'${SANDBOX_ID}'# "/' $FAKEROOT/etc/profile # earlier fatdog
		echo "Starting sandbox now."
		if [ $USE_NS ]; then
			if type dumb-init > /dev/null 2>&1; then
				[ $# -eq 0 ] && DUMB_INIT="dumb-init -c /bin/sh" || DUMB_INIT="dumb-init -c"
			fi
			unshare -f -p --mount-proc=$FAKEROOT/proc chroot $FAKEROOT $DUMB_INIT "$@"
		else
			chroot $FAKEROOT
		fi

		# 11. done - clean up everything 
		umountall
		echo "Leaving sandbox."
	else
		echo "Unable to mount aufs br:$SANDBOX_TMPFS=rw$robranches"
		umount -l $SANDBOX_TMPFS		
	fi
else
	echo "unable to mount tmpfs."
fi
