#! /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 and series checks are irrelevant to this command.
skip_version_check=1
skip_series_check=1

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|*.tar.zst|*.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"
				;;
			"Zstandard compressed data"*)
				echo -n "z" >&4
				echo "zstd -cd $file | md5sum" >&5
				set -- $(zstd -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
		# Legacy 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. 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 the new, faster method to process the spec file. In this mode,
	rpmbuild is told to generate a working tree directly in the target
	directory. This is the default (since quilt version 0.67).

The setup command is only guaranteed to work properly on spec files where
applying all the patches is the last thing done in the %%prep section. This
is a design limitation due to the fact that quilt can only operate on
patches. If other commands in the %%prep section modify the patched files,
they must come first, otherwise you won't be able to push the patch
series.

For example, a %%prep section where you first unpack a tarball, then
apply patches, and lastly perform a tree-wide string substitution, is
not OK. For \"quilt setup\" to work, it would have to be changed to
unpacking the tarball, then performing the tree-wide string substitution,
and lastly applying the patches.
"
		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"

export QUILT_SETUP_FAST=1
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)
		QUILT_SETUP_FAST=1
		shift ;;
	--)
		shift
		break ;;
	esac
done

if [ $# -ne 1 ]
then
	usage
fi

# Read in library functions
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
	# Damn, found an enclosing quilt directory; don't follow its settings
	cd $SUBDIR
	unset SUBDIR
	unset QUILT_PC QUILT_PATCHES QUILT_SERIES
	: ${QUILT_PC:=.pc}
	: ${QUILT_PATCHES:=patches}
	: ${QUILT_SERIES:=series}
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
