#! /bin/sh

#
# customize                                                     (jh,08.11.2005)
#

#
#   customize: manages the customized files and links of a host
#   Copyright (C) 2001, 2005  Jochen Hepp <jochen.hepp@gmx.de>
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#



# --------- definitions ---------

definitions () {
	# if environment variable CUSTOMIZE is not defined
	# then use the default directory $HOME/customize
	customhome="${CUSTOMIZE:-$HOME/customize}"

	version='0.0.14'

	script="${0##*/}"

	if [ -z "$HOSTNAME" ]; then
		HOSTNAME="`hostname 2>/dev/null || uname -n`"
	fi
	host="${HOSTNAME%%.*}"
}



# --------- usage ---------

print_usage () {
	cat <<EOF
Usage: $script OPTION FILE ...

       -c  --common      backup file is on all hosts equal
       -s  --special     backup file is special on this host
       -d  --diff        display differences between file and customized file
       -ds --diffshort   like diff, but do not show the context
       -r  --restore     restore file from customize directory
       -rf --force       restore file but don't ask if overwriting

           --cmpall      compare all files in the custom directory
           --tarcommon   create tarball from the common files
           --untarcommon unpack tarball with the common files
           --tar         create tarball from the special files of this host
           --untar       unpack tarball with the special files for this host

       -V  --version     display version number
       -h  --help        display this help and exit
EOF
}



# --------- version ---------

print_version () {
	cat <<-EOF
		$script $version

		Copyright (C) 2001, 2005 Jochen Hepp
		This program is distributed in the hope that it will be useful,
		but WITHOUT ANY WARRANTY; without even the implied warranty of
		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
		GNU General Public License for more details.

		Written by Jochen Hepp <jochen.hepp@gmx.de>.
EOF
}



# --------- mode ---------

switch_mode () { # mode, file ...   global: $host
	mode="$1"   # global
	shift
	local command=""

	case "$mode" in
		-c)  mode="--common" ;;
		-s)  mode="--special" ;;
		-d)  mode="--diff" ;;
		-ds) mode="--diffshort" ;;
		-r)  mode="--restore" ;;
		-rf) mode="--force" ;;
	esac

	case "$mode" in
		--common|--special) command="backup_file" ;;
		--restore|--force)  command="restore_file" ;;
		--diff|--diffshort) command="diff_file" ;;
		*)
			echo "$script: unrecognized option \`$mode'" >&2
			echo "$script: Try \`$script --help' for more information." >&2
			exit 1
			;;
	esac

	while [ $# -gt 0 ]; do
		# absolute file names
		eval "$command \"$host\" \"`file_name \"$1\"`\""
		shift
	done
}



# --------- make_dir ---------

make_dir () { # directory
	local dir="$1"

	echo "$script: create directory: $dir"
	if ! mkdir -p "$dir"; then
		print_error "$dir: can't create directory"
		exit 10
	fi
}



# --------- backup to customize directory (common or special) ---------

backup_file () { # host, file   global: $mode, $customhome
	local host="$1"
	local file="$2"
	local rootdir
	local dir
	local f

	if [ ! -L "$file" ]; then
		if [ -d "$file" ]; then
			print_error "$file: is a directory"
			exit 10
		elif [ ! -e "$file" ]; then
			print_error "$file: not found"
			exit 10
		fi
	fi

	if [ "$mode" = "--common" ]; then
		rootdir="$customhome/common"
		f="$customhome/$host$file"
		if [ -L "$f" ]; then
			print_error "a special link for $host exists!" \
			            "so use: $script --special FILE"
			exit 5
		elif [ -e "$f" ]; then
			print_error "a special file for $host exists!" \
			            "so use: $script --special FILE"
			exit 5
		fi
	else
		rootdir="$customhome/$host"
	fi

	dir="$rootdir${file%/*}"

	if [ ! -d "$dir" ]; then
		make_dir "$dir"
	fi

	f="$rootdir$file"
	if [ -L "$f" -o -e "$f" ]; then
		rescue_old_backup_file "$rootdir" "${rootdir}-old" "$file"
		rm -f "$f"
	fi

	f="${rootdir}-fallback$file"
	if [ -L "$f" -o -e "$f" ]; then
		rescue_old_backup_file "${rootdir}-fallback" "${rootdir}-old" "$file"
		rm -f "$f"
		dir="${file%/*}"
		while [ "$dir" ] && rmdir "${rootdir}-fallback$dir" 2>/dev/null; do
			dir="${dir%/*}"
		done
		rmdir "${rootdir}-fallback" 2>/dev/null
	fi

	print_mode "$file" "$rootdir$file" ''

	echo "$file" |\
	cpio -p -m -u --quiet "$rootdir"
}



# --------- rescue old backup file ---------

rescue_old_backup_file () { # rootdir, rescuedir, file   global: $customhome
	local rootdir="$1"
	local rescuedir="$2"
	local file="$3"
	local dir
	local rescue

	rescue="$rescuedir${file}_$(date +%y%m%d).old"

	dir="${rescue%/*}"

	if [ ! -d "$dir" ]; then
		make_dir "$dir"
	fi

	if [ ! -L "$rescue" -a ! -e "$rescue" ]; then
		cp -pdf "$rootdir$file" "$rescue"
		echo "$script: old file saved"
	fi
}



# --------- restore file ---------

restore_file () { # host, file
	local host="$1"
	local file="$2"
	local answer

	search_file "$host" "$file"  # -> $backupdir, $warning
	print_mode "$backupdir$file" "$file" "$warning"

	if [ "$mode" != "--force" ]; then
		if [ -L "$file" -o -e "$file" ]; then
			echo -n "destination exists, overwrite? [y/N] "
			read answer
			case "$answer" in
				y*|Y*) : ;;
				*)
					echo "skipped"
					return
					;;
			esac
		fi
	fi

	if [ -d "${file%/*}" ]; then
		( cd "$backupdir" && \
		  echo "${file#/}" | \
		  cpio -p -m -u --quiet / )
	else
		print_error "${file%/*}: destination directory not found!" >&2
		exit 5
	fi
}



# --------- diff file ---------

diff_file () { # host, file
	local host="$1"
	local file="$2"
	local lnkb
	local lnkf
	local backup

	search_file "$host" "$file"  # -> $backupdir, $warning
	backup="$backupdir$file"
	print_mode "$backup" "$file" "$warning"

	if [ -L "$backup" -o -L "$file" ]; then
		lnkb="`readlink \"$backup\"`"
		lnkf="`readlink \"$file\"`"
		if [ "$lnkb" != "$lnkf" ]; then
			echo "link- $lnkb"
			echo "link+ $lnkf"
		fi
	elif [ -f "$backup" -a -f "$file" ]; then
		if [ "$mode" = "--diffshort" ]; then
			diff -U 0 "$backup" "$file"
		else
			diff -u   "$backup" "$file"
		fi
	else
		ls -l "$backup"
		ls -l "$file"
	fi
}



# --------- search file ... (first special, than common) ---------

search_file () { # host, file   global: $customhome   -> $backupdir, $warning
	local host="$1"
	local file="$2"
	local subdir
	local f

	for subdir in "$host" "${host}-fallback" common common-fallback; do
		backupdir="$customhome/$subdir"
		f="$backupdir$file"
		if [ -L "$f" -o -e "$f" ]; then
			break
		fi
		backupdir=''
	done

	if [ -z "$backupdir" ]; then
		print_error "$file: has never been customized"
		exit 10
	fi

	if [ "${subdir%-fallback}" = "$subdir" ]; then
		warning=''
	else
		warning='ATTENTION: THIS IS ONLY A FALLBACK!'
	fi
}



# --------- print_mode ---------

print_mode () { # from, to, warning   global: $script, $mode
	echo "$script: ${mode#--}"
	echo "           $1"
	echo "           $2"
	if [ "$warning" ]; then
		echo
		echo "$warning"
		echo
	fi
}



# --------- compare all files in customhome (special or common) ---------

cmp_all_files () { # host   global: $customhome
	local host="$1"
	local subdir
	local warning
	local dir
	local d
	local file
	local found

	set -- common-fallback common "${host}-fallback" "$host"
	while [ $# -gt 0 ]; do
		subdir="$1"
		shift

		if [ "${subdir%-fallback}" = "$subdir" ]; then
			if [ ! -d "$customhome/$subdir" ]; then
				print_error "$customhome/$subdir: directory not found"
				continue
			fi
			warning=''
		else
			if [ ! -d "$customhome/$subdir" ]; then
				continue
			fi
			warning=' [only fallback]'
		fi

		(cd "$customhome/$subdir" && \
		 find . -type f -o -type l) | \
		while read file; do
			file="${file#.}"
			found=

			for d in "$@"; do
				if [ -L "$customhome/$d$file" -o \
				     -e "$customhome/$d$file" ]; then
					found=yes
					break
				fi
			done

			if [ -z "$found" ]; then
				cmp_file "$customhome/$subdir" "$file" "$warning"
			fi
		done
	done
}



# --------- compare file with file in directory ---------

cmp_file () { # rootdir file warning
	local rootdir="$1"
	local file="$2"
	local warning="$3"
	local backup="$rootdir$file"
	local file_w="$file$warning"

	if [ -L "$file" ]; then
		if [ ! -L "$backup" ]; then
			echo "is link: $file_w"
		elif [ "`readlink \"$file\"`" != "`readlink \"$backup\"`" ]; then
			echo "differs: $file_w"
		elif [ ! -e "$file" ]; then
			echo "dead ln: $file_w"
		elif [ "$warning" ]; then
			echo "is okay: $file_w"
		fi
	elif [ ! -e "$file" ]; then
		echo "missing: $file_w"
	else

		if [ -L "$backup" ]; then
			echo "is file: $file_w"
		elif [ -f "$file" -a -f "$backup" ]; then
			if ! cmp -s "$file" "$backup"; then
				if [ "$file" -nt "$backup" ]; then
					echo "newer:   $file_w"
				elif [ "$file" -ot "$backup" ]; then
					echo "older:   $file_w"
				else
					echo "differs: $file_w"
				fi
			elif [ "$warning" ]; then
				echo "is okay: $file_w"
			fi
#		else
#			ls -l "$backup"
#			ls -l "$file"
		fi
	fi
}



# --------- tar customize subdirectory ---------

tar_customize () { # subdirectory   global: $script
	local subdir="$1"

	if [ ! -d "$customhome/$subdir" ]; then
		print_error "$customhome/$subdir: directory not found"
		exit 10
	fi

	( umask 077 && \
	  tar -C "$customhome/.." \
	      -czf "$script-$subdir.tar.gz" "${customhome##*/}/$subdir" )
}



# --------- untar customize subdirectory ---------

untar_customize () { # subdirectory   global: $script
	local subdir="$1"

	if [ -d "$customhome/$subdir" ]; then
		print_error "$customhome/$subdir: directory does already exist"
		exit 10
	fi

	tar -C "$customhome/.." -xzf "$script-$subdir.tar.gz"
}



# --------- file_name (get absolute file name) ---------

file_name () { # filename
	local file="$PWD/$1"

	# absolute filename?
	case "$1" in
		/*) file="$1" ;;
	esac
	echo "$file" | \
	sed 's%^\./%%; s%/\./%/%g;
	     :up /\/[^/.]\+\/\.\.\// { s%/[^/.]\+/\.\./%/%g; b up; };
	     s%^\(\.\./\)\+%/%'
}



# --------- print error (multiline) ---------

print_error () { # text1, text2, ...   global: $script
	local error_string

	error_string="$script: ERROR: "
	echo "$error_string$1" >&2

	# error_string=${error_string//?/ }
	error_string='                  '
	shift
	while [ "$1" ]; do
		echo "$error_string$1" >&2
		shift
	done
}



# --------- main ---------

# main {

	umask 022
	definitions

	if [ $# -eq 0 ]; then
		print_usage >&2
		exit 1
	fi

	case "$1" in
		--version|-V)
			print_version
			exit 0
			;;
		--help|-h)
			print_usage
			exit 0
			;;
	esac

	if [ ! -d "$customhome" ]; then
		print_error "$customhome: custom directory not found" \
		            "please create it or set environment variable \$CUSTOMIZE"
		exit 50
	fi

	case "$1" in
		--cmpall)
			cmp_all_files "$host"
			;;
		--tarcommon)
			tar_customize "common"
			;;
		--untarcommon)
			untar_customize "common"
			;;
		--tar)
			tar_customize "$host"
			;;
		--untar)
			untar_customize "$host"
			;;
		*)
			switch_mode "$@"
			;;
	esac

	exit 0
# }


# --- end ---

