#!/bin/sh
# James Budiono 2011, 2013, 2015
# puppy test/compilation sandbox
# this version uses rw image
# version 2 - will ask for SFS, and now we use dialog for rw image file location/creation
# version 3 - change awk/sed script combo to one sed script
# version 4 - replace sed script with awk, make sure it will handle oddball cases (loop-N and pup_ro-N 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

# 0. directory locations
. $BOOTSTATE_PATH # AUFS_ROOT_ID
XTERM="defaultterm"
SANDBOX_ROOT=/mnt/sb
FAKEROOT=$SANDBOX_ROOT/fakeroot   # mounted chroot location of sandbox - ie, the fake root
SANDBOX_IMG=$SANDBOX_ROOT/sandbox # mounted rw image of the sandbox disk image
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_IMG
	umount -l $FAKEROOT/tmp
	umount -l $FAKEROOT/proc
	umount -l $FAKEROOT/sys
	umount -l $FAKEROOT/dev
	
	umount -l $FAKEROOT
	umount -l $SANDBOX_IMG
	rmdir $FAKEROOT
	rmdir $SANDBOX_IMG
	} 2> /dev/null
}

# 0.1 must be root
if [ $(id -u) -ne 0 ]; then
	echo "You must be root to use sandbox."
	exit
fi

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

# 0.3 help
case "$1" in
	--help|-h)
	echo "Usage: ${0##*/} [/path/to/persistent.disk-image]"
	echo "Starts a persistent sandbox. Type 'exit' to leave."
	echo "Path is optional, you will be asked to select or create one if not specified."
	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_IMG=$SANDBOX_ROOT/${SANDBOX_IMG##*/}${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
dialog --separate-output --backtitle "rw image sandbox" --title "sandbox config" \
	--checklist "Choose which SFS you want to use" 0 60 10 $items 2> $TMPFILE
chosen="$(cat $TMPFILE)"

clear
if [ -z "$chosen" ]; then
	echo "Cancelled or no SFS is chosen - exiting."
	exit 1
fi

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

# 5. get location of rw image
if [ -z "$1" ]; then
	# location not specified - then ask
	dialog --backtitle "rw image sandbox" --title "choose rw image" \
	--extra-button --extra-label "Create" --ok-label "Locate" \
	--yesno "You didn't specify the location of the rw image file. Do you want to locate existing file, or do you want to create a new one?" 0 0
	case $? in
		0) # ok - locate
			dialog --backtitle "rw image sandbox" --title "Specify location of existing rw image" --fselect $(pwd) 8 60 2> $TMPFILE
			rwbranch=$(cat $TMPFILE)
			rm $TMPFILE
			if [ -n "$rwbranch" ]; then
				if [ ! -f "$rwbranch" ]; then
					echo "$rwbranch doesn't exist - exiting."
					exit					
				fi
			else
				echo "You didn't specify any file or you pressed cancel. Exiting."
				exit
			fi
			;;
		3) # create
			echo "create"
			dialog --backtitle "rw image sandbox" --title "Specify name and path of new the file" --fselect $(pwd) 8 60 2> $TMPFILE
			rwbranch=$(cat $TMPFILE)
			rm $TMPFILE
			if [ -n "$rwbranch" ]; then
				if [ -f "$rwbranch" ]; then
					echo "$rwbranch already exist - exiting."
					exit
				else
					# get the size
					dialog --title "Create new rw image" --inputbox "Specify size (in megabytes)" 0 40 100 2> $TMPFILE
					size=$(cat $TMPFILE)
					rm $TMPFILE
					
					if [ -n "$size" ]; then
						if dd if=/dev/zero of="$rwbranch" bs=1 count=0 seek="$size"M; then
							if ! mke2fs -F "$rwbranch"; then
								echo "I fail to make an ext2 filesystem at $rwbranch, exiting."
								exit
							fi
						else
							echo "I fail to create a ${size}M file at $rwbranch, exiting."
							exit
						fi
					else
						echo "You didn't specify the size or your press cancel. Exiting."
						exit
					fi					
				fi
			else
				echo "You didn't specify any file or you pressed cancel. Exiting."
				exit
			fi
			;;
		1 | 255) # 1 is cancel, 255 is Escape
			;&
		*) # invalid input - treat as cancel
			echo "Cancelled - exiting."
			exit
			;;
	esac
else
	# use what we've been given
	rwbranch="$1"
fi

# 4. make the mountpoints if not exist  yet
mkdir -p $FAKEROOT $SANDBOX_IMG

# 5. do the magic - mount the rw image first, and then the rest with aufs
if mount -o loop "$rwbranch" $SANDBOX_IMG; then
	if mount -t aufs -o "br:$SANDBOX_IMG=rw$robranches" aufs $FAKEROOT; then
		# 5. 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
		
		# 5. 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_IMG
		mount -o bind $SANDBOX_IMG $FAKEROOT/$SANDBOX_IMG	# so we can access it within sandbox
		
		# 6. optional copy, to enable running sandbox-ed xwin 
		cp /usr/share/sandbox/* $FAKEROOT/usr/bin 2> /dev/null
		
		# 7. 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
			unshare -f -p --mount-proc=$FAKEROOT/proc chroot $FAKEROOT
		else
			chroot $FAKEROOT
		fi

		# 8. done - clean up everything 
		umountall
		echo "Leaving sandbox."
	else
		echo "Unable to mount aufs br:$SANDBOX_IMG=rw$robranches"
		umount -l $SANDBOX_IMG	
	fi
else
	echo "unable to mount rw image: $rwbranch"
fi
