#!/usr/bin/bash

CURR_DIR=`pwd`
DBG_DIR=.vstorage-dbg
DBG_MNT=$DBG_DIR/mnt
DBG_RAW=$DBG_DIR/raw

mk_debug_dirs() {
	if [ ! -d $DBG_DIR ]; then
		mkdir $DBG_DIR
	fi
	if [ ! -d $DBG_MNT ]; then
		mkdir $DBG_MNT
	fi
	if [ ! -d $DBG_RAW ]; then
		mkdir $DBG_RAW
	fi
}

wait_debug_mount_ready() {
	# Probably unnecessary but does not harm anyway
	while [ ! -d $1/.vstorage.info ]; do
		echo "waiting debug mount point ready"
		sleep 1
	done
}

mk_debug_mounts() {
	local clustername=$1
	vstorage-mount -c $clustername $DBG_MNT --no-config --disable-gc --blog "$CURR_DIR/$DBG_DIR/debug.blog" &>$DBG_DIR/debug.log
	if [ $? -ne 0 ]; then
		echo "failed to mount $clustername at $DBG_MNT"
		umount $DBG_MNT
		return 255
	fi
	wait_debug_mount_ready $DBG_MNT
	vstorage-mount -c $clustername $DBG_RAW --browse-containers --no-config --disable-gc --blog "$CURR_DIR/$DBG_DIR/debug-raw.blog" &>>$DBG_DIR/debug.log
	if [ $? -ne 0 ]; then
		echo "failed to mount $clustername at $DBG_RAW"
		umount $DBG_RAW
		umount $DBG_MNT
		return 255
	fi
	wait_debug_mount_ready $DBG_RAW
}

# Collect file metadata and save them to archive
pack_meta() {
	local mnt_path=$1
	local file_path=$2
	local archive=$3
	local clustername=`cat $mnt_path/.vstorage.info/clustername`
	#
	# Preliminary checks
	#
	if [ "$clustername" == "" ]; then
		echo "vstorage cluster not found at $mnt_path"
		return 255
	fi
	if [ ! -f "$mnt_path/$file_path" ]; then
		echo "can't find $mnt_path/$file_path - check file path"
		return 255
	fi
	#
	# Create separate mounts of the cluster
	#
	mk_debug_dirs
	mk_debug_mounts $clustername
	if [ $? -ne 0 ]; then
		return 255
	fi
	#
	# Check the source file is indeed encoded
	#
	if [ ! -f "$DBG_RAW/$file_path/lsm.super" ]; then
		echo "file metadata not found - check it is encoded"
		umount $DBG_MNT $DBG_RAW
		return 255
	fi
	#
	# Open source file for reading so it can't be modified from other mounts in the cluster
	#
	exec 3<"$DBG_MNT/$file_path"
	if [ $? -ne 0 ]; then
		echo "failed to open source file for reading at $DBG_MNT/$file_path"
		umount $DBG_MNT $DBG_RAW
		return 255
	fi
	#
	# Collect some information
	#
	vstorage -q -c $clustername get-attr -p "$file_path" > $DBG_DIR/attributes
	if [ $? -ne 0 ]; then
		echo "failed to query attributes of $mnt_path/$file_path on cluster $clustername"
		exec 3>&-
		umount $DBG_MNT $DBG_RAW
		return 255
	fi
	vstorage -q -c $clustername ls -A -p "$file_path" > $DBG_DIR/file-list
	if [ $? -ne 0 ]; then
		echo "failed to query file list of $mnt_path/$file_path on cluster $clustername"
		exec 3>&-
		umount $DBG_MNT $DBG_RAW
		return 255
	fi
	find "$DBG_MNT/$file_path" -printf "%s\n" > $DBG_DIR/size
	rm -f $DBG_DIR/datastreams
	rm -rf $DBG_DIR/stream-info
	mkdir $DBG_DIR/stream-info
	local streams=`find "$DBG_RAW/$file_path" -name 'data.*'`
	while read -r filename; do
		local stream_name=$(basename "$filename")
		echo $stream_name $(stat --format=%s "$filename") $(vstorage -q -c $clustername get-attr -A -p "$file_path/$stream_name" | grep map-type=) >>$DBG_DIR/datastreams
		vstorage -q -c $clustername file-info -p "$file_path/$stream_name" > $DBG_DIR/stream-info/${stream_name#data.}
		if [ $? -ne 0 ]; then
			echo "failed to query stream information for "$file_path/$stream_name""
			exec 3>&-
			umount $DBG_MNT $DBG_RAW
			return 255
		fi
	done <<< "$streams"
	#
	# Pack metadata to archive
	#
	tar -czf $archive $DBG_DIR/attributes $DBG_DIR/size $DBG_DIR/file-list $DBG_DIR/datastreams $DBG_DIR/stream-info -C "$DBG_RAW/$file_path" . --exclude='data.*'
	echo "metadata packed to $archive"
	#
	# Perform final cleanup
	#
	exec 3>&-
	umount $DBG_MNT $DBG_RAW
}

# Unpack file metadata archive creating dummy file with metadata identical to the source but with zero data
unpack_meta() {
	local mnt_path=$1
	local file_path=$2
	local archive=$3
	local clustername=`cat $mnt_path/.vstorage.info/clustername`
	#
	# Preliminary checks
	#
	if [ "$clustername" == "" ]; then
		echo "vstorage cluster not found at $mnt_path"
		return 255
	fi
	if [ -e "$mnt_path/$file_path" ]; then
		echo "$mnt_path/$file_path already exists, we will not overwrite it to avoid loosing data"
		return 255
	fi
	local file_size=`tar -xf $archive $DBG_DIR/size --to-stdout`
	local file_encoding=$(tar -xf $archive $DBG_DIR/attributes --to-stdout | grep encoding=)
	local datastreams=`tar -xf $archive $DBG_DIR/datastreams --to-stdout`
	if [ "$file_size" == "" ] || [ "$file_encoding" == "" ] || [ "$datastreams" == "" ]; then
		echo "archive $archive is invalid or has incompatible format"
		return 255
	fi
	#
	# Create separate mounts of the cluster
	#
	mk_debug_dirs
	mk_debug_mounts $clustername
	if [ $? -ne 0 ]; then
		return 255
	fi
	#
	# Create target file
	#
	truncate "$DBG_MNT/$file_path" -s $file_size
	if [ $? -ne 0 ]; then
		echo "failed to create target file at $DBG_MNT/$file_path"
		umount $DBG_MNT $DBG_RAW
		return 255
	fi
	#
	# Open target file for reading so it can't be modified from other mounts in the cluster
	#
	exec 3<"$DBG_MNT/$file_path"
	if [ $? -ne 0 ]; then
		echo "failed to open file for reading at $DBG_MNT/$file_path"
		umount $DBG_MNT $DBG_RAW
		return 255
	fi
	#
	# Set proper encoding
	#
	vstorage -q -c $clustername set-attr -p "$file_path" $file_encoding
	if [ $? -ne 0 ]; then
		echo "failed to setup $file_encoding to $file_path"
		exec 3>&-
		umount $DBG_MNT $DBG_RAW
		return 255
	fi
	#
	# Remove old files in container before filling it with new ones
	#
	pushd "$DBG_RAW/$file_path" >/dev/null
	rm -rf *
	popd >/dev/null
	#
	# Create datastreams
	#
	local templ_path="$file_path.template"
	mkdir "$DBG_MNT/$templ_path"
	while read -r stream; do
		read -r -a stream_spec <<< "$stream"
		local stream_name=${stream_spec[0]}
		local stream_size=${stream_spec[1]}
		local stream_type=${stream_spec[2]#map-type=*:}
		mkdir "$DBG_MNT/$templ_path/$stream_name"
		vstorage -q -c $clustername set-attr -p "$templ_path/$stream_name" encoding=$stream_type
		if [ $? -ne 0 ]; then
			echo "failed to setup encoding=$stream_type to $templ_path/$stream_name"
			rm -rf "$DBG_MNT/$templ_path"
			exec 3>&-
			umount $DBG_MNT $DBG_RAW
			return 255
		fi
		touch "$DBG_MNT/$templ_path/$stream_name/tmp"
		mv "$DBG_RAW/$templ_path/$stream_name/tmp/data.0" "$DBG_RAW/$file_path/$stream_name"
		truncate "$DBG_RAW/$file_path/$stream_name" -s $stream_size
		if [ $? -ne 0 ]; then
			echo "failed to create target data stream $file_path/$stream_name"
			rm -rf "$DBG_MNT/$templ_path"
			exec 3>&-
			umount $DBG_MNT $DBG_RAW
			return 255
		fi
	done <<< "$datastreams"
	rm -rf "$DBG_MNT/$templ_path"
	#
	# Extract metadata
	#
	tar -xf $archive -C "$DBG_RAW/$file_path" --exclude=$DBG_DIR -m --no-same-owner --no-same-permissions
	echo "metadata unpacked to $mnt_path/$file_path"
	vstorage -q -c $clustername ls -p "$file_path"
	#
	# Perform final cleanup
	#
	exec 3>&-
	umount $DBG_MNT $DBG_RAW
}

meta_info() {
	local mnt_path=$1
	local file_path=$2
	local clustername=`cat $mnt_path/.vstorage.info/clustername`
	#
	# Preliminary checks
	#
	if [ "$clustername" == "" ]; then
		echo "vstorage cluster not found at $mnt_path"
		return 255
	fi
	#
	# Create separate mounts of the cluster
	#
	mk_debug_dirs
	mk_debug_mounts $clustername
	if [ $? -ne 0 ]; then
		return 255
	fi
	#
	# Check the source file is indeed encoded
	#
	if [ ! -f "$DBG_RAW/$file_path/lsm.super" ]; then
		echo "file metadata not found - check it is encoded"
		umount $DBG_MNT $DBG_RAW
		return 255
	fi
	#
	# Open source file for reading so it can't be modified from other mounts in the cluster
	#
	exec 3<"$DBG_MNT/$file_path"
	if [ $? -ne 0 ]; then
		echo "failed to open source file for reading at $DBG_MNT/$file_path"
		umount $DBG_MNT $DBG_RAW
		return 255
	fi
	#
	# Call vstorage-enc to analyze metadata
	#
	echo "### VERSION"
	vstorage-enc version-info "$DBG_RAW/$file_path"
	echo
	echo "### SIZE"
	vstorage-enc size-info    "$DBG_RAW/$file_path"
	#
	# Perform final cleanup
	#
	exec 3>&-
	umount $DBG_MNT $DBG_RAW
}

total_Logical=0
total_Physical=0
total_Used=0
total_Allocated=0

size_info() {
	set -f # disable filenames expansion
	local file=$1
	if [ ! -f "$file" ]; then
		echo "Not a file: $1"
		return 1
	fi
	#
	# Query size information
	#
	local sz_Logical=`find "$file" -printf %s`
	local sz_Physical=`du --block-size=1 "$file" | awk '{print $1}'`
	local vdu; vdu=`vstorage du -b "$file"`
	if [ $? -ne 0 ]; then
		echo $vdu
		return 2
	fi
	local sz_Used=`awk '{print $1}' <<< "$vdu"`
	local fstat; fstat=`vstorage -q file-stats "$file"`
	if [ $? -ne 0 ]; then
		echo $fstat
		return 255
	fi
	local sz_Allocated=`grep total <<< "$fstat" | awk '{print $4}'`
	#
	# Update totals
	#
	total_Logical=$((total_Logical+sz_Logical))
	total_Physical=$((total_Physical+sz_Physical))
	total_Used=$((total_Used+sz_Used))
	total_Allocated=$((total_Allocated+sz_Allocated))
	#
	# Calculate garbage fraction
	#
	local garbage
	if [ $sz_Physical -gt 0 ]; then
		garbage=$((100*$((sz_Physical-sz_Used))/sz_Physical))
	else
		garbage=0
	fi
	#
	# Validate size information
	#
	local valid
	if [ ! $sz_Used -le $sz_Logical ];       then valid='[invalid]'; fi
	if [ ! $sz_Used -le $sz_Allocated ];     then valid='[invalid]'; fi
	if [ ! $sz_Used -le $sz_Physical ];      then valid='[invalid]'; fi
	if [ ! $sz_Physical -le $sz_Allocated ]; then valid='[invalid]'; fi
	#
	# Print results. We use prefixes to allow safe extension of the output format.
	#
	printf "G=%s%%\tL=%s P=%s U=%s A=%s %s\n" $garbage $sz_Logical $sz_Physical $sz_Used $sz_Allocated $valid
	if [ ! $valid = "" ]; then
		# The return code 2 means that something wrong with the file but recusive scan may continue
		return 2
	fi
}

size_info_recursive() {
	set -f # disable filenames expansion
	local path=$1
	local find_args=$2
	if [ ! -d $1 ]; then
		echo "Not a directory: $1"
		return 1
	fi
	#
	# Query matching file paths
	#
	local files=`find "$path" -type f $find_args`
	local file_array
	IFS=$'\n' file_array=($files)
	#
	# Get information about each file
	#
	local file
	for file in ${file_array[@]}; do
		printf "%-40s\t" "$file"
		size_info "$file"
		result=$?
		if [ $result -ne 0 ] && [ $result -ne 2 ]; then
			return $result
		fi
	done
	#
	# Calculate garbage fraction
	#
	local garbage
	if [ $total_Physical -gt 0 ]; then
		garbage=$((100*$((total_Physical-total_Used))/total_Physical))
	else
		garbage=0
	fi
	#
	# Print total
	#
	printf "%-40s\t" "----total----"
	printf "G=%s%%\tL=%s P=%s U=%s A=%s %s\n" $garbage $total_Logical $total_Physical $total_Used $total_Allocated $valid
}


if [ $# -lt 1 ]; then
	echo "Vstorage encoded files debug tool. Usage:"
	echo "$(basename $0) command args .."
	echo "Valid commands:"
	echo "  pack-meta   cluster_mnt_path file_path archive"
	echo "  unpack-meta cluster_mnt_path file_path archive"
	echo "  meta-info   cluster_mnt_path file_path"
	echo "    where file_path is relative to the cluster mount path"
	echo "  size-info path [-R [<find args>]]"
	echo "    where <find args> are any args that find command accepts for ex. -name '*.img'"
	exit 1
fi

case "$1" in
"pack-meta")
	if [ $# -lt 4 ]; then
		echo "Parameters required: pack-meta cluster_mnt_path file_path archive"
		exit 1
	fi
	pack_meta "$2" "$3" "$4"
	exit $?
	;;
"unpack-meta")
	if [ $# -lt 4 ]; then
		echo "Parameters required: unpack-meta cluster_mnt_path file_path archive"
		exit 1
	fi
	unpack_meta "$2" "$3" "$4"
	exit $?
	;;
"meta-info")
	if [ $# -lt 3 ]; then
		echo "Parameters required: meta-info cluster_mnt_path file_path"
		exit 1
	fi
	meta_info "$2" "$3"
	exit $?
	;;
"size-info")
	if [ $# -lt 2 ]; then
		echo "Parameters required: size-info path [-R [<find args>]]"
		exit 1
	fi
	path=$2
	if [ $# -gt 2 ]; then
		if [ "$3" == "-R" ]; then
			shift 3
			size_info_recursive "$path" "$*"
			exit $?
		else
			echo "Invalid parameter(s): $*"
			exit 1
		fi
	fi
	size_info "$path"
	exit $?
	;;
*)
	echo "Unrecognized command: $1"
	exit 1
	;;
esac
