#!/bin/ash
# shared multisession-save library for rc.shutdown and save2session
# Copyright (C) James Budiono 2012, 2014, 2015
# License: GNU GPL Version 3 or later
#
# Version 1.2 - re-factored (Fatdog 702)
# Version 1.1 - with options to use from a running session, not only shutdown
# Version 1.0 - was in Fatdog 600, in rc.shutdown
#
# Note: this will not work on its own, it must be sourced.

# options must be identical with build-iso and remaster	
GROWISOFS="growisofs -M /dev/$MULTI_DEVICE -iso-level 4 -D -R" 
GROWISOFS_INIT="growisofs -Z /dev/$MULTI_DEVICE -iso-level 4 -D -R" 
DEFAULT_MKSQUASHFS_OPTIONS="-comp xz"
MKSQUASHFS_OPTIONS=${MKSQUASHFS_OPTIONS:-$DEFAULT_MKSQUASHFS_OPTIONS}

##################### savefile utilities ########################
### mount device to temporary mount point
# will use existing mountpoint if device is already mounted
# $1-device (with /dev prefix), returns mountpoint (empty if error)
mount_device() {
	local tmpdir dmdev fstype
	dmdev=$(ls -l /dev/mapper/* 2> /dev/null | awk -v dmdev=${1##*-} '$6 == dmdev {print $10; exit; }')
	[ "$dmdev" ] || dmdev=$(ls -l /dev/mapper/* 2> /dev/null | awk -v dmdev="/${1##*/}$" '$NF ~ dmdev { print $(NF-2); exit; }')
	if grep -Eqm 1 "^$1 |^$dmdev " /proc/mounts; then
		awk -v dev=$1 -v dmdev=$dmdev '$1 == dev || $1 == dmdev { print $2; exit; }' /proc/mounts	
	else
		tmpdir=$(mktemp -dp /tmp shutdown-mnt-XXXXXX)
		fstype=$(guess_fstype $1)
		! case $fstype in 
			ntfs) ntfs-3g $1 $tmpdir ;;
			vfat) mount -o utf8 $1 $tmpdir ;;
			f2fs) mount -t f2fs $1 $tmpdir ;;
			unknown|"") false ;; # error
			*) mount -t $fstype $1 $tmpdir ;;
		esac > /dev/null && tmpdir=
		echo $tmpdir
	fi
}

### check if a given file exist on the MULTI_DEVICE device
# $1-path; output: true exist, false not exist
file_exist_on_multi_device() {
	local p tmpdir;
	tmpdir=$(mount_device /dev/$MULTI_DEVICE)
	if [ -z $tmpdir ]; then
		# attempt to initialise DVD if it's unformatted
		if  grep "drive name" /proc/sys/dev/cdrom/info | grep -q $MULTI_DEVICE; then
			echo -n "Found empty DVD - initialising ... "
			> /tmp/empty
			$GROWISOFS_INIT /tmp/empty >> /dev/initrd.err 2>&1 &&
			echo "done." || echo "failed."
		fi
		return 1 # empty drive - always fail
	else
		ls $tmpdir/$1 > /dev/null 2>&1; p=$?
		umount $tmpdir; rmdir $tmpdir
		return $p	
	fi
}

### "burn" a given file to the device
# on DVD, will use growisofs, on harddisk/flash drive, will simply save the file
# $1-growisofs root, $2-hdroot, $3-eject disc, $4-source file(s)
burn_to_device() {
	local save_ok=yes
	local GROWROOT="$1"
	local HDROOT="$2"
	local EJECT="$3"
	local SOURCES="$4"
	local tmpdir
	if grep "drive name" /proc/sys/dev/cdrom/info | grep -q $MULTI_DEVICE; then
		# cdrom - assume dvd, use growisofs
		umount /dev/$MULTI_DEVICE
		! $GROWISOFS -root $GROWROOT $SOURCES >> /dev/initrd.err 2>&1 && save_ok=no		
		[ "$EJECT" ] && cdrom_id --eject-media /dev/$MULTI_DEVICE > /dev/null	# eject media when done		
	else
		# else assume harddisk - copy
		tmpdir=$(mount_device /dev/$MULTI_DEVICE)
		HDROOT="$tmpdir/$HDROOT"
		if [ "$tmpdir" ]; then
			mkdir -p $HDROOT
			! cp -a $SOURCES $HDROOT && save_ok=no
			umount $tmpdir; rmdir $tmpdir
		else
			save_ok=no
		fi
	fi
	
	# check results
	case $save_ok in 
		yes) echo "done."; return 0  ;;
		no) echo "failed."; return 1 ;;
	esac
}



### save in multisession mode
# MULTI_MOUNT, MULTI_DEVICE, SAVEFILE_MOUNT, SAVEFILE_PATH, SAVEFILE_PROTO, MULTI_PREFIX
# $1 - "shutdown" or "noshutdown" - shutdown means do extreme measure to save, MULTI_MOUNT will be gone after running this
#
save_multisession() {
	local shutdown_mode=""; # blank=no, non-blank yes
	[ "$1" = "shutdown" ] && shutdown_mode=yes
	
	# 0. Lock session
	[ -z $MULTI_MOUNT ] && return		# can only do this when we're in multisession mode	
	[ -z "$shutdown_mode" ] && [ -d $MULTI_SAVE_DIR ] && return	# existing save in progress, abort
	mkdir -p $MULTI_SAVE_DIR
		
	# 1. get savefile base name (savefilebase) and path (savepath)
	#    - path used for grafting (if there is none, graft at root directory)
	#    - basename used to construct complete savefilename based on $MULTI_PREFIX, timestamp and .sfs
	savefilebase="$SAVEFILE_PROTO" && [ "$SAVEFILE_PATH" ] && savefilebase=$SAVEFILE_PATH
	savepath="${savefilebase%/*}/" && [ "${savepath}" = "${savefilebase}/" ] && savepath=/
	savefilebase=${savefilebase##*/}; savefilebase=${MULTI_PREFIX}${savefilebase%.*} 
	
	# 2. build the savefile name (basename + timestamp + .sfs) 
	savefileproto=${savefilebase}
	timestamp=$(date -Iminutes | tr : -)
	savefile="$savefileproto-$timestamp-save.sfs"
	basefile="$savefileproto-$timestamp-base.sfs" # so that it is loaded first	
	archivepath="archive/$timestamp"
	
	# 3. save "archive" files first if archive is not empty, do it here to make room for mksquashfs
	if [ $(find "$SAVEFILE_MOUNT"/archive -maxdepth 0 -type d \! -empty) ]; then
		echo -n "Saving archives to $archivepath... "
		burn_to_device "$archivepath" "$savepath/$archivepath" "" "$SAVEFILE_MOUNT/archive/*"
		rm -rf "$SAVEFILE_MOUNT"/archive/*	# keep the original archive folder, we need it
	fi
		
	# 4. see if the disk is empty (no previous sessions), if yes, save old files as initial session
	if ! file_exist_on_multi_device "$savepath/$savefileproto*"; then
		echo -n "Saving initial session $basefile... "
		mksquashfs "$MULTI_MOUNT" "$MULTI_SAVE_DIR/$basefile" $MKSQUASHFS_OPTIONS > /dev/null &&
		burn_to_device "$savepath" "$savepath" "" "$MULTI_SAVE_DIR/$basefile"
		rm -f "$MULTI_SAVE_DIR/$basefile" # conserve RAM, delete after saving
	fi	

	# 5. delete old base files to free up space for new ones - only during shutdown
	[ "$shutdown_mode" ] && find "$MULTI_MOUNT" -xdev \! \( -path "${MULTI_MOUNT}${AUFS_ROOT}*" -o -path "$MULTI_MOUNT" \) -delete 
	mkdir -p $MULTI_SAVE_DIR	# do this again here (aufs bug)		
	
	# 6. build the session file (mksquashfs)
	echo -n "Saving session to $savefile... "
	mksquashfs "$SAVEFILE_MOUNT" "$MULTI_SAVE_DIR/$savefile" $MKSQUASHFS_OPTIONS > /dev/null &&
	burn_to_device "$savepath" "$savepath" "$shutdown_mode" "$MULTI_SAVE_DIR/$savefile"
	rm -f "$MULTI_SAVE_DIR/$savefile" # conserve RAM, delete after saving
		
	# 6. merge down so that next time the same info is not saved again - only for non-shutdown event
	[ -z "$shutdown_mode" ] && fatdog-merge-layers.sh "$SAVEFILE_MOUNT" "$MULTI_MOUNT"
	
	# 7. unlock session and reclaim space
	rm -rf $MULTI_SAVE_DIR
}
