#!/bin/sh
# $NetBSD: _NetBSD-pkgdb,v 1.2 2020/12/29 17:56:14 gdt Exp $
# Copyright The NetBSD Foundation 2020

# This script is for NetBSD only.

# This script is intended to help with the process of migration of
# PKG_DBDIR from /var/db/pkg to /usr/pkg/pkgdb on NetBSD.  It can:
#
#   "check" the system, changing nothing
#
#   "fix" the system, leaving PKG_DBDIR as is and setting config files
#   to point to it.  Thus, one can easily postpone migration.
#
#   "migrate" the system, moving the contents of PKG_DBDIR to the new
#   location, and then setting pointers.
#
# In all cases, the script attempts to check assumptions it makes, and
# avoid trouble if those assumptions are not true.

# Note that PKG_DBDIR can be set in multiple places:
#   /etc/pkg_install.conf (base tools)
#   /usr/pkg/etc/pkg_install.conf (pkg tools)
#   /etc/mk.conf (variable when buidling)
#   /usr/pkgsrc/mk/ (default value of variable)

# This script takes the position that the PKG_DBDIR should be
# explicitly configured in pkg_install.conf in all cases, even if this
# is provably not necessary.  The theory is that a bit of explicit
# config is much less work than mysterious debugging.  If you don't
# like this, you are welcome to undo it, only use "check", or do the
# migration (or not) by hand.

# This script is aimed at the common case where the database was in
# /var/db/pkg and is moving to /usr/pkg/pkgdb, and sources are in
# /usr/pkgsrc (or a place that /usr/pkgsrc is symlinked to).

# This script should work to fix/migrate even on systems with older
# pkgsrc, e.g. if one wanted to stay on 2020Q3 on a system where the
# base system tools were updated.  "Fix" will set variables so
# everything should work correctly.

# The script is designed to do incremental fixes, so if you migrate by
# hand but do not patch up the refcount directory, or set variables,
# it should (NB: This is UNTESTED!) do those steps when run.

# The script is intentionally written to be easy to understand (as
# long as one is comfortable with shell functions), rather than
# compact, avoid repeated code, or elegant.

# This script assumes:
#   NetBSD 8 or newer
#   pkgsrc after about 2020-12-20 (including 2020Q4)
#   pkgsrc PREFIX as /usr/pkg
#   old PKG_DBDIR as /var/db/pkg
#   new/future PKG_DBDIR as /usr/pkg
#   no symlinks in old or new PKG_DBDIR location
#   base pkg_install may be old, or may be after the 2020-12 commit/pullups
#   pkgsrc sources, if they exist, are in /usr/pkgsrc (perhaps a symlink)

# Set pathnames.  Do not expect to set these differently without
# significant testing and thought.
OLDDB=/var/db/pkg
NEWDB=/usr/pkg/pkgdb
CONF_BASE=/etc/pkg_install.conf
CONF_PKG=/usr/pkg/etc/pkg_install.conf
PKGSRC=/usr/pkgsrc

# Shell functions to print messages of varying severity.  (We choose
# to avoid fancy formatting entirely in this script.)
msg () {
    echo "$@" >&2
}

warn () {
    echo "WARNING: $@" >&2
}

fatal () {
    echo "FATAL: $@" >&2
    exit 1
}

usage () {
    msg "_NetBSD-pkgdb: Usage:"
    msg "    check     Check pkgdb status and explain it."
    msg "    fix       Point config files at pkgdb location (don't move it)."
    msg "    migrate   Check if migration can be done automatically, and if so do it."
    exit 1
}

# Count problems.  (Be sure not to use subshells when calling this.)
problems=0
problem_pp () {
    problems=`expr $problems + 1`

}
problem_count () {
    echo $problems
}

dir_exists () {
    if [ ! -h "$1" -a -d "$1" ]; then
	echo true
    else
	echo false
    fi
}

# require_match string bool1 bool2
require_match () {
    if [ "$2" != "$3" ]; then
	fatal "mismatch $1"
	problem_pp
    fi
}

# Read pkg_install.conf PKG_DBDIR.  One arg: config file
get_pkg_install_conf_DBDIR () {
    CONF=$1
    if [ "${CONF}" = "" ]; then
	fatal "missing argument"
	# dead code, left in case error is removed
	echo ""
    elif [ ! -f ${CONF} ]; then
	warn "${CONF} does not exist"
	echo ""
    else
	line=`egrep "^PKG_DBDIR.*=" ${CONF}`
	if [ "$line" = "" ]; then
	    warn "${CONF} does not set PKG_DBDIR"
	    echo ""
	else
	    value=`echo $line | awk -F= '{print $2}'`
	    echo $value
	fi
    fi
}

# Ensure prerequisites.
do_prereq () {
    if [ `uname` != "NetBSD" ];then
	fatal "OS is not NetBSD"
    fi

    # Check a subdir, in case /usr/pkg is a symlink.
    if [ ! -d /usr/pkg/bin ]; then
	fatal "/usr/pkg/bin is not a directory"
    fi
}

# Check and maybe fix or migrate, depending on if fix=true or
# migrate=true.   migrate=true and fix=false is not allowed.
do_steps () {
    ## STEP 0: Check if it is ok to operate at all.
    do_prereq

    ## STEP 1: Sanity check database and figure out where it is.
    # See if old or new exists, and check that they are in pairs.
    OLD_DB1=`dir_exists ${OLDDB}`
    OLD_DB2=`dir_exists ${OLDDB}.refcount`
    require_match "$OLDDB directory pair" $OLD_DB1 $OLD_DB2
    NEW_DB1=`dir_exists ${NEWDB}`
    NEW_DB2=`dir_exists ${NEWDB}.refcount`
    require_match "$NEWDB directory pair" $NEW_DB1 $NEW_DB2

    # Check that we have one PKG_DBDIR, not zero and not two
    if [ "$OLD_DB1" = false -a "$NEW_DB1" = false ]; then
	fatal "No PKG_DBDIR found"
	problem_pp
    fi
    if [ "$OLD_DB1" = true -a "$NEW_DB1" = true ]; then
	fatal "TWO copies of PKG_DBDIR found"
	problem_pp
    fi

    # Set a variable to the DBDIR we should be using, and the one we aren't.
    if [ "$OLD_DB1" = true ]; then
	PKG_DBDIR=$OLDDB
	PKG_DBDIR_NOT=$NEWDB
    elif [ "$NEW_DB1" = true ]; then
	PKG_DBDIR=$NEWDB
	PKG_DBDIR_NOT=$OLDDB
    else
	fatal "LOGIC ERROR IN SCRIPT 001"
	exit 1
    fi
    unset OLD_DB1
    unset OLD_DB2
    unset NEW_DB1
    unset OLD_DB2
    echo "Found PKG_DBDIR as $PKG_DBDIR."

    ## Step 2a: check migrate/fix prereqs
    if [ "$fix" = true ]; then
	warn "THIS SCRIPT IS NOT YET ADEQUATELY TESTED."
	warn "Hit ^C NOW if you do not have a good backup."
	warn "Proceeding in 10 seconds..."
	sleep 10

	if [ `id -u` != 0 ]; then
	    fatal "Not root: will not be able to fix or migrate"
	fi
    fi

    ## Step 2: migrate if requested
    if [ "$migrate" = true ]; then
	if [ "$fix" != true ]; then
	    fatal "LOGIC ERROR IN SCRIPT 002"
	fi
	
	if [ "$PKG_DBDIR" = "${NEWDB}" ]; then
	    warn "migrate requested when already done"
	    # This is not really an error.
	else
	    msg "Doing migration..."
	    msg "NB: refcount will have many fixups; this is normal."

	    # Actual migration is easy.
	    mv $OLDDB $NEWDB
	    mv $OLDDB.refcount $NEWDB.refcount

	    # Change our tracking variables so that later fixups do
	    # the right thing.
	    PKG_DBDIR=$NEWDB
	    PKG_DBDIR_NOT=$OLDDB

	    msg "Migration done, except for refcount fixups."
	fi
    fi

    ## Step 3: Check/fix refcount
    if [ "$fix" = true ]; then
	# Check all files, even though only some have paths.
	find ${PKG_DBDIR}.refcount -type f | while read file; do
	    if egrep $PKG_DBDIR_NOT $file > /dev/null; then
		echo WRONG PATH $file
		sed -i -e "s,${PKG_DBDIR_NOT},${PKG_DBDIR}," $file
		problem_pp
	    fi
	done
    fi
    
    ## Step 4: Check PKG_DBDIR variables and fix if requested.
    # Check that both pkg_install configs point to the actual PKG_DBDIR.
    conf_base_DBDIR=`get_pkg_install_conf_DBDIR $CONF_BASE`
    if [ "$conf_base_DBDIR" != "${PKG_DBDIR}" ]; then
	warn "$CONF_BASE does not set PKG_DBDIR to ${PKG_DBDIR}"
	if [ "$fix" = true ]; then
	    [ -f ${CONF_BASE} ] && cp -p ${CONF_BASE} ${CONF_BASE}.pre-migration
	    sed -i -e '/^PKG_DBDIR=/d' ${CONF_BASE}
	    echo "PKG_DBDIR=${PKG_DBDIR}" >> ${CONF_BASE}
	fi
	problem_pp
    fi
    conf_pkg_DBDIR=`get_pkg_install_conf_DBDIR $CONF_PKG`
    if [ "$conf_pkg_DBDIR" != "${PKG_DBDIR}" ]; then
	warn "$CONF_PKG does not set PKG_DBDIR to ${PKG_DBDIR}"
	if [ "$fix" = true ]; then
	    [ -f ${CONF_PKG} ] && cp -p ${CONF_PKG} ${CONF_PKG}.pre-migration
	    sed -i -e '/^PKG_DBDIR=/d' ${CONF_PKG}
	    echo "PKG_DBDIR=${PKG_DBDIR}" >> ${CONF_PKG}
	fi
	problem_pp
    fi

    # Check mk.conf via pkgsrc.  Really, check that the variable is
    # set when running in an example package, because it is relatively
    # normal to use .include and we decline to re-implement make.
    if [ -d ${PKGSRC}/mk ]; then
	# Avoid a subshell so problem_pp works.
	SAVE=`pwd`
	cd $PKGSRC
	PKGSRC_DBDIR=`cd devel/m4 && make show-var VARNAME=PKG_DBDIR`
	if [ "$PKGSRC_DBDIR" != "${PKG_DBDIR}" ]; then
	    warn "$PKG_DBDIR via pkgsrc/mk does not set PKG_DBDIR to ${PKG_DBDIR}"
	    # This may have failed for other reasons, but if so running again won't
	    # fix it.
	    if [ "$fix" = true ]; then
		sed -i -e '/^PKG_DBDIR=/d' /etc/mk.conf
		echo "PKG_DBDIR=${PKG_DBDIR}" >> /etc/mk.conf
	    fi
	    problem_pp
	fi
	cd $SAVE
	unset SAVE
    else
	msg "NB: Did not find ${PKGSRC} -- did not check mk.conf"
	msg "    If your system does not have sources, this is ok.  If it does, symlink"
	msg "    ${PKGSRC} to the sources and rerun the check."
    fi
}
    
main() {
    if [ "$#" != 1 ]; then
	usage
    fi
    
    case "$1" in
	check)
	    do_steps
	    ;;
	fix)
	    fix=true;
	    do_steps
	    ;;
	migrate)
	    fix=true
	    migrate=true;
	    do_steps
	    ;;
	*)
	    warn Unknown option $1
	    usage
	    ;;
    esac

    count=`problem_count`
    if [ "$count" = 0 ]; then
	msg "No problems found."
    else
	msg "TOTAL PROBLEMS FOUND: $count"
	msg "Re-run script until there are no problems!"
    fi
}

main "$@"