#!/bin/dash
# Restore base package, that is, recover deleted packages from base sfs
#
# Copyright (C) James Budiono 2014
# License: GNU GPL Version 3 or later
#
# Note: assumes proper Slackware package management is in use.
# Note: only works in direct/ram-layer mode.
#       Does *not* work in multisession mode.

### configuration
[ $BOOTSTATE_PATH ] && [ -e $BOOTSTATE_PATH ] && 
. $BOOTSTATE_PATH # AUFS_ROOT_ID, BASE_SFS_MOUNT
APPTITLE="Restore Base Packages"
XTERM="defaultterm"
PKGDBDIR=/var/log/packages
PKGREMOVED=/var/log/removed_packages
ANS_FILE=/tmp/ans.$$ # output from dialog

### runtime variables
LANG=C       # make it run faster
ALLYES=      # non-blank - assume yes for all questions
DIALOG=      # will be filled in with dialog or Xdialog
PACKAGES=    # packages to restore
TOPLAYER=    # the topmost layer of aufs 
AUFS_TOP=br0 # aufs topmost layer

# show usage and exit
usage() {
	cat << EOF
Restore deleted base packages, if possible. Must be run as root.
Usage: ${0##*/} [-h|--help|-y|--yes|-c|--console|-r2|--ram2] packages...
 -h, --help    this help text
 -y, --yes     answer yes to all questions
 -c, --console run in console even when X is detected
 -r2 --ram2    in RAM-layer mode, restore from 2nd topmost layer (pup_save)
EOF

	exit 1
}

# display dialog - input $@ 
dlg() {
	local ret
	$DIALOG --title "$APPTITLE" "$@" 2> $ANS_FILE; ret=$?
	sed -i -e '/^Gtk-CRITICAL/d; /^$/d' $ANS_FILE # workaround Xdialog errors
	ans=$(cat $ANS_FILE) # because ans can be multiple lines
	return $ret

}

# make sure we run as root - input $@
run_as_root() {
	if [ $(id -u) -ne 0 ]; then
		if [ $DISPLAY ]; then
			exec gtksu "$APPTITLE" "$0" "$@"
		else
			eval exec su -c \""$0 $@"\"
		fi
	fi
}

# make sure we run in terminal - input: $@
run_in_terminal() {
	# check we're running in terminal, if not, re-exec
	if ! test -t 0; then
		# not on terminal, re-exec on terminal
		if [ $DISPLAY ]; then
			exec $XTERM -e "$0" "$@"
		else
			exit 1
		fi
	fi

}

# check whether we want to use console or graphic
check_console() {
	type Xdialog > /dev/null && [ "$DISPLAY" ] && [ -z "$DIALOG" ] &&
	DIALOG=Xdialog
	case $DIALOG in
		""|dialog) DIALOG=dialog; run_in_terminal ;;
	esac
}

# find the topmost r/w layer - output TOPLAYER 
find_aufs_toplayer() {
	[ -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 | tail -n 1
)
	! [ -e /sys/fs/aufs/$AUFS_ROOT_ID ] &&
	echo "Problem: AUFS ROOT $AUFS_ROOT_ID does not exist." &&
	exit 1
	
	read TOPLAYER < /sys/fs/aufs/$AUFS_ROOT_ID/$AUFS_TOP
	TOPLAYER=${TOPLAYER%=*}
	#echo $TOPLAYER
	
	! [ -d $TOPLAYER ] && 
	echo "Problem: AUFS topmost layer is inaccessible." && 
	exit 1	
}

# force re-evalution of all the layers
aufs_reval() {
	busybox mount -i -t aufs -o remount,udba=reval aufs /
}

# list all the deleted package names - input $1-pattern
find_deleted_package() {
	find $TOPLAYER/$PKGDBDIR -type f -name ".wh.${1}*" |
	sed 's|.*\.wh\.||'| while read -r p; do
		# must ensure we have the record in PKGREMOVED dir
		[ -e $PKGREMOVED/$p ] && echo $p
	done
}

# list all files from deleted package - input: $1-deleted package name
get_pkg_files() {
	sed '1,/FILE LIST:/d; /^install\//d; /^\.\//d; s|/$||' $PKGREMOVED/$1
}

# restore deleted package, input: $1-partial package name to restore, $ALLYES
restore_pkg() {
	find_deleted_package $1 | while read -r p; do
		[ -z $ALLYES ] && ! dlg --yesno "Found $p, restore?" 0 0 && continue
		clear
		# restore files
		get_pkg_files $p | sort | while read -r f; do
			file="${f##*/}" dir="${f%$file}"
			# whiteout and diropq
			printf "%s\0%s\0" "$TOPLAYER/$f/.wh..wh..opq" "$TOPLAYER/${dir}.wh.${file}" 
		#done | xargs -0 echo rm -f 		
		done | xargs -0 rm -f 
		# restore package
		#echo mv $PKGREMOVED/$p $PKGDBDIR
		mv $PKGREMOVED/$p $PKGDBDIR
	done
}

### UI to select packages to restore
assisted_restore() {
	local p pp

	ALLYES=yes # don't ask again - we already asked it here
	while true; do
		pp=""
		for p in $(find_deleted_package); do
			pp="$pp $p $p"
		done
		[ -z "$pp" ] && dlg --infobox "Nothing more to restore." 0 0 && break
		
		dlg --no-tags --menu "Packages to restore:" 20 60 10 \
		$pp \
		exit Exit || break
		case $ans in
			exit) break ;;
			*) restore_pkg $ans 
			   dlg --infobox "$ans restored." 0 0 ;;
		esac
	done
}

### process options
# input "$@"
parse_options() {
	while [ "$1" ]; do
		case "$1" in
			-h|--help)    usage         ;;
			-y|--yes)     ALLYES=yes    ;;
			-c|--console) DIALOG=dialog ;;
			-r2|--ram2)   AUFS_TOP=br1  ;;
			-*)           usage         ;; # unknown options
			*) PACKAGES="$PACKAGES $1"  ;;
		esac
		shift
	done
}

### main
run_as_root "$@"
parse_options "$@"
check_console "$@"
find_aufs_toplayer
if [ -z "$PACKAGES" ]; then
	assisted_restore
else
	for pkg in $PACKAGES; do
		restore_pkg $pkg
	done
	dlg --infobox "Done." 0 0
fi
aufs_reval
