#!/bin/sh
# James Budiono 2011, 2013, 2015, 2017, 2018, 2020, 2024
# Run a bunch of SFS-es in a sandbox.
# This is the combined version of sfs-sandbox.sh, supports standard, LXC and UML mode
#
# Forked from rw-sandbox.sh version 14 + rw-sandbox-lxc.sh version 10.
# This tool will work with either aufs or overlayfs
#
# Basic usage: sfs-sandbox.sh rw-image sfs sfs ... -- cmd args
#
# All optional. rw-image/sfs can be file, directory, devices, etc
# OS_IMAGE=1 without sfs-layer only works with aufs, not with overlay,
# because overlay needs both upper and workdir on the same filesystem (and they cant be subtrees)
# Note that rw-image used for aufs/overlay/unionfs CANNOT be shared, as they have different structure.
#

### configuration variables
. $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
SFSMOUNT=${SANDBOX_IMG}+sfs       # mounted sfs-es
SANDBOX_ID=
LXC_ID=sb-lxc	# LXC container ID
UML_ID=sb-uml   # UML container ID

### common run-time variables
LAYERING=${LAYERING:-aufs} # aufs, overlay, union
SANDBOX_MODE=${SANDBOX_MODE:-std} # std, lxc, or uml
#QUIET=       # don't print branch details
#USE_DESKTOP= # blank (no), yes
WINDOW_SIZE=${WINDOW_SIZE:-1024x600}

### std-only variables
#NO_NS=1      # don't user namespace, do chroot only instead
#NO_TMP=1     # don't share /tmp

### LXC/UML variables
#USE_NETWORK=  # blank (none), proxyarp, or nat
#SHARE_SOUND=  # blank (no), yes
#SHARE_IPC=    # blank (no), yes
#HOST_ADAPTER= # which adapter to use for auto-calculate
#HOST_IP=      # auto-calculate if blank
#GUEST_IP=     # auto-calculate if blank
NAME_SERVER=${NAME_SERVER:-8.8.8.8}
#OS_IMAGE=     # image file is a OS image

### LXC-only variables
#IDMAP=      # enable IDMAP=yes on env to use user namespaces
LXC_UID=${LXC_UID:-100000}          # UID and GID when running under user namespaces
LXC_GID=${LXC_GID:-100000}
LXC_IDRANGE=${LXC_IDRANGE:-65536}   # no of ids to map under user name spaces
#LXC_EXTRA=  # whatever extra flags you want to pass to LXC - no command line equivalent

### UML-only variables
UML_SRC_DIR=/usr/lib64/uml
VMLINUX=$UML_SRC_DIR/vmlinux
RAM_SIZE=${RAM_SIZE:-256M}
#NICE_LEVEL= # nice level for UML kernel
#UML_EXTRA=  # additional UML boot options

sfslist=  # file containing list of SFS files to be loaded
sfs=      #
rwbranch= #
sbsep=.   # sandbox separator (dot for standard sandbox, dash for LXC sandbox)
XSERVER_PID=
XSERVER_DISP=

############## helpers ##############

### trap handler
# umount all and stop lxc if we are accidentally killed
trap '
[ $CONSOLE_PID ] && kill $CONSOLE_PID; 
[ "$SANDBOX_MODE" = lxc ] && lxc-stop -n $LXC_ID;
umountall' HUP
umountall() {
	{
	[ $XSERVER_PID ] && kill $XSERVER_PID;		
	if [ "$SANDBOX_MODE" = lxc ]; then
		lxc-wait -n $LXC_ID -s STOPPED
		stop_lxc_network
	fi
	
	umount -l $FAKEROOT/$SANDBOX_IMG
	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 $SFSMOUNT/*
	umount -l $SANDBOX_IMG

	rmdir $FAKEROOT
	rmdir $SANDBOX_IMG
	rmdir $SFSMOUNT/*
	rmdir $SFSMOUNT
	} 2> /dev/null
}

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

help() {
	cat << EOF	
${0##*/} [flags] [-f sfslist ] rw-image [[sfs1 sfs2 ...] -- [cmd [args]]]

Starts a sandbox (standard, LXC or UML).
- rw-image can be a file, or a directory. The state of the sandbox will be
  persistently stored in the rw-image. If rw-image is "tmpfs", or an empty
  string, a throwaway session will be created.
- sfslist is a file containing list of sfs to preload. One sfs per line.
  It is optional.
- sfs1, sfs2, etc are also optional. They can be a file, or a directory.
  They are mounted read-only. sfs1 is the top layer.
- Separate sfs-es from command using two dashes (--).

Layering flags:
--aufs       : use aufs (default)
--overlay    : use overlayfs
--union      : use unionfs-fuse

Operation mode flags:
--std        : start standard mode sandbox (default)
--lxc        : start LXC sandbox 
--uml        : start UML sandbox 

Common flags shared by all modes:
--win [size] : desktop window size for --desktop (default is $WINDOW_SIZE)

Flags applicable only in std mode:
--desktop    : use separate desktop (default: share host desktop)
--no-tmp     : do not share host /tmp (cannot share host desktop)
--chroot     : use chroot instead of namespace separation

Flags applicable only in LXC/UML mode:
--desktop    : use desktop (must also enable network), default: no desktop
--os-image   : rw-image is an OS image, don't do any specific Fatdog setup
--sound      : share host's sound (default: no sound)

--net [type] : use network, either 'proxyarp' or 'nat', default: no network
--host-adapter [adapter]: the network adapter to use for auto-calculate
--host-ip    : host-side ip address, default: auto-calculate
--guest-ip   : guest-side ip address, default: auto-calculate
--dns        : the DNS server for guest network (default $NAME_SERVER)

Flags applicable only in LXC mode:
--ipc        : share host's IPC
--idmap      : use username space uid/gid mapping
--uid [val]  : real uid which represents root in the sandbox (default 100000)
--gid [val]  : real gid which represents root in the sandbox (default 100000)
--idrange [val] : how many uids to be mapped (default 65536)
--log [path] : path to logfile to see lxc errors

Flags applicable only in UML-mode:
--ram-size   : RAM size (default is $RAM_SIZE)
--nice-level : niceness level for UML kernel

Note1: the same rw-image cannot be shared/reused for different layering models
       (aufs/overlay/union) because the internal structure used by each
       layering system is different and is not compatible with one another.

Note2: a pure os-image (os-image contained in a disk image, used directly as
       the rw-image without any additional sfs-es) is only possible with aufs.

EOF
	exit 
}

# get random from $1 to $2 (inclusive)
get_random() {
	awk -v min=$1 -v max=$2 'BEGIN{srand(); print int(min+rand()*(max-min+1))}'
}

# $1-ip, $2-which octet, $3-by how much
adjust_ipv4() {
	awk -F. -v ip=$1 -v octet=$2 -v howmuch=$3 \
'BEGIN {
	$0=ip
	$octet=($octet + howmuch) % 256
	# last octet cannot be 255 (bcast), 0 (net) or 1 (gw)
	if (octet == 4) 
		if ($octet == 255 || $octet == 0 || $octet == 1) $octet=2
	printf("%d.%d.%d.%d", $1,$2,$3,$4);
}'
}

### network setup handlers

# input: HOST_IP, HOST_ADAPTER
# output: HOST_IP, HOST_NET, HOST_GW, HOST_GW_DEV
get_host_net() {
	local ipcidr=$HOST_IP
	if [ -z "$HOST_IP" ]; then
		[ "$HOST_ADAPTER" ] && ipcidr=$(busybox ipaddr show dev $HOST_ADAPTER | awk '$1=="inet" && $2 !~ /127.0.0.1/ {print $2}' | head -n1) ||
		ipcidr=$(busybox ipaddr | awk '$1=="inet" && $2 !~ /127.0.0.1/ {print $2}' | head -n1)
		HOST_IP=${ipcidr%/*}
	fi
	HOST_NET=$(busybox ipcalc -n $ipcidr); HOST_NET=${HOST_NET#*=}
	HOST_PREFIX=$(busybox ipcalc -p $ipcidr); HOST_PREFIX=${HOST_PREFIX#*=}
	HOST_GW=$(busybox iproute | awk '$1=="default" { print $3}' | head -n1)
	HOST_GW_DEV=$(busybox iproute | awk '$1=="default" { print $5}' | head -n1)
}

# output: UML_NETWORK
prepare_uml_network() {
	# set guest-ip/host-ip address unless already preset
	get_host_net

	# standard network: proxy arp
	if [ "$USE_NETWORK" = proxyarp ]; then
		[ "$GUEST_IP" ] || GUEST_IP=$(adjust_ipv4 $HOST_IP 4 $(get_random 1 255))
	# NAT network
	else
		[ "$GUEST_IP" ] || GUEST_IP=$(adjust_ipv4 $HOST_IP 3 $(get_random 1 255))
		HOST_IP=$(adjust_ipv4 $GUEST_IP 4 $(get_random 1 255)) # needs to be adjusted
		iptables -t nat -A POSTROUTING -o $HOST_GW_DEV -j MASQUERADE
	fi
	cat > $FAKEROOT/sbin/netstart-sb-uml << EOF
#!/bin/sh
echo Setting up network ...
ifconfig lo up
ifconfig eth0 $GUEST_IP up
route add default gw $HOST_IP
echo nameserver $NAME_SERVER > /etc/resolv.conf 
EOF
	chmod +x $FAKEROOT/sbin/netstart-sb-uml
	UML_NETWORK="eth0=tuntap,,,$HOST_IP HOST_IP=$HOST_IP UML_IP=$GUEST_IP NAME_SERVER=$NAME_SERVER"
}

# input:  LXC_ID HOST_IP GUEST_IP USE_NETWORK OS_IMAGE
# output: updated /etc/hosts /etc/hostname /etc/rc.d/rc.network.lxc /sbin/netstart-sb-lxc
#         HOST_IP GUEST_IP HOST_NET HOST_PREFIX HOST_GW HOST_GW_DEV
#         GUEST_NET GUEST_PREFIX GUEST_GW
prepare_lxc_network() {
	local guest_start_file

	# hostname
	sed -i -e "/${LXC_ID}/ d" $FAKEROOT/etc/hosts
	echo "127.0.0.1 ${LXC_ID}" >> $FAKEROOT/etc/hosts
	echo "${LXC_ID}" > $FAKEROOT/etc/hostname
	
	# compute host network details (ip, net, gw, gw-dev)
	get_host_net

	# compute guest network details
	case "$USE_NETWORK" in
		proxyarp)
			if [ -z "$GUEST_IP" ]; then
				# replace 4th octet
				GUEST_IP=$(adjust_ipv4 $HOST_IP 4 $(get_random 1 255))
			fi
			#    type         guest-ip guest-net guest-prefix gw       host-ip
			echo $USE_NETWORK $GUEST_IP  $HOST_NET $HOST_PREFIX $HOST_GW $HOST_IP > $FAKEROOT/etc/netparams
			;;

		nat)
			if [ -z "$GUEST_IP" ]; then
				# replace 3rd octet
				GUEST_IP=$(adjust_ipv4 $HOST_IP 3 $(get_random 1 255))
			fi
			GUEST_NET=$(busybox ipcalc -n $GUEST_IP); GUEST_NET=${GUEST_NET#*=}
			GUEST_PREFIX=$(busybox ipcalc -p $GUEST_IP); GUEST_PREFIX=${GUEST_PREFIX#*=}
			GUEST_GW=$(adjust_ipv4 $GUEST_IP 4 $(get_random 1 255))
			#    type         guest-ip guest-net  guest-prefix  gw        host-ip
			echo $USE_NETWORK $GUEST_IP  $GUEST_NET $GUEST_PREFIX $GUEST_GW $HOST_IP > $FAKEROOT/etc/netparams
			;;
		
		"") rm -f $FAKEROOT/etc/netparams ;;
	esac
	
	# create guest network init script
	[ -z "$OS_IMAGE" ] && guest_start_file=$FAKEROOT/etc/rc.d/rc.network.lxc ||
	guest_start_file=$FAKEROOT/sbin/netstart-sb-lxc
	
	! [ -e $guest_start_file ] && cat > $guest_start_file << "EOF" &&
#!/bin/sh
[ -e /etc/netparams ] || exit

read type ip net prefix gw host < /etc/netparams
#echo $type $ip $net/$prefix $gw $host

[ $type = proxyarp ] &&
echo 1 > /proc/sys/net/ipv4/conf/all/proxy_arp ||
echo 0 > /proc/sys/net/ipv4/conf/all/proxy_arp

if type busybox > /dev/null && busybox ipaddr > /dev/null 2>&1; then
busybox iplink set lo up; iplink set eth0 up # bring interfaces up
busybox ipaddr add $ip dev eth0              # add own ip address
busybox iproute add $net/$prefix dev eth0    # add own route
busybox iproute add default via $gw dev eth0 # add default gw
else # if busybox
ip link set lo up; ip link set eth0 up # bring interfaces up
ip addr add $ip dev eth0               # add own ip address
ip route add $net/$prefix dev eth0     # add own route
ip route add default via $gw dev eth0  # add default gw
fi  # if busybox
echo nameserver $NAME_SERVER > /etc/resolv.conf 
EOF
	chmod +x $guest_start_file
}

# input: USE_NETWORK, GUEST_IP HOST_IP GUEST_GW GUEST_NET GUEST_PREFIX HOST_GW_DEV
start_lxc_network() {
	case "$USE_NETWORK" in
		proxyarp)
			echo 1 > /proc/sys/net/ipv4/ip_forward         # enable ip forwarding
			echo 1 > /proc/sys/net/ipv4/conf/all/proxy_arp # enable proxy arp
			busybox iplink set $LXC_ID up           # enable interface
			busybox ipaddr add $HOST_IP dev $LXC_ID # assign ip address, same as host
			busybox iproute add $GUEST_IP dev $LXC_ID # add routing to sandbox
			;;
		
		nat)
			echo 1 > /proc/sys/net/ipv4/ip_forward # enable ip forwarding
			busybox iplink set $LXC_ID up             # enable interface
			busybox ipaddr add $GUEST_GW dev $LXC_ID  # assign guest gw ip address
			busybox iproute add $GUEST_NET/$GUEST_PREFIX dev $LXC_ID # add guest net routing
			iptables -t nat -A POSTROUTING -o $HOST_GW_DEV -j MASQUERADE
			;;
	esac
}

# input: USE_NETWORK, HOST_GW_DEV
stop_lxc_network() {
	case "$USE_NETWORK" in
		proxyarp) ;; # nothing to do
		nat) iptables -t nat -D POSTROUTING -o $HOST_GW_DEV -j MASQUERADE ;;
	esac
}


#### desktop emulation helpers
# input:  WINDOW_SIZE
# output: XSERVER_PID, XSERVER_DISP 
start_xserver() {
	# get free display number
	for XSERVER_DISP in $(seq 1 10); do
		[ ! -e /tmp/.X${XSERVER_DISP}-lock ] && break
	done

	if type Xephyr > /dev/null 2>&1; then
		Xephyr :$XSERVER_DISP -listen tcp -ac -host-cursor -screen $WINDOW_SIZE -keybd ephyr,,xkbmodel=evdev,xkbrules=evdev > /dev/null 2>&1 &
		XSERVER_PID=$!	
	elif type Xnest > /dev/null 2>&1; then
		Xnest :$XSERVER_DISP -listen tcp -ac -geometry $WINDOW_SIZE > /dev/null 2>&1 &
		XSERVER_PID=$!
	else
		echo "WARNING: Cannot find Xephyr or Xnest, unable to start desktop emulation."
	fi

	cat > $FAKEROOT/etc/X11/host-x11 << EOF
#!/bin/sh
# create the X11 socket dir if it doesn't exist yet
if ! [ -d /tmp/.X11-unix ]; then
	mkdir -p /tmp/.X11-unix
	chmod 1777 /tmp/.X11-unix
fi
export DISPLAY='$HOST_IP:$XSERVER_DISP'
EOF
 
	cp /usr/bin/xwin $FAKEROOT/usr/bin
	sed -i -e '\_xinit $HOME/.xinitrc_ s|xinit|. /etc/X11/host-x11;|' $FAKEROOT/usr/bin/xwin
	cp $UML_SRC_DIR/wmexit $FAKEROOT/usr/bin # we use UML's wmexit
}



############## main ##############

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

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

# 0.3 process cmdline
[ $# -eq 0 ] && help
while [ $# -gt 0 ]; do
	case "$1" in
		--help|-h)           help ;;
		--file|-f)           shift; sfslist="$1" ;;
				
		--aufs)              LAYERING="aufs"  ;;
		--overlay|--overlayfs) LAYERING="overlayfs" ;;
		--union|--unionfs)   LAYERING="unionfs" ;;

		--std)               SANDBOX_MODE=std ;;
		--chroot|--no-ns)    SANDBOX_MODE=std; NO_NS=1 ;;
		--lxc)               SANDBOX_MODE=lxc; sbsep=- ;;
		--uml)               SANDBOX_MODE=uml ;;

		--no-tmp)            NO_TMP=1 ;;

		--net)               shift; USE_NETWORK="$1" ;;
		--desktop)           USE_DESKTOP=yes ;;
		--sound)             SHARE_SOUND=yes ;;
		--ipc)               SHARE_IPC=yes   ;;
		--win)               shift; WINDOW_SIZE="$1" ;;
		--os-image)          OS_IMAGE=1 ;; 

		--host-adapter)      shift; HOST_ADAPTER="$1" ;;
		--host-ip)           shift; HOST_IP="$1" ;;
		--guest-ip)          shift; GUEST_IP="$1"  ;;
		--dns)               shift; NAME_SERVER="$1" ;;

		--idmap)             IDMAP=yes ;;
		--uid)               shift; LXC_UID="$1" ;;
		--gid)               shift; LXC_GID="$1" ;;
		--idrange)           shift; LXC_IDRANGE="$1" ;;
		--log)               shift; LXC_LOG="$1" ;;		

		--ram-size)          shift; RAM_SIZE="$1" ;;
		--nice)              shift; NICE_LEVEL="$1" ;;
		*) break ;;
	esac
	shift
done
[ $# -gt 0 ] && rwbranch=$1 && shift

# 0.4 cannot launch sandbox within sandbox
if [ "$SANDBOX_MODE" = lxc ]; then
	if ! type lxc-start > /dev/null 2>&1; then
		die "LXC not found, cannot start LXC-mode sandbox."
	fi
	if grep -q LXC_ID $BOOTSTATE_PATH ||
	   [ "$AUFS_ROOT_ID" = overlay ]; then
	   die "Cannot launch sandbox within sandbox."
	fi
else
	if grep -q $SANDBOX_ROOT /sys/fs/aufs/$AUFS_ROOT_ID/br0 ||
	   [ "$AUFS_ROOT_ID" = overlay ]; then
	   die "Cannot launch sandbox within sandbox."
	fi
fi
[ "$LAYERING" = union ] && ! type unionfs > /dev/null 2>&1 &&
die "Cannot find unionfs." 

# 0.5 load list of files, if any 
if [ "$sfslist" ]; then
	sfs=
	while read p; do
		case "$p" in
			\#*|"") continue ;;
			*) sfs="$sfs $p"
		esac
	done < "$sfslist"
	set -- $sfs "$@"
fi

# 0.6 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##*/}${sbsep}XXXXXXX)
	SANDBOX_ID="${sbsep}${FAKEROOT##*${sbsep}}"
	SANDBOX_IMG=$SANDBOX_ROOT/${SANDBOX_IMG##*/}${SANDBOX_ID}
	SFSMOUNT=${SANDBOX_IMG}+sfs
	LXC_ID=${LXC_ID}${SANDBOX_ID}
	UML_ID=${UML_ID}${SANDBOX_ID}
	rmdir $FAKEROOT
fi

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

# 2. mount the rw image first
if [ -z "$rwbranch" ] || [ "$rwbranch" = tmpfs ]; then
	mount -t tmpfs tmpfs $SANDBOX_IMG
elif [ -d "$rwbranch" ]; then
	mount -o bind "$rwbranch" $SANDBOX_IMG
else
	mount "$rwbranch" $SANDBOX_IMG
fi || {
	echo "Unable to mount rw-image: $rwbranch"
	umountall
	exit
}
[ "$SANDBOX_MODE" = lxc ] && chmod 0777 $SANDBOX_IMG

# 3. then mount the sfs-es
SFSCOUNT=1
robranches=
while [ $# -gt 0 ]; do
	p="$1"; shift;
	[ $p = "--" ] && break
	mnt=$SFSMOUNT/$(printf "%0.3d" $SFSCOUNT)
	mkdir -p $mnt
	if [ -d "$p" ]; then
		mount -o bind "$p" $mnt
	else
		mount "$p" $mnt
	fi || {
		[ "$QUIET" ] || printf "Unable to mount branch %0.3d : %s\n" $SFSCOUNT "$p"
		umountall
		exit
	}
	[ "$QUIET" ] || printf "Branch %0.3d : %s\n" $SFSCOUNT "$p"
	robranches=$robranches:$mnt=ro
	: $(( SFSCOUNT++ ))
done

# 4. do the magic
[ "$QUIET" ] || echo "Using $LAYERING"
NOTGOOD=
case $LAYERING in
	aufs)
		if ! mount -t aufs -o "br:$SANDBOX_IMG=rw$robranches" aufs $FAKEROOT; then
			echo "Unable to mount aufs br:$SANDBOX_IMG=rw$robranches"
			NOTGOOD=1
		fi
		;;
		
	overlay|overlayfs)
		mkdir -p $SANDBOX_IMG/upper $SANDBOX_IMG/work
		[ "$robranches" ] && robranches=",lowerdir=$(echo $robranches | sed 's/=ro//g; s/^://')"
		if ! mount -t overlay -o "xino=on,upperdir=$SANDBOX_IMG/upper,workdir=$SANDBOX_IMG/work$robranches" overlay $FAKEROOT; then
			echo "Unable to mount overlay upperdir=$SANDBOX_IMG/upper,workdir=$SANDBOX_IMG/work$robranches"
			NOTGOOD=1
		fi
		;;

	union|unionfs)
		robranches=$(echo $robranches | sed 's/=ro/=RO/g;')
		# NOTE: we want to use use_ino too, but it seems to be removed in recent fuse3.x
		if ! unionfs -o cow,hide_meta_files,allow_other,suid,dev,max_files=65536 "$SANDBOX_IMG=RW$robranches" $FAKEROOT; then
			echo "Unable to mount unionfs"
			NOTGOOD=1
		fi
		;;
	*)
		echo "Unknown layering option: $LAYERING"
		NOTGOOD=1
		;;
esac

# now run the sandbox according to the chose node
[ -z $NOTGOOD ] && case "$SANDBOX_MODE" in



############## STD/UNSHARE/CHROOT SANDBOX ##############
	std) # case $SANDBOX_MODE			
		# 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
		
		# 6. sandbox is ready, now just need to mount other supports - pts, proc, sysfs, usb and tmp
		! [ -d $FAKEROOT/dev ] && mkdir -p $FAKEROOT/dev
		! [ -d $FAKEROOT/sys ] && mkdir -p $FAKEROOT/sys
		! [ -d $FAKEROOT/proc ] && mkdir -p $FAKEROOT/proc
		! [ -d $FAKEROOT/tmp ] && mkdir -p $FAKEROOT/tmp
		mount -o rbind /dev $FAKEROOT/dev
		mount -t sysfs none $FAKEROOT/sys
		mount -t proc none $FAKEROOT/proc
		if [ -z "$NO_TMP" ]; then
			mount -o bind /tmp $FAKEROOT/tmp
		else
			[ "$XDG_RUNTIME_DIR/" ] && mkdir -p $FAKEROOT/$XDG_RUNTIME_DIR &&
			chmod 700 $FAKEROOT/$XDG_RUNTIME_DIR
		fi
		mkdir -p $FAKEROOT/$SANDBOX_IMG
		if [ "$LAYERING" = aufs ]; then
			# so we can access it within sandbox, but we don't do this for overlay because unlike aufs,
			# overlay will get mad if we modify the underlying filesystem while it is running
			mount -o bind $SANDBOX_IMG $FAKEROOT/$SANDBOX_IMG	
		fi
		
		# 7. make sure we identify ourself as in sandbox - and we're good to go!
		sed -i -e '/^PS1/ s/^.*$/PS1="sandbox'${SANDBOX_ID}'# "/' $FAKEROOT/etc/profile # other puppies	
		grep -q "export PS1=" $FAKEROOT/etc/shinit &&
		sed -i -e "s/PS1=.*/PS1='sandbox${SANDBOX_ID}# '/" $FAKEROOT/etc/shinit ||
		echo -e '\nexport PS1="sandbox'${SANDBOX_ID}'# "' >> $FAKEROOT/etc/shinit #fatdog 600
		grep -q "export TERM=" $FAKEROOT/etc/shinit &&
		sed -i -e "s/TERM=.*/TERM=$TERM/" $FAKEROOT/etc/shinit ||
		echo "export TERM=$TERM" >> $FAKEROOT/etc/shinit  # default TERM is screwed, so use host's

		# 8. optional copy, to enable running sandbox-ed xwin
		if [ -z "$USE_DESKTOP" ]; then
			# share host desktop
			cp /usr/share/sandbox/* $FAKEROOT/usr/bin 2> /dev/null
		else
			# use separate desktop
			start_xserver
			# apply immediately, but we still need to create the X11 socket dir
			. $FAKEROOT/etc/X11/host-x11
			if ! [ -d $FAKEROOT/tmp/.X11-unix ]; then
				mkdir -p $FAKEROOT/tmp/.X11-unix
				chmod 1777 $FAKEROOT/tmp/.X11-unix
			fi
			cp /usr/share/sandbox/wmexit $FAKEROOT/usr/bin 2> /dev/null # use our own wmext
			unset XSESSION_ID # make sure not to mess up with host			
		fi

		# 9. launch sandbox, use namespaces if available (kernel support and tool support)
		unset SHELL # so we don't use shell that doesn't exist in the sandbox
		if [ -e /proc/1/ns/pid ] && [ -e /proc/1/ns/mnt ] && type unshare >/dev/null && [ -z $NO_NS ];
		then
			unset START_CMD
			for p in /usr/bin /usr/sbin /bin /sbin; do
				[ -e $FAKEROOT/$p/dumb-init ] && START_CMD="$p/dumb-init -c" && break
			done
			if [ "$START_CMD" ]; then
				[ $# -eq 0 ] && START_CMD="$START_CMD /bin/sh"
			fi
			unshare -f -C -u -p --mount-proc=$FAKEROOT/proc chroot $FAKEROOT $START_CMD "$@"
		else
			chroot $FAKEROOT "$@"
		fi
		;;


		
############## LXC SANDBOX ##############
	lxc) # case $SANDBOX_MODE
		# 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; /LXC_ID/ d' $FAKEROOT/$BOOTSTATE_PATH 2> /dev/null
		echo AUFS_ROOT_ID=$SANDBOX_AUFS_ID >> $FAKEROOT/$BOOTSTATE_PATH
		echo LXC_ID=$LXC_ID >> $FAKEROOT/$BOOTSTATE_PATH

		# 6. sandbox is ready, prepare /dev /proc /sys mountpoints, and create minimal /dev
		mkdir -p $FAKEROOT/dev $FAKEROOT/proc $FAKEROOT/sys
		mknod $FAKEROOT/dev/tty c 5 0; mknod $FAKEROOT/dev/console c 5 1; mkdir $FAKEROOT/dev/pts
		for dev in "null-3" "zero-5" "full-7" "random-8" "urandom-9"; do
			mknod $FAKEROOT/dev/${dev%-*} c 1 ${dev#*-}
		done
		mkdir -p $FAKEROOT/$SANDBOX_IMG
		if [ "$LAYERING" = aufs ]; then
			# so we can access it within sandbox, but we don't do this for overlay because unlike aufs,
			# overlay will get mad if we modify the underlying filesystem while it is running
			mount -o bind $SANDBOX_IMG $FAKEROOT/$SANDBOX_IMG
		fi

		# 7. make sure we identify ourself as in sandbox
		sed -i -e '/^PS1/ s/^.*$/PS1="sandbox-lxc'${SANDBOX_ID}'# "/' $FAKEROOT/etc/profile # other puppies	
		grep -q "export PS1=" $FAKEROOT/etc/shinit &&
		sed -i -e "s/PS1=.*/PS1='sandbox-lxc${SANDBOX_ID}# '/" $FAKEROOT/etc/shinit ||
		echo -e '\nexport PS1="sandbox-lxc'${SANDBOX_ID}'# "' >> $FAKEROOT/etc/shinit #fatdog 600
		grep -q "export TERM=" $FAKEROOT/etc/shinit &&
		sed -i -e "s/TERM=.*/TERM=$TERM/" $FAKEROOT/etc/shinit ||
		echo "export TERM=$TERM" >> $FAKEROOT/etc/shinit  # default TERM is screwed, so use host's

		if [ -z "$OS_IMAGE" ]; then	
			# 8. prepare configuration for /sbin/init 
			rm $FAKEROOT/sbin/init; cp $BASELINE_MOUNT/sbin/init $FAKEROOT/sbin/init
			cp /usr/share/sandbox/lxc/inittab $FAKEROOT/etc/inittab
			# make sure we can shutdown, remove the acpi_poweroff tests
			sed -i -e '/acpi_poweroff/d' $FAKEROOT/sbin/poweroff $FAKEROOT/sbin/reboot	

			# 9. create blank sysinit/shutdown scripts for later customisation
			unset LXC_START_CMD
			if ! [ -e $FAKEROOT/etc/rc.d/rc.sysinit.lxc ]; then
				echo "#!/bin/sh" > $FAKEROOT/etc/rc.d/rc.sysinit.lxc; chmod +x $FAKEROOT/etc/rc.d/rc.sysinit.lxc		
			fi
			if ! [ -e $FAKEROOT/etc/rc.d/rc.shutdown.lxc ]; then
				echo "#!/bin/sh" > $FAKEROOT/etc/rc.d/rc.shutdown.lxc; chmod +x $FAKEROOT/etc/rc.d/rc.shutdown.lxc		
			fi

			# 9.1 append oneshot commands
			# Note: We don't use lxc.init_cmd because we want to keep using /sbin/init
			if [ $# -gt 0 ]; then
				{	echo "#!/bin/sh" 
					for p in "$@"; do printf "'%s' " "$p"; done
					echo
				} > $FAKEROOT/etc/rc.d/rc.oneshot
				chmod +x $FAKEROOT/etc/rc.d/rc.oneshot
			else
				rm -f $FAKEROOT/etc/rc.d/rc.oneshot
				sed -i -e '/rc.oneshot/d' $FAKEROOT/etc/inittab 
			fi

		else ### if OS_IMAGE
			# 8. prepare /bin/sh or one-shot command for init
			LXC_START_CMD="/sbin/init-sb-lxc"
			cat > $FAKEROOT/$LXC_START_CMD << EOF
#!/bin/sh
export HOME=/root
export TERM=$TERM
mkdir -p /dev/shm
mount -t tmpfs tmpfs /dev/shm
[ -x /sbin/netstart-sb-lxc ] && /sbin/netstart-sb-lxc
[ -e /etc/X11/host-x11 ] && . /etc/X11/host-x11
exec < /dev/tty1 > /dev/tty1 2>&1
EOF
			chmod +x $FAKEROOT/$LXC_START_CMD
			if [ $# -gt 0 ]; then
				{ printf "exec "
				for p in "$@"; do printf "'%s' " "$p"; done
				echo
				} >> $FAKEROOT/$LXC_START_CMD
			else
				echo "exec /bin/sh" >> $FAKEROOT/$LXC_START_CMD
			fi
		fi ### if OS_IMAGE

		# 10. LXC configuration
		[ "$LXC_LOG" ] && LXC_LOG="-o $LXC_LOG -l TRACE"
		[ "$SHARE_SOUND" = "yes" ] && LXC_SHARE_SOUND="-s lxc.mount.entry=\"/dev/snd dev/snd none bind,optional,create=dir\""
		[ "$SHARE_IPC" = "yes" ] && LXC_SHARE_IPC="--share-ipc=1" # share ipc from root container
		[ "$IDMAP" ] && {
			echo "#required for sandbox-lxc" > /etc/subuid
			echo "#required for sandbox-lxc" > /etc/subgid
			echo root:$LXC_UID:$LXC_IDRANGE >> /etc/subuid
			echo root:$LXC_GID:$LXC_IDRANGE >> /etc/subgid
		}
		
		# 10.1 LXC setup when running with user namespaces 
		if  [ "$IDMAP" ] && zcat /proc/config.gz | grep -q CONFIG_USER_NS=y; then
			echo "User namespaces enabled, running as uid/gid $LXC_UID/$LXC_GID"
			LXC_AUTO_MOUNT="-s lxc.mount.auto='proc cgroup sys'"
			LXC_UID_MAP="-s lxc.id_map='u 0 $LXC_UID $LXC_IDRANGE'"
			LXC_GID_MAP="-s lxc.id_map='g 0 $LXC_GID $LXC_IDRANGE'"
			LXC_DROP_CAP=
			chown -Rh $LXC_UID:$LXC_GID $FAKEROOT/dev
			if ! [ -e $FAKEROOT/etc/rc.d/rc.sysinit.lxc ]; then
				#rm -rf $FAKEROOT/root; cp -a /root $FAKEROOT
				#rm -rf $FAKEROOT/home; cp -a /home $FAKEROOT
				chown     $LXC_UID:$LXC_GID $FAKEROOT/ $FAKEROOT/home
				chown -Rh $LXC_UID:$LXC_GID $FAKEROOT/etc $FAKEROOT/root
				if [ "$LAYERING" = aufs ]; then # workaround for aufs
					mv $FAKEROOT/root $FAKEROOT/root-new
					mv $FAKEROOT/root-new $FAKEROOT/root
				fi
			fi
		else
			echo "*Not* using user namespaces, dropping capabilities"
			LXC_AUTO_MOUNT="-s lxc.mount.auto='proc cgroup sys'"
			LXC_UID_MAP= 
			LXC_GID_MAP=
			LXC_DROP_CAP="-s lxc.cap.drop='sys_module sys_time sys_rawio syslog'"
		fi

		# 11. network connection
		prepare_lxc_network
		
		# 12. desktop setup can only be done if network is setup
		[ "$USE_DESKTOP" ] && [ "$USE_NETWORK" ] && start_xserver

		# 13. We're good to go!
		echo "Starting sandbox (id: ${LXC_ID}) now."
		#LXC_START_CMD="-- busybox getty -n -l /bin/autologin 38400 tty1"
		# NOTE: lxc.environment can be used to pass "boot parameter" to init	
		eval lxc-start -d -f /etc/lxc/default.conf -n $LXC_ID  \
					-s lxc.devttydir=lxc -s lxc.pts=10 -s lxc.tty=4 \
					-s lxc.rootfs=$FAKEROOT \
					-s lxc.utsname=${LXC_ID} -s lxc.network.type=veth -s lxc.network.veth.pair=${LXC_ID} \
					-s lxc.cgroup.devices.deny=a -s lxc.cgroup.devices.allow=\"c *:* rwm\" \
					-s lxc.autodev=0 $LXC_AUTO_MOUNT \
					$LXC_SHARE_IPC $LXC_SHARE_SOUND $LXC_DROP_CAP $LXC_UID_MAP $LXC_GID_MAP $LXC_LOG \
					$LXC_EXTRA $LXC_START_CMD
		sleep 1		# allow some time for container to start

		# 14. Post-process if we use network
		if [ "$USE_NETWORK" ]; then
			# wait for the thing to show up
			TRY=30 # 30 attempts is enough
			while ! iplink show $LXC_ID > /dev/null 2>&1 && [ $TRY -gt 0 ]; do
				sleep 0.5
				: $((TRY = TRY-1))
			done
			start_lxc_network
		fi

		# 15. start console and restart until container is terminated
		set -m # we need console's pid so we can kill it later		
		while true; do
			lxc-console -q -e g -n "$LXC_ID" -t 1 &
			CONSOLE_PID=$!
			fg %%
			lxc-info -n $LXC_ID >/dev/null 2>&1 || break
		done
		;;



############## UML SANDBOX ##############
	uml) # case $SANDBOX_MODE
		# 5. make sure we identify ourself as in sandbox
		sed -i -e '/^PS1/ s/^.*$/PS1="sandbox-uml'${SANDBOX_ID}'# "/' $FAKEROOT/etc/profile # other puppies	
		grep -q "export PS1=" $FAKEROOT/etc/shinit &&
		sed -i -e "s/PS1=.*/PS1='sandbox-uml${SANDBOX_ID}# '/" $FAKEROOT/etc/shinit ||
		echo -e '\nexport PS1="sandbox-uml'${SANDBOX_ID}'# "' >> $FAKEROOT/etc/shinit #fatdog 600
		grep -q "export TERM=" $FAKEROOT/etc/shinit &&
		sed -i -e "s/TERM=.*/TERM=screen/" $FAKEROOT/etc/shinit ||
		echo "export TERM=screen" >> $FAKEROOT/etc/shinit  # default TERM is screwed, so use host's

		# 6. prepare filesystem
		UML_START_CMD=
		if [ -z "$OS_IMAGE" ]; then
			# we are not running under any initrd at all, so remove this
			sed -i -e '/GENUINE_FATDOG_INITRD/d' $FAKEROOT/$BOOTSTATE_PATH
			# empty the persistent net rules to prevent eth0 from being renamed
			> $FAKEROOT/etc/udev/rules.d/70-persistent-net.rules 
			
			if ! [ -e $FAKEROOT/etc/init.d/00-init-sb-uml ]; then
				cat > $FAKEROOT/etc/init.d/00-init-sb-uml << EOF
#!/bin/sh
[ -x /sbin/netstart-sb-uml ] && /sbin/netstart-sb-uml
rm -f /etc/wpagui # don't let it touch our network setup
echo "Starting UML (id: ${UML_ID}) now."
logger "Starting UML (id: ${UML_ID}) now."
EOF
				chmod +x $FAKEROOT/etc/init.d/00-init-sb-uml		
			fi

			# 6.1 append oneshot commands
			if [ $# -gt 0 ]; then
				{	echo "#!/bin/sh"
					echo "case \$1 in start)" 
					for p in "$@"; do printf "'%s' " "$p"; done
					echo
					echo "esac"
				} > $FAKEROOT/etc/init.d/01-oneshot
				chmod +x $FAKEROOT/etc/init.d/01-oneshot
			else
				rm -f $FAKEROOT/etc/init.d/01-oneshot
			fi
			
		else # -z OS_IMAGE
			UML_START_CMD="/sbin/init-sb-uml"
			mkdir -p $FAKEROOT/dev $FAKEROOT/proc $FAKEROOT/sys $FAKEROOT/tmp
			cat > $FAKEROOT/$UML_START_CMD << EOF
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
! [ -e /dev/null ] && mount -t devtmpfs devtmpfs /dev
mkdir -p /dev/pts; mount -t devpts devpts /dev/pts
mkdir -p /dev/shm; mount -t tmpfs tmpfs /dev/shm
export HOME=/root
export TERM=$TERM
[ -x /sbin/netstart-sb-uml ] && /sbin/netstart-sb-uml
[ -e /etc/X11/host-x11 ] && . /etc/X11/host-x11
echo "Starting UML (id: ${UML_ID}) now."
EOF
			chmod +x $FAKEROOT/$UML_START_CMD
			if [ $# -gt 0 ]; then
				{ printf "exec "
				for p in "$@"; do printf "'%s' " "$p"; done
				echo
				} >> $FAKEROOT/$UML_START_CMD
			else
				echo "exec /bin/sh" >> $FAKEROOT/$UML_START_CMD
			fi
			#cp $FAKEROOT/bin/sh $FAKEROOT/$UML_START_CMD
		fi # OS_IMAGE

		# 7. configuration
		UML_NETWORK=
		UML_DISPLAY=
		UML_SOUND=
		UML_NICE_CMD=
		
		# 7.1 network
		[ "$USE_NETWORK" ] && prepare_uml_network

		# 7.2 desktop
		[ "$USE_NETWORK" ] && [ "$USE_DESKTOP" ] && start_xserver &&
		UML_DISPLAY="DISPLAY=$HOST_IP:$XSERVER_DISP"
		
		# 7.3 others
		[ "$SHARE_SOUND" = "yes" ] && UML_SOUND="dsp=/dev/dsp mixer=/dev/mixer"
		[ "$NICE_LEVEL" ] && UML_NICE_CMD="nice $NICE_LEVEL"
		[ "$UML_START_CMD" ] && UML_START_CMD="init=$UML_START_CMD"

		# 8. launch
		$UML_NICE_CMD $VMLINUX umid=$UML_ID \
		mem=$RAM_SIZE rootfstype=hostfs rw hostfs="$FAKEROOT" \
		$UML_NETWORK $UML_SOUND $UML_DISPLAY \
		$UML_EXTRA $UML_START_CMD
		#reset
		;;

	### unknown sandbox type
	*) # case $SANDBOX_MODE	
		echo "Unknown SANDBOX_MODE [$SANDBOX_MODE], aborting." ;;
esac

# 16. done - clean up everything 
umountall
echo "Leaving sandbox."
