#!/usr/bin/env bash
#
#   pacman-db-upgrade - upgrade the local pacman db to a newer format
#   Generated from pacman-db-upgrade.sh.in; do not edit by hand.
#
#   Copyright (c) 2010-2016 Pacman Development Team <pacman-dev@archlinux.org>
#
#   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, see <http://www.gnu.org/licenses/>.
#

# Avoid creating world-unreadable files
umask 022

# gettext initialization
export TEXTDOMAIN='pacman-scripts'
export TEXTDOMAINDIR='/usr/share/locale'

declare -r myver='5.0.1'

plain() {
	(( QUIET )) && return
	local mesg=$1; shift
	printf "${BOLD}    ${mesg}${ALL_OFF}\n" "$@" >&1
}

msg() {
	(( QUIET )) && return
	local mesg=$1; shift
	printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&1
}

msg2() {
	(( QUIET )) && return
	local mesg=$1; shift
	printf "${BLUE}  ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&1
}

ask() {
	local mesg=$1; shift
	printf "${BLUE}::${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}" "$@" >&1
}

warning() {
	local mesg=$1; shift
	printf "${YELLOW}==> $(gettext "WARNING:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

error() {
	local mesg=$1; shift
	printf "${RED}==> $(gettext "ERROR:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}


# getopt-like parser
parseopts() {
	local opt= optarg= i= shortopts=$1
	local -a longopts=() unused_argv=()

	shift
	while [[ $1 && $1 != '--' ]]; do
		longopts+=("$1")
		shift
	done
	shift

	longoptmatch() {
		local o longmatch=()
		for o in "${longopts[@]}"; do
			if [[ ${o%:} = "$1" ]]; then
				longmatch=("$o")
				break
			fi
			[[ ${o%:} = "$1"* ]] && longmatch+=("$o")
		done

		case ${#longmatch[*]} in
			1)
				# success, override with opt and return arg req (0 == none, 1 == required)
				opt=${longmatch%:}
				if [[ $longmatch = *: ]]; then
					return 1
				else
					return 0
				fi ;;
			0)
				# fail, no match found
				return 255 ;;
			*)
				# fail, ambiguous match
				printf "pacman-db-upgrade: $(gettext "option '%s' is ambiguous; possibilities:")" "--$1"
				printf " '%s'" "${longmatch[@]%:}"
				printf '\n'
				return 254 ;;
		esac >&2
	}

	while (( $# )); do
		case $1 in
			--) # explicit end of options
				shift
				break
				;;
			-[!-]*) # short option
				for (( i = 1; i < ${#1}; i++ )); do
					opt=${1:i:1}

					# option doesn't exist
					if [[ $shortopts != *$opt* ]]; then
						printf "pacman-db-upgrade: $(gettext "invalid option") -- '%s'\n" "$opt" >&2
						OPTRET=(--)
						return 1
					fi

					OPTRET+=("-$opt")
					# option requires optarg
					if [[ $shortopts = *$opt:* ]]; then
						# if we're not at the end of the option chunk, the rest is the optarg
						if (( i < ${#1} - 1 )); then
							OPTRET+=("${1:i+1}")
							break
						# if we're at the end, grab the the next positional, if it exists
						elif (( i == ${#1} - 1 )) && [[ $2 ]]; then
							OPTRET+=("$2")
							shift
							break
						# parse failure
						else
							printf "pacman-db-upgrade: $(gettext "option requires an argument") -- '%s'\n" "$opt" >&2
							OPTRET=(--)
							return 1
						fi
					fi
				done
				;;
			--?*=*|--?*) # long option
				IFS='=' read -r opt optarg <<< "${1#--}"
				longoptmatch "$opt"
				case $? in
					0)
						# parse failure
						if [[ $optarg ]]; then
							printf "pacman-db-upgrade: $(gettext "option '%s' does not allow an argument")\n" "--$opt" >&2
							OPTRET=(--)
							return 1
						# --longopt
						else
							OPTRET+=("--$opt")
						fi
						;;
					1)
						# --longopt=optarg
						if [[ $optarg ]]; then
							OPTRET+=("--$opt" "$optarg")
						# --longopt optarg
						elif [[ $2 ]]; then
							OPTRET+=("--$opt" "$2" )
							shift
						# parse failure
						else
							printf "pacman-db-upgrade: $(gettext "option '%s' requires an argument")\n" "--$opt" >&2
							OPTRET=(--)
							return 1
						fi
						;;
					254)
						# ambiguous option -- error was reported for us by longoptmatch()
						OPTRET=(--)
						return 1
						;;
					255)
						# parse failure
						printf "pacman-db-upgrade: $(gettext "invalid option") '--%s'\n" "$opt" >&2
						OPTRET=(--)
						return 1
						;;
				esac
				;;
			*) # non-option arg encountered, add it as a parameter
				unused_argv+=("$1")
				;;
		esac
		shift
	done

	# add end-of-opt terminator and any leftover positional parameters
	OPTRET+=('--' "${unused_argv[@]}" "$@")
	unset longoptmatch

	return 0
}


usage() {
	printf "pacman-db-upgrade (pacman) %s\n" "${myver}"
	echo
	printf -- "$(gettext "Upgrade the local pacman database to a newer format")\n"
	echo
	printf -- "$(gettext "Usage: %s [options]")\n" "$0"
	echo
	printf -- "$(gettext "options:")\n"
	printf -- "$(gettext "  -b, --dbpath <path>  set an alternate database location")\n"
	printf -- "$(gettext "  -h, --help           show this help message and exit")\n"
	printf -- "$(gettext "  -r, --root <path>    set an alternate installation root")\n"
	printf -- "$(gettext "  -V, --version        show version information and exit")\n"
	printf -- "$(gettext "  --config <path>      set an alternate configuration file")\n"
	printf -- "$(gettext "  --nocolor            disable colorized output messages")\n"
	echo
}

version() {
	printf "pacman-db-upgrade (pacman) %s\n" "$myver"
	printf -- "$(gettext "\
Copyright (c) 2010-2016 Pacman Development Team <pacman-dev@archlinux.org>.\n\
This is free software; see the source for copying conditions.\n\
There is NO WARRANTY, to the extent permitted by law.\n")"
}

die() {
	error "$@"
	exit 1
}

die_r() {
	rm -f "$lockfile"
	die "$@"
}

get_opt_from_config() {
	local keyname="$1" conffile="$2"
	local key value

	while IFS=$'= \t' read -r key value _; do
		if [[ $key = $keyname ]]; then
			echo "$value"
			return
		fi
	done <"$conffile"
}

resolve_dir() {
	local d="$(cd "$1"; pwd -P)"
	[[ $d == */ ]] || d+=/
	printf "%s" "$d"
}

# PROGRAM START

# determine whether we have gettext; make it a no-op if we do not
if ! type -p gettext >/dev/null; then
	gettext() {
		printf "%s\n" "$@"
	}
else
	gettext() {
		/usr/bin/gettext "$@"
	}
fi

USE_COLOR='y'

OPT_SHORT="d:hr:V"
OPT_LONG=('config:' 'dbpath:' 'help' 'nocolor' 'root:' 'version')
if ! parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@"; then
	exit 1 # E_INVALID_OPTION
fi
set -- "${OPTRET[@]}"
unset OPT_SHORT OPT_LONG OPTRET

while true; do
	case "$1" in
		--config)     shift; conffile="$1" ;;
		-d|--dbpath)  shift; dbroot="$1" ;;
		-r|--root)    shift; pacroot="$1" ;;
		-h|--help)    usage; exit 0 ;;
		--nocolor)    USE_COLOR='n' ;;
		-V|--version) version; exit 0 ;;
		-- )          shift; break 2 ;;
	esac
	shift
done

conffile=${conffile:-/etc/pacman.conf}
[[ -z $pacroot ]] && pacroot="$(get_opt_from_config "RootDir" "$conffile")"
[[ -z $dbroot ]] && dbroot="$(get_opt_from_config "DBPath" "$conffile")"

[[ -z $dbroot && -n $pacroot ]] && dbroot="$pacroot//var/lib/pacman"

[[ -z $pacroot ]] && pacroot="/"
[[ -z $dbroot ]] && dbroot="/var/lib/pacman/"

# check if messages are to be printed using color
unset ALL_OFF BOLD BLUE GREEN RED YELLOW
if [[ -t 2 && ! $USE_COLOR = "n" ]]; then
	# prefer terminal safe colored and bold text when tput is supported
	if tput setaf 0 &>/dev/null; then
		ALL_OFF="$(tput sgr0)"
		BOLD="$(tput bold)"
		BLUE="${BOLD}$(tput setaf 4)"
		GREEN="${BOLD}$(tput setaf 2)"
		RED="${BOLD}$(tput setaf 1)"
		YELLOW="${BOLD}$(tput setaf 3)"
	else
		ALL_OFF="\e[1;0m"
		BOLD="\e[1;1m"
		BLUE="${BOLD}\e[1;34m"
		GREEN="${BOLD}\e[1;32m"
		RED="${BOLD}\e[1;31m"
		YELLOW="${BOLD}\e[1;33m"
	fi
fi
readonly ALL_OFF BOLD BLUE GREEN RED YELLOW


if [[ ! -d $dbroot ]]; then
	die "$(gettext "%s does not exist or is not a directory.")" "$dbroot"
fi

if [[ ! -d $dbroot/local ]]; then
	die "$(gettext "%s is not a pacman database directory.")" "$dbroot"
fi

if [[ ! -w $dbroot ]]; then
	die "$(gettext "You must have correct permissions to upgrade the database.")"
fi

# strip any trailing slash from our dbroot
dbroot="${dbroot%/}"
# form the path to our lockfile location
lockfile="${dbroot}/db.lck"

# make sure pacman isn't running
if [[ -f $lockfile ]]; then
	die "$(/bin/gettext "Pacman lock file was found. Cannot run while pacman is running.")"
fi
# do not let pacman run while we do this
touch "$lockfile"

if [[ -f "${dbroot}"/local/ALPM_DB_VERSION ]]; then
	db_version=$(cat "${dbroot}"/local/ALPM_DB_VERSION)
fi

if [[ -z "$db_version" ]]; then
	# pacman-3.4 to 3.5 upgrade - merge depends into desc
	if [[ $(find "$dbroot"/local -name depends) ]]; then
		msg "$(gettext "Pre-3.5 database format detected - upgrading...")"
		for i in "$dbroot"/local/*; do
			if [[ -f "$i"/depends ]]; then
				cat "$i"/depends >> "$i"/desc
				rm "$i"/depends
			fi
		done
		msg "$(gettext "Done.")"
	fi

	# pacman 4.1 to 4.2 upgrade - remove directory symlink support
	msg "$(gettext "Pre-4.2 database format detected - upgrading...")"

	dirlist=()

	unset GREP_OPTIONS
	while IFS= read -r dir; do
		dirlist+=("${pacroot}${dir%/}")
	done < <(grep -h '/$' "$dbroot"/local/*/files | sort -ru)

	mapfile -t dirlist < <(
		for dir in "${dirlist[@]}"; do
			[[ -L "$dir" ]] && echo "$dir"
		done)

	if [[ ${#dirlist[@]} != 0 ]]; then
		pacroot="$(resolve_dir "$pacroot")"

		for dir in "${dirlist[@]}"; do
			realdir="$(resolve_dir "$dir")"

			# verify realdir is inside root
			if [[ ${realdir:0:${#pacroot}} != $pacroot ]]; then
				warning "$(gettext "symlink '%s' points outside pacman root, manual repair required")" "$dir"
				continue
			fi

			# convert to an appropriate form for the replacement
			olddir="${dir:${#pacroot}}/"
			newdir="${realdir:${#pacroot}}"

			# construct the parents of the new directory
			parents=""
			parent="$(dirname "$newdir")"
			while [[ $parent != "." ]]; do
				parents+="$parent/\n"
				parent="$(dirname "$parent")"
			done

			for f in "$dbroot"/local/*/files; do
				awk -v "olddir=$olddir" -v "newdir=$newdir" -v "parents=$parents" '
					function print_path(path) {
						if (path != "" && !(path in seen)) {
							seen[path] = 1
							print path
						}
					}
					BEGIN {
						oldlen = length(olddir) + 1
						file = substr(newdir, 0, length(newdir) - 1)
					}
					{
						if ($0 == "") {
							# end of section, clear seen paths and print as-is
							for ( i in seen ) {
								delete seen[i]
							}
							print
						} else if ($0 == olddir) {
							# replace symlink with its target, including parents
							split(parents, paths, "\n")
							for (i in paths) {
								print_path(paths[i])
							}
							print_path(newdir)
						} else if ($0 == file) {
							# newdir already existed as a file, skip it
						} else if (index($0, olddir) == 1) {
							# update paths that were under olddir
							print_path(newdir substr($0, oldlen))
						} else {
							# print everything else as-is
							print_path($0)
						}
					}' "$f" > "$f.tmp"
				mv "$f.tmp" "$f"
			done
		done
	fi
fi

echo "9" > "$dbroot"/local/ALPM_DB_VERSION

# remove the lock file
rm -f "$lockfile"

# vim: set noet:
