#! /usr/bin/env bash

#  This script is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License version 2 as
#  published by the Free Software Foundation.
#
#  See the COPYING and AUTHORS files for more details.

# Version check is irrelevant to this command.
skip_version_check=1

# Read in library functions
if [ "$(type -t patch_file_name)" != function ]
then
	if ! [ -r $QUILT_DIR/scripts/patchfns ]
	then
		echo "Cannot read library $QUILT_DIR/scripts/patchfns" >&2
		exit 1
	fi
	. $QUILT_DIR/scripts/patchfns
	if [ -n "$SUBDIR" ]
	then
		cd $SUBDIR
		unset SUBDIR
	fi
fi

check_for_existing_directories()
{
	local tag dir last_dir arg status=0

	while read tag dir arg
	do
		[ "$dir" != "." -a "$dir" != "$last_dir" ] || continue

		if [ -e "$prefix$dir" ]
		then
			printf $"Directory %s exists\n" \
			       "$prefix$dir" >&2
			status=1
		fi
		last_dir=$dir
	done < $tmpfile

	return $status
}

check_for_existing_files()
{
	local tag dir last_dir arg status=0

	while read tag dir arg
	do
		[ "$tag" = "patch" -a "$dir" != "$last_dir" ] || continue

		if [ -e "$prefix$dir/$QUILT_PATCHES" ]
		then
			printf $"Directory %s exists\n" \
			       "$prefix$dir/$QUILT_PATCHES" >&2
			status=1
		fi
		if [ -e "$prefix$dir/$QUILT_SERIES" ]
		then
			printf $"File %s exists\n" \
			       "$prefix$dir/$QUILT_SERIES" >&2
			status=1
		fi
		last_dir=$dir
	done < $tmpfile

	return $status
}

# Resolve ".." in path and clean up double slashes and "."
normalize_path()
{
	echo "$1" | sed -r -e 's://:/:g' \
			   -e 's:/\.(/\.)*(/|$):\2:g' \
			   -e ':again' \
			   -e 's:/[^/]+/\.\.(/|$):\1:g' \
			   -e 'tagain'
}

create_symlink()
{
	local target=$1 link=$2 up
	if [ "${target:0:1}" = / -o "${link:0:1}" = / ]
	then
		[ "${target:0:1}" = / ] || target=$PWD/$target
		ln -s "$target" "$link"
		return
	fi

	set -- "$(normalize_path "$PWD/$target")" \
	       "$(normalize_path "$PWD/$link")"
	while [ "${1%%/*}" = "${2%%/*}" ]
	do
		set -- "${1#*/}" "${2#*/}"
	done
	up=$(echo "$2" | sed -r -e 's:(^|/)[^/]*$::' -e 's:[^/]+:..:g')
	set -- "${up:+$up/}$1"
	set -- "${1%/}"
	ln -s "${1:-.}" "$link"
}

dir_to_dir()
{
	local from=$1 to=$2

	[ "${from:0:1}" = / ] || from=$PWD/$from
	from=$(normalize_path "$from")

	[ "${to:0:1}" = / ] || to=$PWD/$to
	to=$(normalize_path "$to")

	# If the target is a subdirectory of the origin, we can express the path
	# in a relative way. Otherwise, return the absolute path.
	if [ "${to:0:${#from}}" = "$from" ]
	then
		to=${to:${#from}}
		to=${to#/}
	fi

	echo "$to"
}

# create md5 sums, also for uncompressed files
create_md5sums()
{
	local sourcedir=$1 output=$2
	local file basename filetype

	echo -n "### md5sum: " >&4
	shopt -s nullglob
	for file in "$sourcedir"*
	do
		basename=${file##*/}
		case "$basename" in
			ready|bigpack|_constraints|_service|baselibs.conf|MD5SUMS|MD5SUMS.meta|*.spec|*.changes|*.sig|*.sign|*rpmlintrc)
				continue
				;;
			# In fast mode, we are only interested in patches, so filter out
			# archives
			*.tar|*.tar.Z|*.tar.gz|*.tgz|*.tar.bz2|*.tar.xz|*.tar.lz|*.zip|*.7z)
				[ -n "$QUILT_SETUP_FAST" ] && continue
				;;
		esac
		[ -f "$file" ] || continue
		echo -n "." >&4
		echo "md5sum < $file" >&5
		set -- $(md5sum < "$file")
		echo "$1 $basename"

		case "$file" in
			*.lzma)
				# file doesn't reliably recognize lzma-compressed files
				filetype="lzma"
				;;
			*.xz)
				# old versions of file don't know about xz-compressed
				# files
				filetype="xz"
				;;
			*)
				filetype=$(file -b "$file")
				;;
		esac

		case "$filetype" in
			compress*|gzip*)
				echo -n "g" >&4
				echo "gzip -cd $file | md5sum" >&5
				set -- $(gzip -cd "$file" | md5sum)
				echo "$1 $basename"
				;;
			bzip2*)
				echo -n "b" >&4
				echo "bzip2 -cd $file | md5sum" >&5
				set -- $(bzip2 -cd "$file" | md5sum)
				echo "$1 $basename"
				;;
			xz*|XZ*)
				echo -n "x" >&4
				echo "xz -cd $file | md5sum" >&5
				set -- $(xz -cd "$file" | md5sum)
				echo "$1 $basename"
				;;
			lzma*)
				echo -n "l" >&4
				echo "lzma -cd $file | md5sum" >&5
				set -- $(lzma -cd "$file" | md5sum)
				echo "$1 $basename"
				;;
			lzip*)
				echo -n "l" >&4
				echo "lzip -cd $file | md5sum" >&5
				set -- $(lzip -cd "$file" | md5sum)
				echo "$1 $basename"
				;;
		esac
	done > $output
	echo >&4
	shopt -u nullglob
}

# Uses global variables: verbose, sourcedir, targetdir
inspect()
{
	local specfile=$1 specdir
	local abs_sourcedir=$sourcedir tmpdir

	[ "${abs_sourcedir:0:1}" = / ] || abs_sourcedir=$PWD/$abs_sourcedir

	if [ "${specfile:0:1}"  = / ]
	then
		specdir=$(dirname "$specfile")
		specfile=${spec_file##*/}
	else
		specdir=$PWD
	fi

	tmpdir=$(gen_tempfile -d ${VARTMPDIR:-/var/tmp}/${0##*/})
	mkdir -p $tmpdir || exit 1
	add_exit_handler "rm -rf $tmpdir"
	mkdir -p $tmpdir/bin
	ln -s $QUILT_DIR/scripts/inspect-wrapper $tmpdir/bin/patch
	ln -s $QUILT_DIR/scripts/inspect-wrapper $tmpdir/bin/tar
	ln -s $QUILT_DIR/scripts/inspect-wrapper $tmpdir/bin/unzip
	ln -s $QUILT_DIR/scripts/inspect-wrapper $tmpdir/bin/7z

	# Redirect file descriptors
	# 5 is used in verbose mode, 4 in non-verbose mode, and 2 for both (real errors)
	if [ -n "$verbose" ]
	then
		exec 3>&1 5>&2 4>/dev/null
	else
		exec 3>&1 4>&2 5>/dev/null
	fi

	if [ -n "$QUILT_SETUP_FAST" ]
	then
		# Fast mode
		[ -d "$targetdir" ] || mkdir -p "$targetdir" || exit 1
		ln -s "$targetdir" $tmpdir/build
		export -f create_md5sums
	else
		# Standard mode
		mkdir -p $tmpdir/build
		create_md5sums "$sourcedir" $tmpdir/md5sums
	fi
	export -f normalize_path dir_to_dir

	# let rpm do all the dirty specfile stuff ...
	echo -n "### rpmbuild: " >&4

	PATH="$tmpdir/bin:$PATH" \
	rpmbuild --eval "%define _sourcedir $abs_sourcedir" \
		 --eval "%define _specdir   $specdir" \
		 --eval "%define _builddir  $tmpdir/build" \
		 --eval "%define __patch    $tmpdir/bin/patch" \
		 --eval "%define __tar      $tmpdir/bin/tar" \
		 --eval "%define __unzip    $tmpdir/bin/unzip" \
		 --eval "%define __7zip     $tmpdir/bin/7z" \
		 --eval "$DEFINE_FUZZ" \
		 --nodeps \
		 -bp "$specdir/$specfile" < /dev/null >&5 2>&5
	status=$?
	echo >&4
	return $status
}

usage()
{
	printf $"Usage: quilt setup [-d path-prefix] [-v] [--sourcedir dir] [--fuzz=N] [--slow|--fast] {specfile|seriesfile}\n"
	if [ x$1 = x-h ]
	then
		printf $"
Initializes a source tree from an rpm spec file or a quilt series file.

-d	Optional path prefix for the resulting source tree.

--sourcedir
	Directory that contains the package sources. Defaults to \`.'.

-v	Verbose debug output.

--fuzz=N
	Set the maximum fuzz factor (needs rpm 4.6 or later).

--slow	Use the original, slow method to process the spec file. This is the
	default for now, but that might change in the future. In this mode,
	rpmbuild generates a working tree in a temporary directory while all
	its actions are recorded, and then everything is replayed from scratch
	in the target directory.

--fast	Use an alternative, faster method to process the spec file. In this
	mode, rpmbuild is told to generate a working tree directly in the
	target directory.
"
		exit 0
	else
		exit 1
	fi
}

options=`getopt -o d:vh --long sourcedir:,fuzz:,slow,fast -- "$@"`

if [ $? -ne 0 ]
then
	usage
fi

eval set -- "$options"

prefix=
sourcedir=

while true
do
	case "$1" in
	-d)
		prefix=${2%/}/
		shift 2 ;;
	-h)
		usage -h ;;
	-v)
		verbose=1
		shift ;;
	--sourcedir)
		sourcedir=${2%/}/
		shift 2 ;;
	--fuzz)
		# Only works with rpm 4.6 and later
		DEFINE_FUZZ="%define _default_patch_fuzz $2"
		shift 2 ;;
	--slow)
		QUILT_SETUP_FAST=
		shift ;;
	--fast)
		export QUILT_SETUP_FAST=1
		shift ;;
	--)
		shift
		break ;;
	esac
done

if [ $# -ne 1 ]
then
	usage
fi

tmpfile=$(gen_tempfile)
add_exit_handler "rm -f $tmpfile"

# The patches link will point to the source directory, while extra patches
# may be available under $prefix. If the latter is a subdirectory of the former,
# a prefix can be added to fix up the path to the extra patches.
export QUILT_SETUP_PREFIX=$(dir_to_dir "$sourcedir" "$prefix")

case "$1" in
*.spec)
	spec_file=$1

	# check if rpmbuild is installed before running inspect
	check_external_tool rpmbuild rpm-build

	if [ -n "$QUILT_SETUP_FAST" ]
	then
		if [ "${prefix:0:1}" = / ]
		then
			targetdir=$prefix
		else
			targetdir=$PWD/$prefix
		fi
	fi

	if ! inspect "$spec_file" 2>&1 > $tmpfile
	then
		printf $"The %%prep section of %s failed; results may be incomplete\n" "$spec_file"
		if [ -z "$verbose" ]
		then
			printf $"The -v option will show rpm's output\n"
		fi
	fi
	;;
*)
	series_file=$1
	# parse series file
	while read line; do
		set -- $line
		case "$@" in
		"# Sourcedir: "*)
			shift 2
			tar_dir="$@"
			tar_dir=${tar_dir// /\\ }
			;;
		"# Source: "*)
			shift 2
			source="$@"
			filetype=$(file -b "$source")
			case "$filetype" in
			Zip*)
				echo "unzip ${tar_dir:-.} ${source// /\\ }"
				;;
			7z*)
				echo "7z ${tar_dir:-.} ${source// /\\ }"
				;;
			*)
				echo "tar ${tar_dir:-.} ${source// /\\ }"
				;;
			esac
			;;
		"# Patchdir: "*)
			shift 2
			patch_dir="$@"
			patch_dir=${patch_dir// /\\ }
			;;
		''|'#'*)
			;;
		*)
			echo "patch ${patch_dir:-.} $@" ;;
		esac
	done < "$series_file" > $tmpfile
	;;
esac

# If running on a spec file in fast mode, the source tree is already unpacked;
# in all other cases, we must prepare the source tree now
if [ -z "$QUILT_SETUP_FAST" -o -n "$series_file" ]
then
	# Make sure that unpacking will not overwrite anything
	check_for_existing_directories || exit 1

	while read tag dir arg1 arg2
	do
		[ "${arg1:0:1}" != '#' ] || continue

		case "$tag" in
		tar)
			tarball=$sourcedir$arg1
			if [ ! -e "$tarball" ]
			then
				printf $"File %s not found\n" "$tarball" >&2
				exit 1
			fi
			printf $"Unpacking archive %s\n" "$tarball"
			mkdir -p "${prefix:-.}" "$prefix$dir"
			cat_file "$tarball" \
			| tar xf - -C "$prefix$dir"
			;;
		unzip)
			tarball=$sourcedir$arg1
			if [ ! -e "$tarball" ]
			then
				printf $"File %s not found\n" "$tarball" >&2
				exit 1
			fi
			printf $"Unpacking archive %s\n" "$tarball"
			mkdir -p "${prefix:-.}" "$prefix$dir"
			unzip -qqo "$tarball" -d "$prefix$dir"
			;;
		7z)
			tarball=$sourcedir$arg1
			if [ ! -e "$tarball" ]
			then
				printf $"File %s not found\n" "$tarball" >&2
				exit 1
			fi
			printf $"Unpacking archive %s\n" "$tarball"
			mkdir -p "${prefix:-.}" "$prefix$dir"
			7z x -bd "$tarball" -o"$prefix$dir"
			;;
		esac
	done < $tmpfile
fi

if ! check_for_existing_files
then
	echo $"Trying alternative patches and series names..." >&2
	QUILT_PATCHES=quilt_patches
	QUILT_SERIES=quilt_series
	check_for_existing_files || exit 1
fi

series_header()
{
	local tar_dir=$1 tar_file=$2 dir=$3

	echo "# Patch series file for quilt, created by ${0##*/}"
	[ -n "$tar_dir" ] && echo "# Sourcedir: $tar_dir"
	[ -n "$tar_file" ] && echo "# Source: $tar_file"
	echo "# Patchdir: $dir"
	echo "#"
}

while read tag dir arg1 arg2
do
	case "$tag" in
	tar|unzip|7z)
		tar_dir=$dir
		[ "$tar_dir" = . ] && tar_dir=
		tar_file=$arg1
		;;
	patch)
		if [ ! -e "$prefix$dir/$QUILT_PATCHES" ]
		then
			create_symlink "$sourcedir" "$prefix$dir/$QUILT_PATCHES"
			(cd "$prefix$dir" && create_db)
		fi

		if [ -n "$series_file" ]
		then
			[ -e "$prefix$dir/$QUILT_SERIES" ] \
			|| create_symlink "$series_file" \
					  "$prefix$dir/$QUILT_SERIES"
		else
			if ! [ -e "$prefix$dir/$QUILT_SERIES" ]
			then
				series_header "$tar_dir" "$tar_file" "$dir" \
					      > "$prefix$dir/$QUILT_SERIES"
			fi
			echo "$arg1" $arg2 >> "$prefix$dir/$QUILT_SERIES"
		fi
		;;
	esac
done < $tmpfile
