summaryrefslogblamecommitdiffstats
path: root/core/includes/chroot.inc
blob: 04db53ee2d4aa355eab2b3fbdc2fd41197130205 (plain) (tree)
1
2
3
4
5
6
7
8
9
           

                                                                               
                                                 
 

                                                                         
 
                                                                   
                                              

                                                                            
 








                                                                               
                                                  


                                                    


                                                                                      





                                                          


                                          
                                               













                                                                                    















                                                                                                                                                             

          
                                                                                                                       
                                                               
                                                                                                  



                                        


                                                                   
                         








                                                                                                                                         

                                                                                      
                                                                                                             


                                                                                                                   


                                                                                                                                     
                                          
                                                      





                                                                                                                      
                  








                                                                                                                                                    
            


                                         



                                                                                    










                                                                                                                 





                                                                       

                                                                       

                                                                       












                                                                                                                          

                                                                                                       
                                                      
                                                                                                              



                                                                                     
                                                                                                             
                
























                                                                                                                   



                                                                   
 

                                                                                 

                                












                                                                                        




                                                                                                                                                                   
            
                                                                                                                                                                                                                                   















                                                                                                         

                                                                        
















                                                                                             
                                          



                                                                               
                                               
                                                                
                                       







                                                                                                                      



                                        
















                                                                         





                                                                                                         
                    


                                                                  
 
#!/bin/bash
# -----------------------------------------------------------------------------
#
# Copyright (c) 2014..2018 bwLehrpool-Projektteam
#
# This program/file is free software distributed under the GPL version 2.
# See https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
#
# If you have any feedback please consult https://bwlehrpool.de and
# send your feedback to support@bwlehrpool.de.
#
# General information about bwLehrpool can be found at https://bwlehrpool.de
#
# -----------------------------------------------------------------------------
#
#			Common functions for chrooting
#
# -----------------------------------------------------------------------------

declare -rg CHROOT_TEMPDIR="${ROOT_DIR}/tmp/chroot"
declare -rg CHROOT_MOUNTDIR="${CHROOT_TEMPDIR}/rootmount"
declare -rg CHROOT_BINDDIR="${CHROOT_TEMPDIR}/rootbind"
declare -rg CHROOT_TMPFS="${CHROOT_TEMPDIR}/upper"
declare -rg CHROOT_LOWERDIR="/"
declare -rg CHROOT_BINDMOUNTS="/dev /proc /sys /run"

# Helper for the helper to detect overlay filesystem name
chroot_detect_overlayfs() {
	declare -g OVERLAYFS_FSTYPES="$(grep -oE '\boverlay(fs)?$' /proc/filesystems)"
	[ -z "$OVERLAYFS_FSTYPES" ] && return 1
	# at least one overlayfs in /proc/filesystems
	# either one should work, will try them all later
	pdebug "Found overlayfs types: $OVERLAYFS_FSTYPES"
	readonly OVERLAYFS_FSTYPES
	return 0
}
# Helper to make sure we can use overlayfs
chroot_init_overlayfs() {
	[ -n "$OVERLAYFS_FSTYPES" ] && return 0
	# check if we already support it
	chroot_detect_overlayfs && return 0

	# nothing found, try to load kernel module
	pwarning "No overlayfs found in /proc/filesystems, trying to load module..."
	for NAME in overlay overlayfs; do
		if modprobe "${NAME}"; then
			chroot_detect_overlayfs && break
		fi
	done
	if [ -z "$OVERLAYFS_FSTYPES" ]; then
		perror "Could not initialize overlayfs!"
	fi
}

# Helper function to setup the directory structure
chroot_prepare_dirs() {
	# first check if CHROOT_TEMPDIR exists
	if [ -d "${CHROOT_TEMPDIR}" ]; then
		# try to umount and rmdir CHROOT_MOUNTDIR
		umount "${CHROOT_MOUNTDIR}" 2>/dev/null
		if [ -d "${CHROOT_MOUNTDIR}" ]; then
			rmdir "${CHROOT_MOUNTDIR}" || perror "Could not remove CHROOT_MOUNTDIR '${CHROOT_MOUNTDIR}', meaning it has stuff in it. Aborting..."
		fi

		# try to umount and rmdir CHROOT_BINDDIR
		umount "${CHROOT_BINDDIR}" 2>/dev/null
		if [ -d "${CHROOT_BINDDIR}" ]; then
		 	rmdir "${CHROOT_BINDDIR}" || perror "Could not remove CHROOT_BINDDIR '${CHROOT_BINDDIR}', meaning it has stuff in it. Aborting..."
		fi
	fi

	mkdir -p "${CHROOT_TEMPDIR}" || perror "Could not create base directory for mount directories $CHROOT_TEMPDIR."
	for DIR in "${CHROOT_BINDDIR}" "${CHROOT_MOUNTDIR}"; do
		mkdir -p "${DIR}" || perror "Could not create directory for mount directory $DIR."
	done
}

# Helper to mount the overlay structure:
# - bind mount system / to CHROOT_BINDDIR and make it read-only
# - make an overlay from CHROOT_LOWERDIR CHROOT_UPPERDIR
# - bind mount additional pseudo-fs (as given in CHROOT_BINDMOUNTS)
chroot_prepare_mounts() {
	# first mount / on CHROOT_BINDDIR and remount read-only
	mount -o bind "${CHROOT_LOWERDIR}" "${CHROOT_BINDDIR}" \
			|| perror "Could not bind-mount CHROOT_LOWERDIR '$CHROOT_LOWERDIR' to CHROOT_BINDDIR '$CHROOT_BINDDIR'."
	mount -o remount,ro,bind "${CHROOT_BINDDIR}"		|| perror "Could not remount CHROOT_BINDDIR '$CHROOT_BINDDIR' read-only."

	# check that it really is read-only
	[ "x$(mount | grep -E "^/ on ${CHROOT_BINDDIR}" | grep -v '\(.*ro.*\)')" != "x" ] \
			&& perror "CHROOT_BINDDIR '${CHROOT_BINDDIR}' is not read-only! Aborting..."

	# Newer kernels (5.x) cannot have upperdir as subdirectory of lowerdir - tmpfs
	mkdir -p "${CHROOT_TMPFS}"
	mount -t tmpfs -o size=2G chrootupper "${CHROOT_TMPFS}" || perror "Could not mount tmpfs as upperdir"
	mkdir -p "${CHROOT_UPPER_TMPFS}" || perror "Could not create ${CHROOT_UPPER_TMPFS}"
	rsync -axHAX "${CHROOT_UPPERDIR}/" "${CHROOT_UPPER_TMPFS}/" || perror "Could not put upperdir into upupdir"

	# Note: The overlay fs mount syntax seems to be changed between Ubuntu 14.04.2 and 14.04.3 (Kernel 3.13 and 3.19). Instead of
	# checking overlay-modinfo (which may fail if overlayfs is not incorporated as module) or kernel versions, we simply try to
	# mount 'old school' first and then, if that fails, the new way to mount with workdir. See differences in mount syntax below.
	pinfo "Now mounting overlayfs ..."
	for OVERLAYFS_NAME in ${OVERLAYFS_FSTYPES}; do
		# We have to use a overlayfs workdir which _must_ be in the same filesystem as CHROOT_UPPERDIR. So
		# we traverse to the directory below CHROOT_UPPERDIR and mkdir/mktemp a workdir there. In the possible
		# case that CHROOT_UPPERDIR is the root dir of a filesystem there's nothing we can do but exit.
		mkdir -p "$CHROOT_WORKDIR"
		if [ -z "$CHROOT_WORKDIR" ] || ! [ -d "$CHROOT_WORKDIR" ]; then
			perror "Could not mkdir overlayfs workdir $CHROOT_WORKDIR for new overlayfs mount syntax."
		fi
		# Now we try to mount the overlayfs in the new fashion:
		lsof -n > /tmp/bbboboboooo
		set -x
		mount -v -t "${OVERLAYFS_NAME}" "${OVERLAYFS_NAME}" \
			-o lowerdir="${CHROOT_BINDDIR}",upperdir="${CHROOT_UPPER_TMPFS}",workdir="${CHROOT_WORKDIR}" \
					"${CHROOT_MOUNTDIR}" \
				|| perror "Could not mount (overlayfs) $CHROOT_BINDDIR, $CHROOT_UPPER_TMPFS, ${CHROOT_WORKDIR} to $CHROOT_MOUNTDIR."
		set +x
		pinfo "New overlayfs mount syntax has worked, commencing."
	done

	#  mount pseudo-filesystems
	for DIR in $CHROOT_BINDMOUNTS; do
		if ! [ -d "$DIR" ]; then
			pwarning "Skipping bind mount of inexistant directory: $DIR"
			continue
		fi
		mount -o bind "${DIR}" "${CHROOT_MOUNTDIR}/${DIR}" \
			|| perror "Could not bind mount '$DIR' into CHROOT_MOUNTDIR/DIR '$CHROOT_MOUNTDIR/$DIR'."
	done
}

# Helper to generate the mighty autoexec.bat
chroot_gen_autoexec() {
	# create the script to be automatically executed.
	cat >"${CHROOT_MOUNTDIR}/autoexec.bat"<<-EOF
		#!/bin/bash
		#######################################################
		#                                                     #
		#                     Warning!                        #
		#                                                     #
		#  This file is only meant to be executed within      #
		#  the specially chrooted mltk building environment.  #
		#                                                     #
		#  Do NOT execute it if you are not sure what you do, #
		#  it may be very harmful if being run in a normal    #
		#  system environment!                                #
		#                                                     #
		#######################################################
		echo "chroot started successfully."
	EOF

	# dump the piped input to it
	cat >> "${CHROOT_MOUNTDIR}/autoexec.bat"

	# make it executable
	chmod +x "${CHROOT_MOUNTDIR}/autoexec.bat" || perror "Failed to make '${CHROOT_MOUNTDIR}/autoexec.bat' exeutable."
}

chroot_handle_whiteouts() {
	local WHITEOUT_LIST="${CHROOT_UPPERDIR}/overlay.whiteout.list"
	rm -f -- "$WHITEOUT_LIST"
	mkdir -p "$(dirname "$WHITEOUT_LIST")" || perror "Could not create $(dirname "$WHITEOUT_LIST")"
	pdebug "Searching for overlayfs-whiteouts ..."
	for WHITEOUT in $(find "$CHROOT_UPPERDIR" \( -type c -perm 0000 \) -o -lname "(overlay-whiteout)"); do
		pdebug "Whiteout found: $WHITEOUT"
		echo "/./${WHITEOUT#$CHROOT_UPPERDIR}" >> "$WHITEOUT_LIST"
		rm -f -- "$WHITEOUT" || perror "Could not delete whiteout $WHITEOUT!"
	done
	[ -s "$WHITEOUT_LIST" ] && pinfo "Whiteout list dumped to '${CHROOT_UPPERDIR}/overlay.whiteout.list'"
	return 0
}

###############################################################################
#
#				MAIN FUNCTION
#
# Main function to be called from the outside
# Usage:
#	chroot_run <build_dir> < <code_to_exec_in_chroot>
#
# Example:
#	chroot_run /tmp/chroot_build <<-EOF
#		echo "This will be executed inside the chroot"
#	EOF
#
# It will run:
#	- chroot_prepare
#	- chroot $CHROOT_TEMPDIR/rootmount
#	- executes $CHROOT_TEMPDIR/rootmount/autoexec.bat
#	- chroot_cleanup
chroot_run() {
	# check args
	[ $# -eq 1 ] || perror "'chroot_run' requires exactly 1 parameter. Given $#.  Use 'chroot_run <build_dir>'"

	local CHROOT_UPPERDIR="$1"
	mkdir -p "$1"

	local CHROOT_WORKDIR="${CHROOT_TMPFS}/workdir-$MODULE"
	local CHROOT_UPPER_TMPFS="${CHROOT_TMPFS}/upperdir-$MODULE"

	# init overlayfs
	chroot_init_overlayfs || perror "Failed to initialize overlayfs with $?."
	# Clean up previous mess
	chroot_cleanup_mounts
	# first prepare the dir structure
	chroot_prepare_dirs || perror "'chroot_prepare_dirs' failed with $?."
	chroot_prepare_mounts || perror "'chroot_prepare_mounts' failed with $?."

	# generate the code to be executed when chroot'ing
	chroot_gen_autoexec || perror "'chroot_gen_autoexec' failed with $?."

	# do the chroot
	exec 0>&8		# This redirection is used for debugging within a chroot
	chroot --userspec root:root "${CHROOT_MOUNTDIR}" /autoexec.bat
	local RET=$?
	if [ "$RET" -eq 0 ]; then
		pinfo "chroot executed '${CHROOT_MOUNTDIR}/autoexec.bat' succeeded."
		if rsync -axHAX "${CHROOT_UPPER_TMPFS}/" "${CHROOT_UPPERDIR}/"; then
			umount -l -f "${CHROOT_UPPER_TMPFS}" || pwarning "Cannot umount $CHROOT_UPPER_TMPFS"
		else
			pwarning "Could not properly rsync UPPER_TMPFS to UPPERDIR. tmpfs at '${CHROOT_UPPER_TMPFS}' will not be unmounted for further inspection."
		fi
	else
		perror "Failed to run '$CHROOT_MOUNTDIR/autoexec.bat' inside the chroot to '$CHROOT_MOUNTDIR' with error code: $RET. You can examine the upperdir at $CHROOT_UPPER_TMPFS. It's a tmpfs that hasn't been unmounted."
	fi

	# handle whiteouts
	chroot_handle_whiteouts || perror "'chroot_handle_whiteouts' failed with error code: $?"

	# finally cleanup all the mounting stuff we did previously
	chroot_cleanup_mounts			|| perror "'chroot_cleanup' failed with $?."
}

###############################################################################
#
#				CLEANUP FUNCTIONS
#
# Helper to check if the given path is mounted
chroot_check_mount_point() {
	[ "$#" -eq 1 ] || perror "'chroot_check_mount_point' called with $# arguements, only 1 accepted."
	local MOUNT="$1"
	if [ "x$(mount | grep "$(readlink -f "$MOUNT")")" != "x" ]; then
		# still mounted
		pdebug "'$MOUNT' is mounted!"
		return 1
	else
		pdebug "'$MOUNT' is not mounted."
		return 0
	fi
}

# Helper to umount the given path
chroot_umount() {
	[ "$#" -eq 1 ] || perror "'chroot_umount' called with $# arguments, only 1 accepted."
	local MOUNT="$1"

	# check if MOUNT is mounted
	if ! chroot_check_mount_point "${MOUNT}"; then
		# still mounted
		if umount "${MOUNT}"; then
			pdebug "Successfully umounted '${MOUNT}'."
		else
			pwarning "Could not umount '${MOUNT}'! Trying again..."
			# now it gets ugly
			for i in $(seq 1 5); do
				umount -l "${MOUNT}" && return 0
				sleep 1
			done
			perror "Could not umount '${MOUNT}' after 5 tries! This shouldn't happen. Check your scripts."
		fi
	else
		pdebug "'${MOUNT}' is not mounted."
	fi

	# better be safe than sorry
}

# Helper to cleanup the temporary mounts
chroot_cleanup_mounts() {
	local exe FILE pid tries
	local DOKILL=
	for tries in 1 2 0; do
		for exe in /proc/*/exe; do
			pid=${exe#/proc/}
			pid=${pid%/exe}
			FILE=$(readlink -f "$exe")
			if [ "${FILE#$CHROOT_TEMPDIR}" != "$FILE" ]; then
				pwarning "Killing $FILE ($pid)"
				kill $DOKILL "$pid"
				DOKILL="-9"
				kill "$pid"
			fi
		done
		[ -z "$DOSLEEP" ] && break
		sleep "$tries"
	done
	local i mnt ok
	for i in "" "" "" "-l" "-lf"; do
		ok=true
		for mnt in $(awk -v "dir=^${CHROOT_TEMPDIR}" '{if ($2 ~ dir) print $2}' /proc/mounts); do
			[ "$mnt" = "$CHROOT_UPPER_TMPFS" ] && continue
			umount $i "$mnt" || ok=false
		done
		$ok && break
	done
	$ok || perror "Could not unmount all chroot related dirs."
}