#!/bin/bash -e
#
#   pacman-db-upgrade - upgrade the local pacman db to a newer format
#
#   Copyright (c) 2010-2025 Pacman Development Team <pacman-dev@lists.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='@localedir@'

declare -r myver='@PACKAGE_VERSION@'

MAKEPKG_LIBRARY=${MAKEPKG_LIBRARY:-'@libmakepkgdir@'}

# Import libmakepkg
source "$MAKEPKG_LIBRARY"/util/message.sh
source "$MAKEPKG_LIBRARY"/util/parseopts.sh

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 "  -d, --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 -- "Copyright (c) 2010-2025 Pacman Development Team <pacman-dev@lists.archlinux.org>.\n"
	printf '\n'
	printf -- "$(gettext "\
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 "$@"
}

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 gettext &>/dev/null; then
	gettext() {
		echo "$@"
	}
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 ;;
	esac
	shift
done

conffile=${conffile:-@sysconfdir@/pacman.conf}
[[ -z $pacroot ]] && pacroot=$(pacman-conf --config="$conffile" rootdir)
[[ -z $dbroot ]] && dbroot=$(pacman-conf --config="$conffile" --rootdir="$pacroot" dbpath)

# check if messages are to be printed using color
if [[ -t 2 && $USE_COLOR != "n" ]]; then
	colorize
else
	unset ALL_OFF BOLD BLUE GREEN RED YELLOW
fi

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 "$(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"
