#! /bin/sh

#
# patch-source                                                  (jh,08.11.2004)
#

#
#   patch-source: generates and patches the source from a recipe
#   Copyright (C) 2004  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#



# --- definitions ---

src_prefix=.



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

print_usage () {
	cat <<EOF
Usage: $script [OPTIONS] FILE ...

       FILE                recipe of sources to use and patches to apply

       --src-prefix=DIR    prefix of source directory
                           [default=$src_prefix]
       --[no-]verify-gpg   [don't] use gpg to verify signatures
                           [default=$verify_gpg]
       --trustlevel=LEVEL  set needed trustworthy of a signature:
                           undefined, never, marginal, fully or ultimate
                           [default=$trustdefault]

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



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

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

		Copyright (C) 2004 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
}



# --------- config ---------

config () { # destdir, trustdefault, src_prefix   global: verify_gpg
	local destdirdefault="$1"
	local trustdefault="$2"
	local src_prefix="$3"
	local srcdir="$src_prefix"
	local destdir=''
	local checkdestdir=''
	local line
	local cmd
	local trustlevel="$trustdefault"
	local keylongid=''
	local keyname=''
	local keyemail=''
	local gpg_status=''
	local gpg_info=''

	if [ "$verify_gpg" != 'no' ]; then
		gpg_status="$(mktemp)"
		gpg_info="$(mktemp)"
		if [ ! -f "$gpg_status" -o ! -f "$gpg_info" ]; then
			error 'unable to create temporary files'
			exit 1
		fi
	fi

	while read line; do
		case "$line" in
			\#*)
				line="`echo \"$line\" | sed 's/^# *//; s/ .*$//'`"
				if [ "$line" -a -z "$destdir" ]; then
					destdir="$line"
					checkdestdir=''
				fi
				;;

			trustlevel=*)
				line="${line#trustlevel=}"
				if [ `trustlevel "$line"` -gt `trustlevel "$trustdefault"` ]; then
					trustlevel="$line"
				else
					trustlevel="$trustdefault"
				fi
				;;

			keylongid=*|keyname=*|keyemail=*)
				eval "`echo \"${line%%=*}\" | sed 's/-/_/g'`=\"${line##*=}\""
				;;

			dir=*)
				destdir="${line#dir=}"
				checkdestdir=''
				;;

			src=*)
				srcdir="${line#src=}"
				if [ "$src_prefix" -a "${src_prefix%/}" != '.' ]; then
					case "$srcdir" in
						/*|\
						http://*|\
						ftp://*) ;;
						*)  srcdir="${src_prefix%/}/$srcdir" ;;
					esac
				fi
				;;

			*)
				cmd="${line#cmd=}"
				if [ -z "$line" -o -z "$cmd" ]; then continue; fi

				if [ -z "$checkdestdir" ]; then
					if [ -z "$destdir" -o \
					     \( -f "$destdir" -a "$destdir.d" = "$destdirdefault" \) \
					   ]; then
						destdir="$destdirdefault"
					fi
					makedestdir "$destdir"
					checkdestdir='ok'
				fi

				if [ "$cmd" != "$line" ]; then
					do_cmd "$cmd" "$destdir"
				else
					do_file "$line" "$srcdir" "$destdir" \
					        "$trustlevel" "$keylongid" "$keyname" "$keyemail" \
					        "$gpg_status" "$gpg_info"
				fi
				;;
		esac
	done

	if [ "$verify_gpg" != 'no' ]; then
		rm -f "$gpg_status" "$gpg_info"
	fi
}



# --------- makedestdir ---------

makedestdir () { # destdir
	if [ -z "$destdir" ]; then
		destdir="$1"
	fi
	echo "# $destdir"
	makedir "$destdir"
}



# --------- do_cmd ---------

do_cmd () { # cmd, destdir
	local cmd="$1"
	local destdir="$2"
	local cwd

	echo "  Command  $cmd"
	cwd="$PWD"
	if [ ! -d "$destdir" ]; then
		error "$destdir: no such directory"
		exit 1
	fi
	cd "$destdir" || \
	{	error "$destdir: can not change to directory";
		exit 1; }
	if ! eval "$cmd"; then
		error "$cmd: command failed"
		exit 1
	fi
	cd "$cwd"
}



# --------- do_file ---------

do_file () { # file, srcdir, destdir
             # trustlevel, keylongid, keyname, keyemail, gpg_status, gpg_info
             # global: verify_gpg
	local file_name="$1"
	local srcdir="$2"
	local destdir="$3"
	local trustlevel="$4"
	local keyid="$5"
	local keyname="$6"
	local keyemail="$7"
	local gpg_status="$8"
	local gpg_info="$9"
	local tmp
	local ext
	local file=''
	local name="${file_name##*/}"
	local signfile=''
	local signfile_name

	if [ "`archiv_pipe \"$name\"`" ]; then
		echo -n "Extracting $file_name "
	else
		echo -n "  Applying $file_name "
	fi

	if [ "$srcdir" -a "${srcdir%/}" != '.' ]; then
		case "$file_name" in
			/*|\
			http://*|\
			ftp://*) ;;
			*)  file_name="${srcdir%/}/$file_name" ;;
		esac
	fi

	if [ "$verify_gpg" != 'no' ]; then
		for ext in sign asc; do
			signfile_name="$file_name.$ext"
			tmp="`get_file \"$signfile_name\"`"
			if [ -r "$tmp" ]; then
				signfile="$tmp";
				break
			fi
		done
		echo -n '.'

		if [ -z "$signfile" ]; then
			echo ' failed.'
			error "$file: no signature file found"
			rm -f "$gpg_status" "$gpg_info"
			exit 1
		fi

		file="`get_file \"$file_name\"`"
		echo -n '.'

		file_verify "$file" "$file_name" "$signfile" "$signfile_name" \
		            "$trustlevel" "$keylongid" "$keyname" "$keyemail" \
		            "$gpg_status" "$gpg_info"
		echo -n '.'

		unget_file "$signfile" "$signfile_name"
	fi


	if [ -z "$file" ]; then
		file="`get_file \"$file_name\"`"
		echo -n '.'
	fi

	file_auto "$file" "$file_name" "$destdir"

	unget_file "$file" "$file_name"

	echo ' done.'
}



# --------- file_auto ---------

file_auto () { # file, file_name, destdir
	local file="$1"
	local file_name="$2"
	local destdir="$3"
	local name="${file_name##*/}"
	local pipe=''
	local cwd=''
	local tmpdir

	if [ ! -r "$file" ]; then
		error "$file: can't read file"
		exit 1
	fi

	pipe="`archiv_pipe \"$name\"`"

	if [ "$pipe" ]; then
		tmpdir="`env TMPDIR=\"$destdir\" mktemp -d`"
		if [ -z "$tmpdir" ]; then
			error 'unable to create temporary directory'
			exit 1
		fi

		$pipe < "$file" | tar -C "$tmpdir" -xf -
		if [ "`find \"$tmpdir\" -mindepth 1 -maxdepth 1 -type d -print | \
		       wc -l`" -ne 1 ]; then
			error "$file_name: does not contain exactly one directory"
			exit 1
		else
			find "$tmpdir" -mindepth 2 -maxdepth 2 -print0 | \
			xargs -0 mv --target-directory "$destdir"
		fi
		find "$tmpdir" -depth -type d -exec rmdir "{}" \;

	else
		pipe="`patch_pipe \"$name\"`"
		if [ -z "$pipe" ]; then
			error "$file_name: unknown suffix"
			exit 1
		fi

		cwd="$PWD"
		cd "$destdir" ||  \
		{	error "$destdir: no such directory";
			exit 1; }
		( cd "$cwd" && $pipe < "$file" ) | \
		patch -p1 -s

		if [ $? -ne 0 ]; then
		 	echo ' failed.'
			exit 1
		fi

		if [ "`find . '(' -name '*.rej' -o -name '.*.rej' ')' -print`" ]; then
		 	echo ' failed.'
			error 'Aborting.  Reject files found.'
			exit 1
		fi
		find . '(' -name '*.orig' -o -name '.*.orig' ')' -print0 | \
		xargs -0 rm -f

		cd "$cwd"
	fi
}



# --------- archiv_pipe ---------

archiv_pipe () { # name
	local name="$1"

	case "$name" in
		*.tgz|\
		*.tar.gz)  echo 'gunzip -dc' ;;
		*.tbz|\
		*.tar.bz|\
		*.tbz2|\
		*.tar.bz2) echo 'bunzip2 -dc' ;;
		*.tar.Z)   echo 'uncompress -c' ;;
		*.tar.zip) echo 'unzip -c' ;;
		*.tar)     echo 'cat';;
	esac
}



# --------- patch_pipe ---------

patch_pipe () { # name
	local name="$1"

	case "$name" in
		patch*.gz|\
		*.diff.gz)   echo 'gunzip -dc' ;;
		patch*.bz2|\
		*.diff.bz2)  echo 'bunzip2 -dc' ;;
		patch*.Z|\
		*.Z)         echo 'uncompress -c' ;;
		patch*.zip|\
		*.zip)       echo 'unzip -c' ;;
		patch*|\
		*.patch|\
		*.diff)      echo 'cat' ;;
	esac
}



# --------- file_verify ---------

file_verify () { # file, file_name, signfile, signfile_name,
                 # trustlevel, keylongid, keyname, keyemail, tmp1, tmp2
                 # global: verbose
	local file="$1"
	local file_name="$2"
	local signfile="$3"
	local signfile_name="$4"
	local trustlevel="$5"
	local keyid="$6"
	local keyname="$7"
	local keyemail="$8"
	local gpg_status="$9"
	local gpg_info="${10}"
	local file
	local gpgargs
	local line
	local trust
	local siglongid
	local signame
	local sigemail

	if [ ! -r "$file" ]; then
		error "$file: can't read file"
		exit 1
	fi

	if [ ! -r "$signfile" ]; then
		error "$file: no signature file found"
		exit 1
	fi

	if [ "$verbose" ]; then
		gpgargs=--verbose
	else
		gpgargs=--quiet
	fi

	exec 7>"$gpg_status" 8>"$gpg_info"

	if ! gpg --no-options --batch --no-tty "$gpgargs" \
	         --status-fd 7 --logger-fd 8 \
	         --verify "$signfile" "$file"; then
		exec 7>&- 8>&-
		error "$signfile_name: signature not trusted"
		cat "$gpg_info" >&6
		rm -f "$gpg_status" "$gpg_info"
		exit 1

	else
		exec 7>&- 8>&-
		trust=''
		siglongid=''
		signame=''
		sigemail=''
		exec 8<&0 <"$gpg_status"
			while read line; do
				set -- $line
				if [ "$1" = '[GNUPG:]' ]; then
					case "$2" in
						TRUST_*) trust="${2#TRUST_}" ;;
						GOODSIG)
							siglongid="`echo \"$3\" | sed 'y/ABCDEF/abcdef/'`"
							shift 3
							signame="`echo \"$*\" | sed 's/<.*$//'`"
							sigemail="`echo \"$*\" | sed 's/^.*<//; s/>$//'`"
							;;
					esac
				fi
			done
		exec 0<&8 8<&-

		if [ `trustlevel "$trust"` -lt `trustlevel "$trustlevel"` ]; then
			error "$signfile_name: signature not trusted (trust to small)"
			cat "$gpg_info" >&6
			rm -f "$gpg_status" "$gpg_info"
			exit 1
		fi
		rm -f "$gpg_info"

		if [ "$keylongid" ]; then
			keylongid="`echo \"$keylongid\" | sed 'y/ABCDEF/abcdef/'`"
			if [ "$keylongid" != "$siglongid" ]; then
				error "$keylongid <> $siglongid: long ids of signing key differ"
				rm -f "$gpg_status" "$gpg_info"
				exit 1
			fi
		fi
		if [ "$keyname" -a "$keyname" != "$signame" ]; then
			error "$keyname <> $signame: names of signing key differ"
			rm -f "$gpg_status" "$gpg_info"
			exit 1
		fi
		if [ "$keyemail" -a "$keyemail" != "$sigemail" ]; then
			error "$keyemail <> $sigemail: email addresses of signing key differ"
			rm -f "$gpg_status" "$gpg_info"
			exit 1
		fi
	fi
}



# --------- trustlevel ---------

trustlevel () { # trustlevel
	local trustlevel="$1"
	local value=-2;

	case "$trustlevel" in
		UNDEFINED|undefined) value=-1 ;;
		NEVER|never)         value=0 ;;
		MARGINAL|marginal)   value=1 ;;
		FULLY|fully)         value=2 ;;
		ULTIMATE|ultimate)   value=3 ;;
	esac

	echo "$value"
}



# --------- get_file ---------

get_file () { # file
	local file_name="$1"
	local file="$file_name"

	case "$file_name" in
		http://*|\
		ftp://*)
			file="$(mktemp)"
			if [ ! -f "$file" ]; then
				error 'unable to create temporary file'
				exit 1
			fi
			wget -q -O "$file" "$file_name" >&5 2>&6 || \
			rm -f "$file"
			;;
	esac

	echo "$file"
}



# --------- unget_file ---------

unget_file () { # file, file_name
	local file="$1"
	local file_name="$2"

	if [ "$file" != "$file_name" ]; then
		if [ -f "$file" ]; then
			rm -f "$file"
		fi
	fi
}



# --------- makedir ---------

makedir () { # dir
	local dir="$1"

	if [ ! -d "$dir" ]; then
		mkdir "$dir" || \
		{	error "$dir: unable to create directory";
			exit 1; }
	fi
}



# --------- error ---------

error () { # error
	echo "$script: $1" 1>&6
}



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

# main {
	script="${0##*/}"
	version='0.0.6'
	filefound=''

	# file descriptors
	# 5: output
	# 6: warnings
	exec 5>&1 6>&2

	if [ "$SRC_PREFIX" ]; then
		src_prefix="$SRC_PREFIX"
	fi

	verify_gpg='yes'
	trustdefault='marginal'

	if [ $# -lt 1 ]; then
		print_usage >&6
		exit 1
	fi

	while [ $# -gt 0 ]; do
		case "$1" in
			--version|-V)
				print_version
				exit 0
				;;
			--help|-h)
				print_usage
				exit 0
				;;
			--src-prefix=*)
				v="${1#--}"
				eval "`echo \"${v%%=*}\" | sed 's/-/_/g'`=\"${1##*=}\""
				;;
			--verify-gpg)
				eval "`echo \"${1#--}\" | sed 's/-/_/g'`=yes"
				;;
			--no-verify-gpg)
				eval "`echo \"${1#--no-}\" | sed 's/-/_/g'`=no"
				;;
			--trustlevel=*)
				trustdefault="${1##*=}"
				if [ `trustlevel "$trustdefault"` -lt -1 ]; then
					error "$trustdefault: unknown trust level"
					exit 1
				fi
				;;
			*)
				srcdir=''
				destdir=''
				if [ ! -r "$1" ]; then
					error "$1: cannot read file"
					exit 1
				else
					exec 9<&0 <"$1"
						config "${1##*/}.d" "$trustdefault" "$src_prefix"
					exec 0<&9 9<&-
					filefound='yes'
				fi
				;;
		esac
		shift
	done

	if [ "$filefound" != 'yes' ]; then
		print_usage >&6
		exit 1
	fi

	exec 5>&- 6>&-
# }



# --- end ---

