#!/bin/dash
# Remove whiteouts from pup_save/pup_rw/pup_multi to unhide files from
# lower SFS layers
#
# Copyright (C) James Budiono 2015, 2020
# License: GNU GPL Version 3 or later
#

# 0. directory locations
. $BOOTSTATE_PATH # AUFS_ROOT_ID
[ "$NOT_USING_AUFS" ] && exit 1

### configuration
APPTITLE="Unhide SFS"
DIRS_TO_CLEAN="/aufs/pup_rw /aufs/pup_save /aufs/pup_multi"
[ "$DISPLAY" ] && DIALOG=Xdialog || DIALOG=dialog

### helper
yesno() {
	$DIALOG --title "$APPTITLE" --yesno "$1" 0 0
}

msg() {
	if [ "$DISPLAY" ]; then
		$DIALOG --title "$APPTITLE" --infobox "$1" 0 0 10000
	else
		$DIALOG --title "$APPTITLE" --infobox "$1" 0 0
	fi
}

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

splash() {
	case "$1" in
		stop) if [ $XPID ]; then kill $XPID; XPID=; fi ;;
		*)	if [ "$DISPLAY" ]; then
				$DIALOG --no-button --title "$APPTITLE" --infobox "$1" 0 0 1000000 &
				XPID=$!
			else
				$DIALOG --no-ok --title "$APPTITLE" --infobox "$1" 0 0
			fi ;;
	esac	
}

###
# 
# $1-action ("" or "-delete")
clean_all_whiteout() {
	for dir in $DIRS_TO_CLEAN; do
		echo Whiteouts from $dir ...
		find $dir -depth -name ".wh..wh.*" -prune -o -name ".wh.*" -print $1 2>/dev/null 
		echo "----"
	done
	echo "DONE"
}

# $1-sfs $2-action
clean_sfs_whiteout() {
	local p f d fn ff
	for dir in $DIRS_TO_CLEAN; do
		echo Whiteouts from $dir that hide $1 ...
		find $dir -depth -name ".wh..wh.*" -prune -o -name ".wh.*" -print 2>/dev/null |
		while read -r p; do
			f="${p#$dir}"
			d="${f%/*}"
			fn="${f##*/}"; fn="${fn#.wh.}"
			ff="$d/$fn"
			if [ -e "$1/$ff" -o -L "$1/$ff" ]; then
				echo $p
				[ "$2" ] && rm -f "$p"
			fi
		done
		echo "----"
	done 
	echo "DONE" 
}

# $1-action
clean_non_sfs_whiteout() {
	local SFSPoints="$(grep -oE "/aufs/kernel-modules|/aufs/pup_init|/aufs/pup_ro|/aufs/pup_ro[0-9]+" /proc/mounts | sort -u)"
	local p f d fn ff dd del
	for dir in $DIRS_TO_CLEAN; do
		echo Whiteouts from $dir not hiding any SFS files ...
		find $dir -depth -name ".wh..wh.*" -prune -o -name ".wh.*" -print 2>/dev/null |
		while read -r p; do
			f="${p#$dir}"
			d="${f%/*}"
			fn="${f##*/}"; fn="${fn#.wh.}"
			ff="$d/$fn"

			del=yes
			for dd in $SFSPoints; do
				[ -e "$dd/$ff" -o -L "$dd/$ff" ] && del=
			done
			
			if [ "$del" ]; then
				echo $p
				[ "$1" ] && rm -f "$p"
			fi
		done
		echo "----"
	done 
	echo "DONE" 
}

### 
# $1-sfs $2-action
clean_whiteout() {
	case "$1" in
		All) clean_all_whiteout $2 ;;
		Non-SFS) clean_non_sfs_whiteout $2 ;;
		*) clean_sfs_whiteout $*   ;;
	esac
}

aufs_reval() {
	printf "Refreshing aufs cache..."
	busybox mount -i -t aufs -o remount,udba=reval aufs /
	echo "done."
}




### main
# 0. warnings
[ $(id -u) -ne 0 ] && die "This program must be run as root."

yesno "This program will unhide files from lower SFS layers by
removing their whiteouts from the save layer.
Doing this may accidentally unhide unwanted files too.

Do you want to continue?" ||
die "Cancelled, nothing changed."

# 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) {
		# dont show the save layer
		if ($0 == "/aufs/pup_rw"   || 
		    $0 == "/aufs/pup_save" ||
		    $0 == "/aufs/pup_multi") next;

		# print the branches and its mappings
		if (mounttypes[$0])
			print $0, mounttypes[$0]
		else
			print $0, "unknown"
	}
  }  
'
)
# '

# 3. Ask user to choose the SFS to unhide
TMPFILE=$(mktemp -p /tmp)
$DIALOG --backtitle "Choose the SFS layer" --title "$APPTITLE" \
	--menu "Choose which SFS you want to unhide." 0 60 10 $items \
	"Non-SFS" "Remove whiteouts not hiding SFS" \
	All "Remove all whiteouts" \
	2> $TMPFILE ||
die "Cancelled, nothing changed."
chosen="$(cat $TMPFILE)"

# 4. Warning for all whiteout removals
case $chosen in
	All) yesno \
"You choose to remove all whiteouts. This will unhide all files from
all layers, and it may have unintended consequences.

Are you sure you want to do this?" ||
die "Cancelled, nothing changed."
	;;
esac

# 5. Show the files that would be deleted and confirm
splash "Checking, please wait..."
clean_whiteout $chosen > $TMPFILE
splash stop
$DIALOG --title "$APPTITLE" \
	--backtitle "List of whiteouts that will be deleted. Click OK to continue, or ESC to cancel.." \
	--textbox $TMPFILE 0 0 ||
die "Cancelled, nothing changed."

# 6. Actually do the deletion
splash "Removing whiteouts, please wait..."
clean_whiteout $chosen -delete > $TMPFILE
splash stop
$DIALOG --title "$APPTITLE" \
	--backtitle "DONE. Here is the list of deleted whiteouts." \
	--no-cancel \
	--textbox $TMPFILE 0 0
rm -f $TMPFILE

# 7. Reload aufs
aufs_reval

