#!/hint/bash

. /usr/share/makepkg/util.sh

# global shell options for enhanced bash scripting
shopt -s extglob globstar nullglob


# Useful functions
UMASK=""
set_umask () {
	export UMASK="${UMASK:-$(umask)}"
	umask 002
}

restore_umask () {
	umask "$UMASK" >/dev/null
}

# Proxy function to check if a file exists. Using [[ -f ... ]] directly is not
# always wanted because we might want to expand bash globs first. This way we
# can pass unquoted globs to is_globfile() and have them expanded as function
# arguments before being checked.
is_globfile() {
	[[ -f $1 ]]
}

# just like mv -f, but we touch the file and then copy the content so
# default ACLs in the target dir will be applied
mv_acl() {
	rm -f "$2"
	touch "$2"
	cat "$1" >"$2" || return 1
	rm -f "$1"
}

# just like mv -f, but won't break relative symlinks
mv_ln() {
	if [[ -L $1 ]]; then
		if [[ -d $2 ]]; then
			set -- "$1" "$2/${1##*/}"
		fi
		if [[ ! $1 -ef $2 ]]; then
			local target
			target=$(readlink -f -- "$1") || return 1
			ln -srfT -- "$target" "$2" || return 1
		fi
		rm -f -- "$1"
	else
		mv -f -- "$1" "$2"
	fi
}

# set up general environment
WORKDIR=$(mktemp -dt "${0##*/}.XXXXXXXXXX")
LOCKS=()
REPO_MODIFIED=0

# Used: plain, msg, msg2, warning, error, in_array, get_full_version, abort, die
# Overwritten: cleanup
# Ignored: stat_busy, stat_done,
#          setup_workdir, trap_abort, trap_exit,
#          lock, slock, lock_close
#          pkgver_equal, find_cached_package, check_root
. "$(librelib common)"

script_lock() {
	local LOCKDIR="$TMPDIR/.scriptlock.${0##*/}"
	if ! mkdir "$LOCKDIR" >/dev/null 2>&1 ; then
		local _owner="$(/usr/bin/stat -c %U "$LOCKDIR")"
		error "Script %s is already locked by %s." "${0##*/}" "$_owner"
		exit 1
	else
		set_umask
		return 0
	fi
}

script_unlock() {
	local LOCKDIR="$TMPDIR/.scriptlock.${0##*/}"
	if [[ ! -d $LOCKDIR ]]; then
		warning "Script %s was not locked!" "${0##*/}"
		restore_umask
		return 1
	else
		rmdir "$LOCKDIR"
		restore_umask
		return 0
	fi
}

cleanup() {
	local l
	local repo
	local arch

	trap - EXIT INT QUIT TERM
	for l in "${LOCKS[@]}"; do
		repo=${l%.*}
		arch=${l#*.}
		if [[ -d $TMPDIR/.repolock.$repo.$arch ]]; then
			msg "Removing left over lock from [%s] (%s)" "$repo" "$arch"
			repo_unlock "$repo" "$arch"
		fi
	done
	if [[ -d $TMPDIR/.scriptlock.${0##*/} ]]; then
		msg "Removing left over lock from %s" "${0##*/}"
		script_unlock
	fi
	rm -rf "$WORKDIR"

	if (( REPO_MODIFIED )); then
		date +%s > "${FTP_BASE}/lastupdate"
	fi

	[[ -n $1 ]] && exit "$1"
}

trap abort INT QUIT TERM HUP
trap cleanup EXIT


#repo_lock <repo-name> <arch> [timeout]
repo_lock () {
	local LOCKDIR="$TMPDIR/.repolock.$1.$2"
	local DBLOCKFILE="${FTP_BASE}/${1}/os/${2}/${1}${DBEXT}.lck"
	local _count
	local _trial
	local _timeout
	local _lockblock
	local _owner

	# This is the lock file used by repo-add and repo-remove
	if [[ -f ${DBLOCKFILE} ]]; then
		error "Repo [%s] (%s) is already locked by repo-{add,remove} process %s" "$1" "$2" "$(cat "$DBLOCKFILE")"
		return 1
	fi

	if (( $# == 2 )); then
		_lockblock=true
		_trial=0
	elif (( $# == 3 )); then
		_lockblock=false
		_timeout=$3
		let _trial=$_timeout/$LOCK_DELAY
	fi

	_count=0
	while (( _count <= _trial )) || [[ $_lockblock = true ]]; do
		if ! mkdir "$LOCKDIR" >/dev/null 2>&1 ; then
			_owner="$(/usr/bin/stat -c %U "$LOCKDIR")"
			warning "Repo [%s] (%s) is already locked by %s." "$1" "$2" "$_owner"
			msg2 "Retrying in %s seconds..." "$LOCK_DELAY"
		else
			LOCKS+=("$1.$2")
			set_umask
			return 0
		fi
		sleep "$LOCK_DELAY"
		let _count=$_count+1
	done

	error "Repo [%s] (%s) is already locked by %s. Giving up!" "$1" "$2" "$_owner"
	return 1
}

repo_unlock () { #repo_unlock <repo-name> <arch>
	local LOCKDIR="$TMPDIR/.repolock.$1.$2"
	if [[ ! -d $LOCKDIR ]]; then
		warning "Repo lock [%s] (%s) was not locked!" "$1" "$2"
		restore_umask
		return 1
	else
		rmdir "$LOCKDIR"
		restore_umask
		return 0
	fi
}

# usage: _grep_pkginfo pkgfile pattern
_grep_pkginfo() {
	local _ret

	_ret="$(/usr/bin/bsdtar -xOqf "$1" .PKGINFO | grep "^${2} = " | tail -1)"
	echo "${_ret#${2} = }"
}

# usage: _grep_buildinfo pkgfile pattern
_grep_buildinfo() {
	local _ret

	_ret="$(/usr/bin/bsdtar -xOqf "$1" .BUILDINFO | grep "^${2} = " | tail -1)"
	echo "${_ret#${2} = }"
}

# Get the package base or name as fallback
getpkgbase() {
	local _base

	_base="$(_grep_pkginfo "$1" "pkgbase")"
	if [[ -z $_base ]]; then
		getpkgname "$1"
	else
		echo "$_base"
	fi
}

issplitpkg() {
	local _base

	_base="$(_grep_pkginfo "$1" "pkgbase")"
	if [[ -z $_base ]]; then
		return 1
	else
		return 0
	fi
}

# Get the package name
getpkgname() {
	local _name

	_name="$(_grep_pkginfo "$1" "pkgname")"
	if [[ -z $_name ]]; then
		error "Package '%s' has no pkgname in the PKGINFO. Fail!" "$1"
		exit 1
	fi

	echo "$_name"
}

# Get the pkgver-pkgrel of this package
getpkgver() {
	local _ver

	_ver="$(_grep_pkginfo "$1" "pkgver")"
	if [[ -z $_ver ]]; then
		error "Package '%s' has no pkgver in the PKGINFO. Fail!" "$1"
		exit 1
	fi

	echo "$_ver"
}

getpkgarch() {
	local _ver

	_ver="$(_grep_pkginfo "$1" "arch")"
	if [[ -z $_ver ]]; then
		error "Package '%s' has no arch in the PKGINFO. Fail!" "$1"
		exit 1
	fi

	echo "$_ver"
}

check_packager() {
	local _packager

	_packager=$(_grep_pkginfo "$1" "packager")
	[[ -n $_packager && $_packager != 'Unknown Packager' ]]
}

check_buildinfo() {
	/usr/bin/bsdtar -tf "$1" .BUILDINFO >/dev/null 2>&1
}

check_builddir() {
	local _builddir

	_builddir=$(_grep_buildinfo "$1" "builddir")
	[[ -n $_builddir && $_builddir = '/build' ]]
}

getpkgfile() {
	if  (( $# != 1 )); then
		error 'No canonical package found!'
		exit 1
	elif [[ ! -f ${1} ]]; then
		error "Package %s not found!" "$1"
		exit 1
	elif [[ ! -f ${1}.sig ]]; then
		error "Package signature %s not found!" "$1.sig"
		exit 1
	fi

	echo "${1}"
}

getpkgfiles() {
	local f files
	if ! printf '%s\n' "${@%\.*}" | awk 'a[$0]++{exit 1}'; then
		error 'Duplicate packages found!'
		exit 1
	fi

	for f in "$@"; do
		files+=("$(getpkgfile "$f")") || exit 1
	done

	echo "${files[@]}"
}

check_pkgfile() {
	local pkgfile=$1

	local pkgname="$(getpkgname "${pkgfile}")" || return 1
	local pkgver="$(getpkgver "${pkgfile}")" || return 1
	local pkgarch="$(getpkgarch "${pkgfile}")" || return 1

	in_array "${pkgarch}" "${ARCHES[@]}" 'any' || return 1

	[[ ${pkgfile##*/} = "${pkgname}-${pkgver}-${pkgarch}"* ]]
}

check_pkgvcs() {
	local pkgfile="${1}"
	local _pkgbase="$(getpkgbase "${pkgfile}")" || return 1
	local _pkgname="$(getpkgname "${pkgfile}")" || return 1
	local _pkgver="$(getpkgver "${pkgfile}")" || return 1
	local _pkgarch="$(getpkgarch "${pkgfile}")" || return 1
	local repo="${2}"

	in_array "${repo}" "${PKGREPOS[@]}" || return 1

	if [[ ! -f ${WORKDIR}/pkgbuilds/${repo}-${_pkgarch}/${_pkgbase}/PKGBUILD ]]; then
		if ! type vcs_export &>/dev/null; then
			return 0
		fi
		mkdir -p "${WORKDIR}/pkgbuilds/${repo}-${_pkgarch}"
		vcs_export "$repo" "$_pkgarch" "$_pkgbase" \
			"${WORKDIR}/pkgbuilds/${repo}-${_pkgarch}/${_pkgbase}" >/dev/null || return 1
	fi

	local vcsver="$(. "${WORKDIR}/pkgbuilds/${repo}-${_pkgarch}/${_pkgbase}/PKGBUILD"; get_full_version)"
	[[ "${vcsver}" = "${_pkgver}" ]] || return 1

	local vcsnames=($(. "${WORKDIR}/pkgbuilds/${repo}-${_pkgarch}/${_pkgbase}/PKGBUILD"; echo "${pkgname[@]}"))
	in_array "${_pkgname}" "${vcsnames[@]}" || return 1

	return 0
}

check_splitpkgs() {
	local repo="${1}"
	shift
	local pkgfiles=("${@}")
	local pkgfile
	local pkgdir
	local vcsname

	mkdir -p "${WORKDIR}/check_splitpkgs/"
	pushd "${WORKDIR}/check_splitpkgs" >/dev/null

	for pkgfile in "${pkgfiles[@]}"; do
		issplitpkg "${pkgfile}" || continue
		local _pkgbase="$(getpkgbase "${pkgfile}")"
		local _pkgname="$(getpkgname "${pkgfile}")"
		local _pkgarch="$(getpkgarch "${pkgfile}")"
		mkdir -p "${repo}/${_pkgarch}/${_pkgbase}"
		echo "${_pkgname}" >> "${repo}/${_pkgarch}/${_pkgbase}/staging"

		if [[ ! -f ${WORKDIR}/pkgbuilds/${repo}-${_pkgarch}/${_pkgbase}/PKGBUILD ]]; then
			if ! type vcs_export &>/dev/null; then
				popd >/dev/null
				return 0
			fi
			mkdir -p "${WORKDIR}/pkgbuilds/${repo}-${_pkgarch}"
			vcs_export "$repo" "$_pkgarch" "$_pkgbase" \
				"${WORKDIR}/pkgbuilds/${repo}-${_pkgarch}/${_pkgbase}" >/dev/null || return 1
		fi

		local vcsnames=($(. "${WORKDIR}/pkgbuilds/${repo}-${_pkgarch}/${_pkgbase}/PKGBUILD"; echo "${pkgname[@]}"))
		printf '%s\n' "${vcsnames[@]}" >> "${repo}/${_pkgarch}/${_pkgbase}/vcs"
	done
	popd >/dev/null

	for pkgdir in "${WORKDIR}/check_splitpkgs/${repo}"/*/*; do
		[[ ! -d ${pkgdir} ]] && continue
		sort -u "${pkgdir}/staging" -o "${pkgdir}/staging"
		sort -u "${pkgdir}/vcs" -o "${pkgdir}/vcs"
		if [[ ! -z "$(comm -13 "${pkgdir}/staging" "${pkgdir}/vcs")" ]]; then
			return 1
		fi
	done

	return 0
}

check_pkgrepos() {
	local pkgfile=$1

	local pkgname="$(getpkgname "${pkgfile}")" || return 1
	local pkgver="$(getpkgver "${pkgfile}")" || return 1
	local pkgarch="$(getpkgarch "${pkgfile}")" || return 1

	is_globfile "${FTP_BASE}/${PKGPOOL}/${pkgname}-${pkgver}-${pkgarch}"${PKGEXTS} && return 1
	is_globfile "${FTP_BASE}/${PKGPOOL}/${pkgname}-${pkgver}-${pkgarch}"${PKGEXTS}.sig && return 1
	[[ -f ${FTP_BASE}/${PKGPOOL}/${pkgfile##*/} ]] && return 1
	[[ -f ${FTP_BASE}/${PKGPOOL}/${pkgfile##*/}.sig ]] && return 1

	return 0
}

#usage: chk_license ${license[@]}"
chk_license() {
	local l
	for l in "${@}"; do
		in_array "${l}" "${ALLOWED_LICENSES[@]}" && return 0
	done

	return 1
}

check_repo_permission() {
	local repo=$1

	(( ${#PKGREPOS[@]} == 0 )) && return 1
	[[ -z "${PKGPOOL}" ]] && return 1

	in_array "${repo}" "${PKGREPOS[@]}" || return 1

	[[ -w $FTP_BASE/${PKGPOOL} ]] || return 1

	local arch
	for arch in "${ARCHES[@]}"; do
		local dir="${FTP_BASE}/${repo}/os/${arch}/"
		[[ -w ${dir} ]] || return 1
		[[ -f ${dir}${repo}${DBEXT} && ! -w ${dir}${repo}${DBEXT} ]] && return 1
		[[ -f ${dir}${repo}${FILESEXT} && ! -w ${dir}${repo}${FILESEXT} ]] && return 1
	done

	return 0
}

set_repo_permission() {
	local repo=$1
	local arch=$2
	local dbfile="${FTP_BASE}/${repo}/os/${arch}/${repo}${DBEXT}"
	local filesfile="${FTP_BASE}/${repo}/os/${arch}/${repo}${FILESEXT}"

	if [[ -w ${dbfile} ]]; then
		local group=$(/usr/bin/stat --printf='%G' "$(dirname "${dbfile}")")
		chgrp "$group" "${dbfile}"    || error "Could not change group of %s to %s" "$dbfile"    "$group"
		chgrp "$group" "${filesfile}" || error "Could not change group of %s to %s" "$filesfile" "$group"
		chmod g+w "${dbfile}"    || error "Could not set write permission for group %s to %s" "$group" "$dbfile"
		chmod g+w "${filesfile}" || error "Could not set write permission for group %s to %s" "$group" "$filesfile"
	else
		error "You don't have permission to change %s" "$dbfile"
	fi
}

# usage: arch_expac repo arch expac_args...
arch_expac() {
	local repo=$1
	local arch=$2
	local args=("${@:3}")
	local dir="${WORKDIR}/expac-${repo}-${arch}"

	if ! [[ -d $dir ]]; then
		mkdir -p -- "${dir}/root"
		cat >"${dir}/pacman.conf" <<-EOT
		[options]
		RootDir = ${dir}/root
		DBPath = ${dir}/root
		Architecture = ${arch}

		[$repo]
		Server = file://${FTP_BASE}/\$repo/os/\$arch
		EOT
	fi

	fakeroot pacman --config="${dir}/pacman.conf" -Sy >/dev/null
	expac --config="${dir}/pacman.conf" --sync "${args[@]}"
}

# usage: arch_expac_pkgbase repo arch format pkgbase
# Like arch_expac, but 'target' is a pkgbase rather than a pkgname.
# Only accepts one target.
arch_expac_pkgbase() {
	local repo=$1
	local arch=$2
	local format=$3
	local pkgbase=$4
	# makepkg <5.1 didn't set pkgbase in .PKGINFO for non-split
	# packages when when pkgbase==pkgname; so until all packages
	# have been rebuilt with makepkg 5.1, we need to look at
	# pkgbase (%e) and pkgname (%n) when filtering based on
	# pkgbase.
	arch_expac "$repo" "$arch" "%e %n $format" |
		awk -F' ' -v pkgbase="$pkgbase" \
			'$1 == pkgbase || ($1 == "(null)" && $2 == pkgbase)' |
		cut -d' ' -f3-
}

arch_repo_modify() {
	local action=$1
	local repo=$2
	local arch=$3
	local pkgs=("${@:4}")
	local dbfile="${FTP_BASE}/${repo}/os/${arch}/${repo}${DBEXT}"

	if [[ ${action} = remove && ! -f ${dbfile} ]]; then
		error "No database found at '%s'" "$dbfile"
		return 1
	fi

	# package files for repo-add might be relative to repo dir
	pushd "${dbfile%/*}" >/dev/null
	/usr/bin/repo-${action} -q "${dbfile}" "${pkgs[@]}" \
		|| error '%s' "repo-${action} ${dbfile@Q} ${pkgs[*]@Q}"
	set_repo_permission "${repo}" "${arch}"
	popd >/dev/null

	REPO_MODIFIED=1
}

. "$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")/db-functions-${VCS}"
