#!/bin/bash
##
#  Korinf project
#
#  Local chroot mount related functions
#
#  Copyright (c) Etersoft <http://etersoft.ru> 2005, 2006, 2007, 2009, 2021
#  Copyright (c) Vitaly Lipatov <lav@etersoft.ru> 2009, 2021
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU Affero General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.

#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU Affero General Public License for more details.

#  You should have received a copy of the GNU Affero General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
##


MOUNT_MODULE_INCLUDED=1

create_base_docker()
{
	local dist=$1

	# use versioned or base Dockerfile
	local dockerfile=$KORINFETC/docker/$dist/Dockerfile
	if [ "$TARGETARCH" = "i586" ] ; then
		# use arch in path (x86_64 for i586 by default)
		dockerfile=$KORINFETC/docker/$BUILDARCH/$dist/Dockerfile
		dockerfile_target=$KORINFETC/docker/$BUILDARCH-$TARGETARCH/$dist/Dockerfile
		# if there exists special Dockefile
		[ "$TARGETARCH" != "$BUILDARCH" ] && [ -s "$dockerfile_target" ] && dockerfile=$dockerfile_target
	fi

	local rcommand=$(get_remote_commands $dist)
	local toremove=''
	
	# autocreating Dockerfile
	if [ ! -s "$dockerfile" ] && [ -n "$rcommand" ] ; then
		rcommand=$(echo "$rcommand" | sed -e "s|@VERSION@|$(get_distversion_by_dist $dist)|") #"
		rhas "$rcommand" ":" || rcommand="$rcommand:$(get_distversion_by_dist $dist)"
		dockerfile=$(mktemp)
		local template=$KORINFETC/docker/$BUILDARCH/$(get_distname_by_dist $dist)/Dockerfile.template
		[ -s "$template" ] || template=$KORINFETC/docker/$BUILDARCH/Dockerfile.template
		[ -s "$template" ] || fatal "template $template is missed"
		echo "Create default Dockerfile $dockerfile for $rcommand from $template"
		cat $template | sed -e "s|@FROM@|$rcommand|" | tee $dockerfile
		toremove=$dockerfile
	fi

	# TODO: generate in some way
	#[ -s "$dockerfile" ] || dockerfile=$KORINFETC/docker/$DISTRNAME/Dockerfile
	local baseimage=$(get_docker_image $dist)
	a='' docker build -f $dockerfile -t $baseimage . #|| return 1
	if [ -n "$toremove" ] ; then
		rm -v $toremove
	fi
}

# args: <dist>
# uses:DISTRNAME
run_docker_container()
{
	local dist=$1
	assert_var DISTRNAME
	local dockerimage

	dockerimage=$(get_docker_image $dist)
	# Используем образ для модуля, если нет — базовый
	if ! is_docker_image $dockerimage ; then
		dockerimage=$(KORINFMODULE= get_docker_image $dist)
	fi

	# Создаём базовый образ, если его нет
	if ! is_docker_image $dockerimage ; then
		echo "Creating base docker build system $dockerimage:" >&2
		create_base_docker $dist
	fi


	# create tmp home dir
	assert_var CHROOTDIR LOCUSER INTUSER

	EXTERNALDOCKERHOME=1
	MOUNTBUILDERSTRING=''
	if [ -n $EXTERNALDOCKERHOME ] ; then
		# x86_64/Debian/6.0 -> x86_64-Debian-6.0
		# Создаём необходимые каталоги
		mkdir -p $CHROOTDIR
		BUILDROOT=`mktemp -d $CHROOTDIR/${dist//\//-}-XXXXXX`
		BUILDERHOME=$BUILDROOT/home/$INTUSER
		mkdir -p $BUILDERHOME/tmp/
		# will set owner later via run_in_chroot
		#$SUDO chown $LOCUSER $BUILDERHOME/
		MOUNTBUILDERSTRING="--mount type=bind,source=$BUILDERHOME,target=/home/$INTUSER"
	fi

	platform=''
	[ "$BUILDARCH" = "i586" ] && platform="--platform linux/i386"

	# generate contname with random suffix
	contname=$(get_docker_container $dist)-$(pwgen -1)
	echo "Creating build system in docker $contname from $dockerimage:" >&2
	# TODO: check before remove
	a='' docker rm $contname 2>/dev/null # || return 1
	a='' docker run -d \
		--name $contname \
		$platform \
		--tmpfs /tmp:exec \
		$MOUNTBUILDERSTRING \
		$dockerimage
	a='' docker start $contname

	# save name of running container
	CONTAINERNAME=$contname

	run_in_chroot $dist "chown $INTUSER /home/$INTUSER /home/$INTUSER/tmp"

	return 0
}

# args: <dist>
stop_docker_container()
{
	assert_var CONTAINERNAME
	local dist=$1
	local contname=$CONTAINERNAME
	if [ -n "$BOOTSTRAP$INSTALLREQUIREDPACKAGE" ] && [ "$STATUSBUILD" = "done" ] ; then
		dockerimage=$(get_docker_image $dist)
		echo "Save $contname to docker image $dockerimage:latest:" >&2
		a='' docker commit $contname $dockerimage:latest
		# TODO: вообще-то тут надо удалять все образы, порождённые от этого базового
	fi

	[ -n "$ADEBUG" ] && return
	echo "Remove docker build system:" >&2
	a='' evz stop $contname || return 1
	echo 'Yes' | a='' evz destroy $contname || return 1
	#a='' docker rmi --no-prune $(get_docker_image $dist) || return 1
}

# args: <dist> <BUILDERHOME>
mount_linux()
{
	local dist=$1
	[ -n "$dist" ] || fatal "empty dist"

	local rhost
	case "$(get_remote_host $dist)" in
		"docker")
			run_docker_container $dist
			return 0
			;;
		"local")
			;;
		*)
			# skip mounting for remote systems
			return 0
			;;
	esac

	local MOUNTPARAM

	# Note!
	BUILDERHOME=$2

	assert_var CHROOTDIR LOCUSER INTUSER

	# x86_64/Debian/6.0 -> x86_64-Debian-6.0
	BUILDROOT=`mktemp -d $CHROOTDIR/${dist//\//-}-XXXXXX`
	# Создаём необходимые каталоги
	mkdir -p $CHROOTDIR
	$SUDO chown $LOCUSER $CHROOTDIR

	# Note! Define and create BUILDHOME here
	if [ -z "$BUILDERHOME" ] ; then
		# create temp home
		BUILDERHOME=$BUILDROOT-home
		mkdir -p $BUILDERHOME/{,tmp,RPM,RPM/BUILD,RPM/RPMS} || fatal "Can't create $BUILDERHOME"
		chmod g+rwx $BUILDERHOME
		$SUDO chown $LOCUSER $BUILDERHOME -R
	fi

	# FIXME: why LOCUSER??
	chmod g+rwx $BUILDROOT
	$SUDO chown $LOCUSER $BUILDROOT -R

	[ -z "$BUILDROOT" ] && echo "Skip mount due empty BUILDROOT" && return 1
	echo "Mount $LOCALLINUXFARM/$dist to $BUILDROOT (user $INTUSER)..."

	# FIXME:
	[ -d $LOCALLINUXFARM ] && MOUNTPARAM="--bind" || MOUNTPARAM="-o soft"
	$SUDO mount $LOCALLINUXFARM/$dist $BUILDROOT $MOUNTPARAM || { warning "Cannot mount..." ; return 1 ; }
	$SUDO chmod o+rx $BUILDROOT
	#ls -l $BUILDROOT
	test -d $BUILDROOT/etc/ || fatal "Linux system is missed in $BUILDROOT"

	# TODO: need create these dir as root!
	# $BUILDROOT/run
	$SUDO su $LOCUSER -c "mkdir -p $BUILDROOT/proc $BUILDROOT/sys $BUILDROOT/tmp $BUILDROOT/home $BUILDROOT/home/$INTUSER $BUILDROOT/srv/wine"
	$SUDO mount -t proc proc $BUILDROOT/proc || { warning "Cannot mount /proc" ; return 1 ; }
	$SUDO mount -t sysfs none $BUILDROOT/sys || { warning "Cannot mount /sys" ; return 1 ; }
	#$SUDO mount $BUILDROOT $BUILDROOT/sys || { warning "Cannot mount /sys" ; return 1 ; }
	$SUDO mount -t devpts none $BUILDROOT/dev/pts -o nosuid,noexec,gid=tty,mode=620 || { warning "Cannot mount /sys" ; return 1 ; }

	if [ -d "$BUILDROOT/dev/shm" ] ; then
		$SUDO mount -t tmpfs shmfs $BUILDROOT/dev/shm || { warning "Cannot mount /dev/shm" ; return 1 ; }
	fi

	echo "Mount home $BUILDERHOME ..."
	$SUDO mount $BUILDERHOME $BUILDROOT/home/$INTUSER --bind || { warning "Cannot mount home" ; return 1 ; }

	echo "Mount /tmp $BUILDROOT-tmp ..."
	mkdir -p $BUILDROOT-tmp
	# TODO: not allowed in sudo
	#$SUDO chown root:root $BUILDROOT-tmp
	$SUDO chmod 01777 $BUILDROOT-tmp
	$SUDO mount $BUILDROOT-tmp $BUILDROOT/tmp --bind || { warning "Cannot mount /tmp" ; return 1 ; }

	#echo Mount srv/wine...
	#$SUDO mount /net/wine $BUILD/srv/wine --bind || { warning "Cannot mount home" ; return 0 ; }
}

terminate_using()
{
	local u
	local LISTPROC
	u=$1
	$SUDO fuser -vk $u
	LISTPROC=`$SUDO su $LOCUSER -c "/usr/sbin/lsof | grep $u | awk '{print \\$2}' | sort -u"`
	echo "Kill process use our dir (f.i. gpg-agent): $LISTPROC"
	$SUDO su $LOCUSER -c "echo $LISTPROC | xargs kill"
}

# args: <dist>
# uses BUILDROOT, DISTRNAME
end_umount()
{
	local dist=$1
	local i u ERR
	ERR=0

	# if early error
	[ -z "$DISTRNAME" ] && return

	[ -n "$dist" ] || fatal "empty dist"

	local rhost
	case "$(get_remote_host $dist)" in
		"docker")
			assert_var INTUSER
			# [ -n "$ADEBUG" ] || run_in_chroot --user $dist "rm /home/$INTUSER/tmp/* ; rm -r /home/$INTUSER/RPM/* ; true"
			if [ -n "$BUILDROOT" ] ; then
				echo "Removing $BUILDROOT ..."
				if [ -z "$ADEBUG" ] ; then
					run_in_chroot --user $dist "rm -rf /home/$INTUSER/* ; true"
					rm -rf $BUILDROOT
				fi
			else
				[ -z "$ADEBUG" ] || echo "Skip umount due empty BUILDROOT"
			fi
			stop_docker_container $dist
			[ -z "$BUILTRPM" ] || rm -rfv $BUILTRPM
			return 0
			;;
		"local")
			;;
		*)
			# skip umount for remote systems
			return 0
			;;
	esac

	if [ -z "$BUILDROOT" ] ; then
		[ -z "$ADEBUG" ] || echo "Skip umount due empty BUILDROOT"
		return 1
	fi

	echo "Unmounting and cleaning $BUILDROOT..."
	for i in /proc/bus/usb /proc/sys/fs/binfmt_misc /proc /sys /run /home/$INTUSER /dev/shm /dev/pts /tmp / ; do
		u=$BUILDROOT$i
		if [ -d $u ] && mount | grep $i >/dev/null ; then
			if ! $SUDO umount $u ; then
				terminate_using $u
				sleep 1
				$SUDO umount -v $u || terminate_using $u
			fi
			#|| echo "Failed $u umount"
			#$SUDO umount $i || $SUDO umount $i -l || ERR=1
		fi
	done
	[ $ERR = 0 ] && echo "DONE" || echo "Umount FAILED"
	if [ -z "$ADEBUG" ] ; then
		if echo $BUILDERHOME | grep -q $BUILDROOT ; then
			echo "Removing $BUILDERHOME ..."
			#$SUDO su $LOCUSER -c "rm -rf $BUILDERHOME" || ERR=1
			$SUDO su $LOCUSER -c "rm -rf $BUILDERHOME" || sleep 2 || $SUDO su $LOCUSER -c "rm -rf $BUILDERHOME" || (echo $(fuser $BUILDERHOME) && ERR=1)
			TMPBUILDERHOME=
		fi
		echo "Removing $BUILDROOT ..."
		#$SUDO su $LOCUSER -c "rmdir -v $BUILDROOT" || ERR=1
		$SUDO su $LOCUSER -c "rmdir -v $BUILDROOT" || sleep 2 || $SUDO su $LOCUSER -c "rmdir -v $BUILDROOT" || (echo $(fuser $BUILDERROOT) &&  ERR=1)
		[ $ERR = 0 ] && echo "DONE" || echo "Removing FAILED"
	else
		echo "Skip removing due ADEBUG set"
	fi
	BUILDROOT=""
	return $ERR
}

# args: [--user USER] [--interactive] <dist> <command>
# run command for dist
# use --user for change to local user INTUSER
run_in_chroot()
{
	assert_var INTUSER

	local LOUSER=""
	if [ "$1" = "--user" ] ; then
		LOUSER=$INTUSER
		shift
	else
		# FIXME
		LOUSER=root
	fi

	local INTERACTIVE="-i"
	[ "$1" = "--interactive" ] && INTERACTIVE="-ti" && shift

	local dist="$1"
	shift

	[ -n "$dist" ] || fatal "empty dist"

	# run via ssh if remote build system
	local rhost rcommands
	rhost="$(get_remote_host $dist)"

	if [ "$rhost" = "docker" ] ; then
		local arch
		arch=''
		[ "$BUILDARCH" = "i586" ] && arch="i386"
		echo "Run command in docker build system:" >&2
		if [ -z "$LOUSER" ] || [ "$LOUSER" = "root" ] ; then
			#evz exec $DISTRNAME/$DISTRVERSION "$@"
			echo "# $@" >&2
			a='' docker exec $INTERACTIVE -e LC_ALL=C $CONTAINERNAME $arch sh -c "$*"
		else
			#evz exec $DISTRNAME/$DISTRVERSION "gosu $INTUSER export LANG=C ; export LC_ALL=C ; . ~/.profile ; $@"
			echo "$ $@" >&2
			a='' docker exec $INTERACTIVE --user $INTUSER -e LC_ALL=C $CONTAINERNAME $arch sh -c "$*"
		fi
		return
	fi

	if [ "$rhost" = "local" ] ; then
		echo "Run command in chrooted build system:" >&2
		echo "# $@" >&2
		assert_var BUILDARCH BUILDROOT
		PATH=/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin $NICE setarch $BUILDARCH $SUDO chroot $BUILDROOT \
			su - $LOUSER -c "export LANG=C ; export LC_ALL=C ; $@"
		# TODO: use -s instead -c: https://bugzilla.redhat.com/show_bug.cgi?id=186879
		return
	fi

	if [ -n "$rhost" ] ; then
		rcommands=`eval echo $(get_remote_commands $dist)`
		# do not use command for a user
		#[ -n "$LOUSER" ] || rcommands=
		# Note: this output can be write to file with redirect output
		echo "Run command in remote ($rhost) build system:" >&2
		# FIXME: add -t if need interactive
		if [ -z "$LOUSER" ] || [ "$LOUSER" = "root" ] ; then
		    #ssh -i $PRIVATESSHKEY $INTUSER@$rhost $rcommands "\"export LANG=C ; export LC_ALL=C ; $@\""
			echo "# $@" >&2
		    ssh -i $PRIVATESSHKEY $INTUSER@$rhost $rcommands "\"$@\""
		else
			echo "$ $@" >&2
		    ssh -i $PRIVATESSHKEY $INTUSER@$rhost "export LANG=C ; export LC_ALL=C ; . ~/.profile ; $@"
		fi
		return
	fi

	fatal "empty rhost"
}
