#!/bin/sh
#
# $NetBSD: postinstall.in,v 1.71 2024/10/27 19:58:26 spz Exp $
#
# Copyright (c) 2002-2022 The NetBSD Foundation, Inc.
# All rights reserved.
#
# This code is derived from software contributed to The NetBSD Foundation
# by Luke Mewburn.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# postinstall
#	Check for or fix configuration changes that occur
#	over time as NetBSD evolves.
#

#
# NOTE: Be sure to use ${DEST_DIR} prefix before all real file operations.
#

#
# checks to add:
#	- sysctl(8) renames (net.inet6.ip6.bindv6only -> net.inet6.ip6.v6only)
#	- de* -> tlp* migration (/etc/ifconfig.de*, $ifconfig_de*, ...) ?
#	- support quiet/verbose mode ?
#	- differentiate between failures caused by missing source
#	  and real failures
#	- install moduli into usr/share/examples/ssh and use from there?
#	- differentiate between "needs fix" versus "can't fix" issues
#

# This script is executed as part of a cross build.  Allow the build
# environment to override the locations of some tools.
: ${AWK:=awk}
: ${DB:=db}
: ${GREP:=grep}
: ${HOST_SH:=sh}
: ${MAKE:=make}
: ${PWD_MKDB:=/usr/sbin/pwd_mkdb}
: ${SED:=sed}
: ${SORT:=sort}
: ${STAT:=stat}
: ${RM:=rm}

#
#	helper functions
#

err()
{
	local exitval=$1
	shift
	echo 1>&2 "${PROGNAME}: $*"
	if [ -n "${SCRATCHDIR}" ]; then
	    ${RM} -rf "${SCRATCHDIR}"
	fi
	exit ${exitval}
}

warn()
{
	echo 1>&2 "${PROGNAME}: $*"
}

msg()
{
	echo "	$*"
}

mkdtemp()
{
	# Make sure we don't loop forever if mkdir will always fail.
	[ -d /tmp ] || err 2 /tmp is not a directory
	[ -w /tmp ] || err 2 /tmp is not writable

	local base="/tmp/_postinstall.$$"
	local serial=0
	local dir

	while true; do
		dir="${base}.${serial}"
		mkdir -m 0700 "${dir}" && break
		serial=$((${serial} + 1))
	done
	echo "${dir}"
}

# Quote args to make them safe in the shell.
# Usage: quotedlist="$(shell_quote args...)"
#
# After building up a quoted list, use it by evaling it inside
# double quotes, like this:
#    eval "set -- $quotedlist"
# or like this:
#    eval "\$command $quotedlist \$filename"
#
shell_quote()
{(
	local result=''
	local arg qarg
	LC_COLLATE=C ; export LC_COLLATE # so [a-zA-Z0-9] works in ASCII
	for arg in "$@" ; do
		case "${arg}" in
		'')
			qarg="''"
			;;
		*[!-./a-zA-Z0-9]*)
			# Convert each embedded ' to '\'',
			# then insert ' at the beginning of the first line,
			# and append ' at the end of the last line.
			# Finally, elide unnecessary '' pairs at the
			# beginning and end of the result and as part of
			# '\'''\'' sequences that result from multiple
			# adjacent quotes in he input.
			qarg="$(printf "%s\n" "$arg" | \
			    ${SED} -e "s/'/'\\\\''/g" \
				-e "1s/^/'/" -e "\$s/\$/'/" \
				-e "1s/^''//" -e "\$s/''\$//" \
				-e "s/'''/'/g"
				)"
			;;
		*)
			# Arg is not the empty string, and does not contain
			# any unsafe characters.  Leave it unchanged for
			# readability.
			qarg="${arg}"
			;;
		esac
		result="${result}${result:+ }${qarg}"
	done
	printf "%s\n" "$result"
)}

# Convert arg $1 to a basic regular expression (as in sed)
# that will match the arg.  This works by inserting backslashes
# before characters that are special in basic regular expressions.
# It also inserts backslashes before the extra characters specified
# in $2 (which defaults to "/,").
# XXX: Does not handle embedded newlines.
# Usage: regex="$(bre_quote "${string}")"
bre_quote()
{
	local arg="$1"
	local extra="${2-/,}"
	printf "%s\n" "${arg}" | ${SED} -e 's/[][^$.*\\'"${extra}"']/\\&/g'
}

# unprefix dir
#	Remove any dir prefix from a list of paths on stdin,
#	and write the result to stdout.  Useful for converting
#	from ${DEST_DIR}/path to /path.
#
unprefix()
{
	[ $# -eq 1 ] || err 3 "USAGE: unprefix dir"
	local prefix="${1%/}"
	prefix="$(bre_quote "${prefix}")"

	${SED} -e "s,^${prefix}/,/,"
}

# additem item description
#	Add item to list of supported items to check/fix,
#	which are checked/fixed by default if no item is requested by user.
#
additem()
{
	[ $# -eq 2 ] || err 3 "USAGE: additem item description"
	defaultitems="${defaultitems}${defaultitems:+ }$1"
	eval desc_$1=\"\$2\"
}

# adddisableditem item description
#	Add item to list of supported items to check/fix,
#	but execute the item only if the user asks for it explicitly.
#
adddisableditem()
{
	[ $# -eq 2 ] || err 3 "USAGE: adddisableditem item description"
	otheritems="${otheritems}${otheritems:+ }$1"
	eval desc_$1=\"\$2\"
}

# checkdir op dir mode
#	Ensure dir exists, and if not, create it with the appropriate mode.
#	Returns 0 if ok, 1 otherwise.
#
check_dir()
{
	[ $# -eq 3 ] || err 3 "USAGE: check_dir op dir mode"
	local op="$1"
	local dir="$2"
	local mode="$3"
	[ -d "${dir}" ] && return 0
	if [ "${op}" = "check" ]; then
		msg "${dir} is not a directory"
		return 1
	elif ! mkdir -m "${mode}" "${dir}" ; then
		msg "Can't create missing ${dir}"
		return 1
	else
		msg "Missing ${dir} created"
	fi
	return 0
}

# check_ids op type file srcfile start id ...
#	Check if file of type "users" or "groups" contains the relevant IDs.
#	Use srcfile as a reference for the expected contents.
#	The specified "id" names should be given in numerical order,
#	with the first name corresponding to numerical value "start",
#	and with the special name "SKIP" being used to mark gaps in the
#	sequence.
#	Returns 0 if ok, 1 otherwise.
#
check_ids()
{
	[ $# -ge 6 ] || err 3 "USAGE: checks_ids op type file srcfile start id ..."
	local op="$1"
	local type="$2"
	local file="$3"
	local srcfile="$4"
	local start="$5"
	shift 5
	#local ids="$@"

	if [ ! -f "${file}" ]; then
		msg "${file} doesn't exist; can't check for missing ${type}"
		return 1
	fi
	if [ ! -r "${file}" ]; then
		msg "${file} is not readable; can't check for missing ${type}"
		return 1
	fi
	local notfixed=""
	if [ "${op}" = "fix" ]; then
		notfixed="${NOT_FIXED}"
	fi
	local missing="$(${AWK} -v start=$start -F: '
		BEGIN {
			for (x = 1; x < ARGC; x++) {
				if (ARGV[x] == "SKIP")
					continue;
				idlist[ARGV[x]]++;
				value[ARGV[x]] = start + x - 1;
			}
			ARGC=1
		}
		{
			found[$1]++
			number[$1] = $3
		}
		END {
			for (id in idlist) {
				if (!(id in found))
					printf("%s (missing)\n", id)
				else if (number[id] != value[id])
					printf("%s (%d != %d)\n", id,
					    number[id], value[id])
				start++;
			}
		}
	' "$@" < "${file}")"	|| return 1
	if [ -n "${missing}" ]; then
		msg "Error ${type}${notfixed}:" $(echo ${missing})
		msg "Use the following as a template:"
		set -- ${missing}
		while [ $# -gt 0 ]
		do
			${GREP} -E "^${1}:" ${srcfile}
			shift 2
		done | sort -t: -k3n
		msg "and adjust if necessary."
		return 1
	fi
	return 0
}

# populate_dir op onlynew src dst mode file ...
#	Perform op ("check" or "fix") on files in src/ against dst/
#	If op = "check" display missing or changed files, optionally with diffs.
#	If op != "check" copies any missing or changed files.
#	If onlynew evaluates to true, changed files are ignored.
#	Returns 0 if ok, 1 otherwise.
#
populate_dir()
{
	[ $# -ge 5 ] || err 3 "USAGE: populate_dir op onlynew src dst mode file ..."
	local op="$1"
	local onlynew="$2"
	local src="$3"
	local dst="$4"
	local mode="$5"
	shift 5
	#local files="$@"

	if [ ! -d "${src}" ]; then
		msg "${src} is not a directory; skipping check"
		return 1
	fi
	check_dir "${op}" "${dst}" 755 || return 1

	local cmpdir_rv=0
	local f fs fd error
	for f in "$@"; do
		fs="${src}/${f}"
		fd="${dst}/${f}"
		error=""
		if [ ! -f "${fd}" ]; then
			error="${fd} does not exist"
		elif ! cmp -s "${fs}" "${fd}" ; then
			if $onlynew; then	# leave existing ${fd} alone
				continue;
			fi
			error="${fs} != ${fd}"
		else
			continue
		fi
		if [ "${op}" = "check" ]; then
			msg "${error}"
			if [ -n "${DIFF_STYLE}" -a -f "${fd}" ]; then
				diff -${DIFF_STYLE} ${DIFF_OPT} "${fd}" "${fs}"
			fi
			cmpdir_rv=1
		elif ! ${RM} -f "${fd}" ||
		     ! cp -f "${fs}" "${fd}"; then
			msg "Can't copy ${fs} to ${fd}"
			cmpdir_rv=1
		elif ! chmod "${mode}" "${fd}"; then
			msg "Can't change mode of ${fd} to ${mode}"
			cmpdir_rv=1
		else
			msg "Copied ${fs} to ${fd}"
		fi
	done
	return ${cmpdir_rv}
}

# compare_dir op src dst mode file ...
#	Perform op ("check" or "fix") on files in src/ against dst/
#	If op = "check" display missing or changed files, optionally with diffs.
#	If op != "check" copies any missing or changed files.
#	Returns 0 if ok, 1 otherwise.
#
compare_dir()
{
	[ $# -ge 4 ] || err 3 "USAGE: compare_dir op src dst mode file ..."
	local op="$1"
	local src="$2"
	local dst="$3"
	local mode="$4"
	shift 4
	#local files="$@"

	populate_dir "$op" false "$src" "$dst" "$mode" "$@"
}

# move_file op src dst --
#	Check (op == "check") or move (op != "check") from src to dst.
#	Returns 0 if ok, 1 otherwise.
#
move_file()
{
	[ $# -eq 3 ] || err 3 "USAGE: move_file op src dst"
	local op="$1"
	local src="$2"
	local dst="$3"

	if [ -f "${src}" -a ! -f "${dst}" ]; then
		if [ "${op}" = "check" ]; then
			msg "Move ${src} to ${dst}"
			return 1
		fi
		if ! mv "${src}" "${dst}"; then
			msg "Can't move ${src} to ${dst}"
			return 1
		fi
		msg "Moved ${src} to ${dst}"
	fi
	return 0
}

# rcconf_is_set op name var [verbose] --
#	Load the rcconf for name, and check if obsolete rc.conf(5) variable
#	var is defined or not.
#	Returns 0 if defined (even to ""), otherwise 1.
#	If verbose != "", print an obsolete warning if the var is defined.
#
rcconf_is_set()
{
	[ $# -ge 3 ] || err 3 "USAGE: rcconf_is_set op name var [verbose]"
	local op="$1"
	local name="$2"
	local var="$3"
	local verbose="$4"
	local notfixed=""
	if [ "${op}" = "fix" ]; then
		notfixed="${NOT_FIXED}"
	fi
	(
		for f in \
		    "${DEST_DIR}/etc/rc.conf" \
		    "${DEST_DIR}/etc/rc.conf.d/${name}"; do
			[ -f "${f}" ] && . "${f}"
		done
		eval echo -n \"\${${var}}\" 1>&3
		if eval "[ -n \"\${${var}+SET}\" ]"; then
			if [ -n "${verbose}" ]; then
				msg \
    "Obsolete rc.conf(5) variable '\$${var}' found.${notfixed}"
			fi
			exit 0
		else
			exit 1
		fi
	)
}

# rcvar_is_enabled var
#	Check if rcvar is enabled
#
rcvar_is_enabled()
{
	[ $# -eq 1 ] || err 3 "USAGE: rcvar_is_enabled var"
	local var="$1"
	(
		[ -f "${DEST_DIR}/etc/rc.conf" ] && . "${DEST_DIR}/etc/rc.conf"
		eval val=\"\${${var}}\"
		case $val in
		#	"yes", "true", "on", or "1"
		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
			exit 0
			;;

		*)
			exit 1
			;;
		esac
	)
}

# find_file_in_dirlist() file message dir1 ... --
#	Find which directory file is in, and sets ${dir} to match.
#	Returns 0 if matched, otherwise 1 (and sets ${dir} to "").
#
#	Generally, check the directory for the "checking from source" case,
#	and then the directory for the "checking from extracted etc.tgz" case.
#
find_file_in_dirlist()
{
	[ $# -ge 3 ] || err 3 "USAGE: find_file_in_dirlist file msg dir1 ..."

	local file="$1" ; shift
	local msg="$1" ; shift
	local dir1st=	# first dir in list
	# returns dir
	for dir in "$@"; do
		: ${dir1st:="${dir}"}
		if [ -f "${dir}/${file}" ]; then
			if [ "${dir1st}" != "${dir}" ]; then
				msg \
    "(Checking for ${msg} from ${dir} instead of ${dir1st})"
			fi
			return 0
		fi
	done
	msg "Can't find source directory for ${msg}"
	return 1
}

# file_exists_exact path
#	Returns true if a file exists in the ${DEST_DIR} whose name
#	is exactly ${path}, interpreted in a case-sensitive way
#	even if the underlying file system is case-insensitive.
#
#	The path must begin with '/' or './', and is interpreted as
#	being relative to ${DEST_DIR}.
#
file_exists_exact()
{
	[ -n "$1" ] || err 3 "USAGE: file_exists_exact path"
	local path="${1#.}"
	[ -h "${DEST_DIR}${path}" ] || \
		[ -e "${DEST_DIR}${path}" ] || return 1
	while [ "${path}" != "/" -a "${path}" != "." ] ; do
		local dirname="$(dirname "${path}" 2>/dev/null)"
		local basename="$(basename "${path}" 2>/dev/null)"
		ls -fa "${DEST_DIR}${dirname}" 2> /dev/null \
			| ${GREP} -q -F -x "${basename}" \
			|| return 1
		path="${dirname}"
	done
	return 0
}

# obsolete_paths op
#	Obsolete the list of paths provided on stdin.
#	Each path should start with '/' or './', and
#	will be interpreted relative to ${DEST_DIR}.
#
obsolete_paths()
{
	[ -n "$1" ] || err 3 "USAGE: obsolete_paths fix|check"
	local op="$1"
	local failed=0
	local ofile cmd ftype

	while read ofile; do
		if ! ${file_exists_exact} "${ofile}"; then
			continue
		fi
		ofile="${DEST_DIR}${ofile#.}"
		cmd="${RM}"
		ftype="file"
		if [ -h "${ofile}" ]; then
			ftype="link"
		elif [ -d "${ofile}" ]; then
			ftype="directory"
			cmd="rmdir"
		elif [ ! -e "${ofile}" ]; then
			continue
		fi
		if [ "${op}" = "check" ]; then
			msg "Remove obsolete ${ftype} ${ofile}"
			failed=1
		elif ! eval "${cmd} \"\${ofile}\""; then
			msg "Can't remove obsolete ${ftype} ${ofile}"
			failed=1
		else
			msg "Removed obsolete ${ftype} ${ofile}"
		fi
	done
	return ${failed}
}

# obsolete_libs dir
#	Display the minor/teeny shared libraries in dir that are considered
#	to be obsolete.
#
#	The implementation supports removing obsolete major libraries
#	if the awk variable PurgeOldMajor is set, although there is no
#	way to enable that in the enclosing shell function as this time.
#
obsolete_libs()
{
	[ $# -eq 1 ] || err 3 "USAGE: obsolete_libs dir"
	local dir="$1"

	_obsolete_libs "${dir}"
	_obsolete_libs "/usr/libdata/debug/${dir}"
}

exclude()
{
	if [ -z "$*" ]; then
		cat
	else
		eval ${GREP} -v -E "'(^$(echo $* | sed -e 's/ /|^/'g))'"
	fi
}

#
# Find all the symlink targets of shared libraries and exclude them
# from consideration for removal.
#
# This protects "new old" libs after downgrading a system.  Say, you
# had a more recent system that comes with libfoo.so.1.2 and then
# downgraded to an older version that had libfoo.so.1.1.  You still
# have 1.2 that "wins" the competition for the "libfoo.so.1" stem and
# thus 1.1 is considered obsolete by the awk script in _obsolete_libs
# below.  But your libfoo.so.1 now points to 1.1 so deleting 1.1 will
# render your system broken.  exclude_libs is fed the output of
# potentially obsolete libs and absolves those (libfoo.so.1.1) that
# are a target of a symlink (libfoo.so.1 -> libfoo.so.1.1)
#
exclude_libs()
{
	local targets="$(find lib*.so.* -prune -type l -exec readlink '{}' + 2> /dev/null \
	    | ${SED} -e 's@.*/@@' | ${SORT} -u)"
	${GREP} -F -v -x "$targets"
}

_obsolete_libs()
{
	local dir="$1"

	(

	[ -e "${DEST_DIR}/${dir}" ] || return 0
	cd "${DEST_DIR}/${dir}" || err 2 "can't cd to ${DEST_DIR}/${dir}"

	# TODO: make this selectable with a command line option?
	local maybe_purge_major
	#maybe_purge_major='-v PurgeOldMajor=1'

	printf '%s\n' lib*.so.* | ${AWK} ${maybe_purge_major} '
#{

BEGIN {
	BASE_REGEX  = "^lib.*\\.so\\."
	MAJOR_REGEX = (BASE_REGEX "[0-9]+\\.")

	# in the usual case different major versions of the same
	# library are considered different stems and do not compete
	# with each other, we keep one of each, but you may request to
	# purge old majors, in which case all versions compete for the
	# single basename stem
	if (PurgeOldMajor)
		keepone = BASE_REGEX
	else
		keepone = MAJOR_REGEX
}

# major version symlink
PurgeOldMajor && /^lib.*\.so\.[0-9]+$/ {
	checklib(major, $0, BASE_REGEX)
}

# specific minor version of a library
/^lib.*\.so\.[0-9]+\.[0-9]+(\.[0-9]+)?(\.debug)?$/ {
	checklib(minor, $0, keepone)
}

function checklib(latest, libname, stem_regex) {
	if (! match(libname, stem_regex))
		return
	stem = substr(libname, RSTART, RLENGTH)
	vers = substr(libname, RLENGTH + 1)

	# first time we see this stem? just record the version
	if (! (stem in latest)) {
		latest[stem] = vers
		return
	}

	# split version suffixes into the list of numeric components
	oversc = split(latest[stem], overs, ".")
	nversc = split(vers,         nvers, ".")
	maxc = (oversc > nversc) ? oversc : nversc

	# is the new version "later" than the one we have seen?
	for (i = 1; i <= maxc; ++i) {
		cmp = (overs[i]+0) - (nvers[i]+0)

		if (cmp < 0) {
			# the one we have seen is older, so report it
			# as obsolete and update the latest seen
			# version to this new one
			print stem latest[stem]
			latest[stem] = vers
			return
		}
		else if (cmp > 0) {
			# the one we have just read is older than the
			# one we have seen previously, so report this
			# "new" one as obsolete
			print libname
			return
		}
	}
}

	# the ouput is further filtered by exclude_libs that protects
	# libraries that have symlinks pointing to them, which one
	# encounters when downgrading
#}'	\
	| exclude_libs \
	| ${SED} "s|^|${dir}/|"

	)
}

# obsolete_stand dir
#	Prints the names of all obsolete files and subdirs below the
#	provided dir.  dir should be something like /stand/${MACHINE}.
#	The input dir and all output paths are interpreted
#	relative to ${DEST_DIR}.
#
#	Assumes that the numerically largest subdir is current, and all
#	others are obsolete.
#
obsolete_stand()
{
	[ $# -eq 1 ] || err 3 "USAGE: obsolete_stand dir"
	local dir="$1"
	local subdir

	if ! [ -d "${DEST_DIR}${dir}" ]; then
		msg "${DEST_DIR}${dir} doesn't exist; can't check for obsolete files"
		return 1
	fi

	( cd "${DEST_DIR}${dir}" && ls -1d [0-9]*[0-9]/. ) \
	| ${GREP} -v '[^0-9./]' \
	| sort -t. -r -n -k1,1 -k2,2 -k3,3 \
	| tail -n +2 \
	| while read subdir ; do
		subdir="${subdir%/.}"
		find "${DEST_DIR}${dir}/${subdir}" -depth -print
	done \
	| unprefix "${DEST_DIR}"
}

# modify_file op srcfile scratchfile awkprog
#	Apply awkprog to srcfile sending output to scratchfile, and
#	if appropriate replace srcfile with scratchfile.
#
modify_file()
{
	[ $# -eq 4 ] || err 3 "USAGE: modify_file op file scratch awkprog"

	local op="$1"
	local file="$2"
	local scratch="$3"
	local prog="$4"
	local failed=0
	local line

	${AWK} "${prog}" < "${file}" > "${scratch}"
	if ! cmp -s "${file}" "${scratch}"; then
		diff "${file}" "${scratch}" > "${scratch}.diffs"
		if [ "${op}" = "check" ]; then
			msg "${file} needs the following changes:"
			mffailed=1
		elif ! ${RM} -f "${file}" ||
		     ! cp -f "${scratch}" "${file}"; then
			msg "${file} changes not applied:"
			mffailed=1
		else
			msg "${file} changes applied:"
		fi
		while read line; do
			msg "	${line}"
		done < "${scratch}.diffs"
	fi
	return ${failed}
}


# contents_owner op directory user group
#	Make sure directory and contents are owned (and group-owned)
#	as specified.
#
contents_owner()
{
	[ $# -eq 4 ] || err 3 "USAGE: contents_owner op dir user group"

	local op="$1"
	local dir="$2"
	local user="$3"
	local grp="$4"
	local files error

	if [ "${op}" = "check" ]; then
		files=$(find "${dir}" \( \( ! -user "${user}" \) -o \
		                \( ! -group "${grp}" \) \) )
		error=$?
		if [ ! -z "$files" ] || [ $error != 0 ]; then
			msg "${dir} and contents not all owned by" \
			    "${user}:${grp}"
			return 1
		else
			return 0
		fi
	elif [ "${op}" = "fix" ]; then
		find "${dir}" \( \( ! -user "${user}" \) -o \
		\( ! -group "${grp}" \) \) \
		-exec chown "${user}:${grp}" -- {} \;
	fi
}

# get_makevar var ...
#	Retrieve the value of a user-settable system make variable
get_makevar()
{
	local var value
	$SOURCEMODE || err 3 "get_makevar must be used in source mode"
	[ $# -eq 0 ] && err 3 "USAGE: get_makevar var ..."

	for var in "$@"; do
		value="$(echo '.include <bsd.own.mk>' | \
		    ${MAKE} -f - -V "\${${var}}")"

		eval ${var}=\"\${value}\"
	done
}

# detect_x11
#	Detect if X11 components should be analysed and set values of
#	relevant variables.
detect_x11()
{
	if $SOURCEMODE; then
		get_makevar MKX11 X11ROOTDIR X11SRCDIR
	else
		if [ -f "${SRC_DIR}/etc/mtree/set.xetc" ]; then
			MKX11=yes
			X11ROOTDIR=/this/value/isnt/used/yet
		else
			MKX11=no
			X11ROOTDIR=
		fi
		X11SRCDIR=/nonexistent/xsrc
	fi
}

#
#	find out where MAKEDEV lives, set MAKEDEV_DIR appropriately
#
find_makedev()
{
	if [ -e "${DEST_DIR}/dev/MAKEDEV" ]; then
		MAKEDEV_DIR="${DEST_DIR}/dev"
	elif [ -e "${DEST_DIR}/etc/MAKEDEV" ]; then
		MAKEDEV_DIR="${DEST_DIR}/etc"
	else
		MAKEDEV_DIR="${DEST_DIR}/dev"
	fi
}


#
#	items
#	-----
#
# NOTE: Keep these items sorted, except for obsolete* which are listed last.
#

#
#	atf
#

handle_atf_user()
{
	local op="$1"
	local conf="$2"
	local option="unprivileged-user"
	local old="_atf"
	local new="_tests"
	local failed=0

	local c=$(readlink -f "${conf}")
	if ${GREP} -q "[^#]*${option}[ \t]*=.*${old}" "${c}"
	then
		if [ "${op}" = "fix" ]; then
			${SED} -e "/[^#]*${option}[\ t]*=/s/${old}/${new}/" \
			    "${c}" >"${c}.new"
			failed=$(( ${failed} + $? ))
			mv "${c}.new" "${c}"
			failed=$(( ${failed} + $? ))
			msg "Set ${option}=${new} in ${c}"
		else
			msg "${option}=${old} in ${c} should be " \
			    "${option}=${new}"
			failed=1
		fi
	fi

	return ${failed}
}

additem atf "install missing atf configuration files and validate them"
do_atf()
{
	[ -n "$1" ] || err 3 "USAGE: do_atf fix|check"
	local conf="${DEST_DIR}/etc/atf/common.conf"
	local atfdir="${DEST_DIR}/etc/atf"
	local op="$1"
	local failed=0

	# Ensure atf configuration files are in place.
	if find_file_in_dirlist NetBSD.conf "NetBSD.conf" \
	    "${SRC_DIR}/external/bsd/atf/etc/atf" \
	    "${SRC_DIR}/etc/atf"; then
		    # ${dir} is set by find_file_in_dirlist()
		    populate_dir "${op}" true "${dir}" "${atfdir}" 644 \
		    NetBSD.conf common.conf || failed=1
	else
		failed=1
	fi
	if find_file_in_dirlist atf-run.hooks "atf-run.hooks" \
	    "${SRC_DIR}/external/bsd/atf/dist/tools/sample" \
	    "${SRC_DIR}/etc/atf"; then
		# ${dir} is set by find_file_in_dirlist()
		populate_dir "${op}" true "${dir}" "${atfdir}" 644 \
		    atf-run.hooks || failed=1
	else
		failed=1
	fi

	# Validate the _atf to _tests user/group renaming.
	if [ -f "${conf}" ]; then
		handle_atf_user "${op}" "${conf}" || failed=1
	else
		failed=1
	fi

	return ${failed}
}


#
#	autofsconfig
#

additem autofsconfig "automounter configuration files"
do_autofsconfig()
{
	[ -n "$1" ] || err 3 "USAGE: do_autofsconfig fix|check"
	local autofs_files="
include_ldap
include_nis
special_hosts
special_media
special_noauto
special_null
"
	local op="$1"
	local failed=0

	if [ "$op" = "fix" ]; then
		mkdir -p "${DEST_DIR}/etc/autofs"
	fi
	failed=$(( ${failed} + $? ))
	populate_dir "$op" true "${SRC_DIR}/etc" \
	    "${DEST_DIR}/etc" \
	    644 \
	    auto_master
	failed=$(( ${failed} + $? ))
	populate_dir "$op" true "${SRC_DIR}/etc/autofs" \
	    "${DEST_DIR}/etc/autofs" \
	    644 \
	    ${autofs_files}
	return ${failed}
}


#
#	blocklist
#

fixblock()
{
	local op="$1"
	local target="${DEST_DIR}$2"

	if [ ! -f "${target}" ]; then
		continue
	fi

	if ${GREP} '[bB]lacklist' "${target}" > /dev/null; then
		if [ "$1" = "check" ]; then
			msg "Fix old configuration file(s)."
			return 1
		else
			local p=$(${STAT} -f %Lp "${target}")
			chmod u+w "${target}" || return 1
			if [ "$2" = "/etc/npf.conf" ]; then
				${SED} -i -e 's/"blacklistd"/"blocklistd"/g' "${target}"
			else
				${SED} -i -e 's/\([bB]\)lacklist/\1locklist/g' "${target}"
			fi
			chmod "${p}" "${target}"
		fi
	fi
}

additem blocklist "rename old files to blocklist"
do_blocklist()
{
	[ -n "$1" ] || err 3 "USAGE: do_blocklist fix|check"
	local op="$1"
	local i old

	# if we are actually using blocklistd
	for i in /var/db/blacklistd.db /etc/blacklistd.conf; do
		old="${DEST_DIR}${i}"
		if [ ! -f "${old}" ]; then
			continue
		elif [ "$1" = "check" ]; then
			msg "Rename old file(s)."
			return 1
		fi
		local new=$(echo "${old}" | ${SED} s/blacklist/blocklist/)
		mv "${old}" "${new}" || return 1
	done

	for i in /etc/rc.conf /etc/npf.conf /etc/blocklistd.conf \
	    /etc/defaults/rc.conf; do
		fixblock "${op}" "${i}" || return 1
	done
}


#
#	bluetooth
#

additem bluetooth "Bluetooth configuration is up to date"
do_bluetooth()
{
	[ -n "$1" ] || err 3 "USAGE: do_bluetooth fix|check"
	local op="$1"
	local failed=0

	populate_dir "${op}" true \
		"${SRC_DIR}/etc/bluetooth" "${DEST_DIR}/etc/bluetooth" 644 \
		hosts protocols btattach.conf btdevctl.conf
	failed=$(( ${failed} + $? ))

	move_file "${op}" "${DEST_DIR}/var/db/btdev.xml" \
			"${DEST_DIR}/var/db/btdevctl.plist"
	failed=$(( ${failed} + $? ))

	local notfixed=""
	if [ "${op}" = "fix" ]; then
		notfixed="${NOT_FIXED}"
	fi
	for _v in btattach btconfig btdevctl; do
		if rcvar_is_enabled "${_v}"; then
			msg \
    "${_v} is obsolete in rc.conf(5)${notfixed}: use bluetooth=YES"
			failed=$(( ${failed} + 1 ))
		fi
	done

	return ${failed}
}


#
#	catpages
#

obsolete_catpages()
{
	local op="$1"
	local basedir="$2"
	local section="$3"
	local mandir="${basedir}/man${section}"
	local catdir="${basedir}/cat${section}"
	test -d "$mandir" || return 0
	test -d "$catdir" || return 0
	(cd "$mandir" && find . -type f) | {
	local failed=0
	while read manpage; do
		manpage="${manpage#./}"
		case "$manpage" in
		*.Z)
			catname="$catdir/${manpage%.*.Z}.0"
			;;
		*.gz)
			catname="$catdir/${manpage%.*.gz}.0"
			;;
		*)
			catname="$catdir/${manpage%.*}.0"
			;;
		esac
		test -e "$catname" -a "$catname" -ot "$mandir/$manpage" || continue
		if [ "${op}" = "fix" ]; then
			${RM} "$catname"
			failed=$(( ${failed} + $? ))
			msg "Removed obsolete cat page $catname"
		else
			msg "Obsolete cat page $catname"
			failed=1
		fi
	done
	exit $failed
	}
}

additem catpages "remove outdated cat pages"
do_catpages()
{
	local op="$1"
	local failed=0
	local manbase sec
	for manbase in /usr/share/man /usr/X11R6/man /usr/X11R7/man; do
		for sec in 1 2 3 4 5 6 7 8 9; do
			obsolete_catpages "$1" "${DEST_DIR}${manbase}" "${sec}"
			failed=$(( ${failed} + $? ))
			if [ "${op}" = "fix" ]; then
				rmdir "${DEST_DIR}${manbase}/cat${sec}"/* \
					2>/dev/null
				rmdir "${DEST_DIR}${manbase}/cat${sec}" \
					2>/dev/null
			fi
		done
	done
	return $failed
}


#
#	ddbonpanic
#

additem ddbonpanic "verify ddb.onpanic is configured in sysctl.conf"
do_ddbonpanic()
{
	[ -n "$1" ] || err 3 "USAGE: do_ddbonpanic fix|check"

	if ${GREP} -E '^#*[[:space:]]*ddb\.onpanic[[:space:]]*\??=[[:space:]]*[[:digit:]]+' \
		"${DEST_DIR}/etc/sysctl.conf" >/dev/null 2>&1
	then
		result=0
	else
		if [ "$1" = check ]; then
			msg \
    "The ddb.onpanic behaviour is not explicitly specified in /etc/sysctl.conf"
			result=1
		else
			echo >> "${DEST_DIR}/etc/sysctl.conf"
			${SED} < "${SRC_DIR}/etc/sysctl.conf" \
			   -e '/^ddb\.onpanic/q' | \
			       ${SED} -e '1,/^$/d' >> \
			    "${DEST_DIR}/etc/sysctl.conf"
			result=$?
		fi
	fi
	return ${result}
}


#
#	defaults
#

additem defaults "/etc/defaults/ being up to date"
do_defaults()
{
	[ -n "$1" ] || err 3 "USAGE: do_defaults fix|check"
	local op="$1"
	local failed=0
	local etcsets=$(getetcsets)

	local rc_exclude_scripts=""
	if $SOURCEMODE; then
		# For most architectures rc.conf(5) should be the same as the
		# one obtained from a source directory, except for the ones
		# that have an append file for it.
		local rc_conf_app="${SRC_DIR}/etc/etc.${MACHINE}/rc.conf.append"
		if [ -f "${rc_conf_app}" ]; then
			rc_exclude_scripts="rc.conf"

			# Generate and compare the correct rc.conf(5) file
			mkdir "${SCRATCHDIR}/defaults"

			cat "${SRC_DIR}/etc/defaults/rc.conf" "${rc_conf_app}" \
			    > "${SCRATCHDIR}/defaults/rc.conf"

			compare_dir "${op}" "${SCRATCHDIR}/defaults" \
			    "${DEST_DIR}/etc/defaults" \
			    444 \
			    "rc.conf"
			failed=$(( ${failed} + $? ))
		fi
	fi

	find_file_in_dirlist pf.boot.conf "pf.boot.conf" \
	    "${SRC_DIR}/usr.sbin/pf/etc/defaults" "${SRC_DIR}/etc/defaults" \
	    || return 1
	# ${dir} is set by find_file_in_dirlist()
	compare_dir "$op" "${dir}" "${DEST_DIR}/etc/defaults" 444 pf.boot.conf
	failed=$(( ${failed} + $? ))

	rc_exclude_scripts="${rc_exclude_scripts} pf.boot.conf"

	local rc_default_conf_files="$(select_set_files /etc/defaults/ \
	    "/etc/defaults/\([^[:space:]]*\.conf\)" ${etcsets} | \
	    exclude ${rc_exclude_scripts})"
	compare_dir "$op" "${SRC_DIR}/etc/defaults" "${DEST_DIR}/etc/defaults" \
		444 \
		${rc_default_conf_files}
	failed=$(( ${failed} + $? ))


	return ${failed}
}


#
#	dhcpcd
#

additem dhcpcd "dhcpcd configuration is up to date"
do_dhcpcd()
{
	[ -n "$1" ] || err 3 "USAGE: do_dhcpcd fix|check"
	local op="$1"
	local failed=0

	find_file_in_dirlist dhcpcd.conf "dhcpcd.conf" \
	    "${SRC_DIR}/external/bsd/dhcpcd/dist/src" \
	    "${SRC_DIR}/etc" || return 1
			# ${dir} is set by find_file_in_dirlist()
	populate_dir "$op" true "${dir}" "${DEST_DIR}/etc" 644 dhcpcd.conf
	failed=$(( ${failed} + $? ))

	check_dir "${op}" "${DEST_DIR}/var/db/dhcpcd" 755
	failed=$(( ${failed} + $? ))

	move_file "${op}" \
		"${DEST_DIR}/etc/dhcpcd.duid" \
		"${DEST_DIR}/var/db/dhcpcd/duid"
	failed=$(( ${failed} + $? ))

	move_file "${op}" \
		"${DEST_DIR}/etc/dhcpcd.secret" \
		"${DEST_DIR}/var/db/dhcpcd/secret"
	failed=$(( ${failed} + $? ))

	move_file "${op}" \
		"${DEST_DIR}/var/db/dhcpcd-rdm.monotonic" \
		"${DEST_DIR}/var/db/dhcpcd/rdm_monotonic"
	failed=$(( ${failed} + $? ))

	for lease in "${DEST_DIR}/var/db/dhcpcd-"*.lease*; do
		[ -f "${lease}" ] || continue
		new_lease=$(basename "${lease}" | ${SED} -e 's/dhcpcd-//')
		new_lease="${DEST_DIR}/var/db/dhcpcd/${new_lease}"
		move_file "${op}" "${lease}" "${new_lease}"
		failed=$(( ${failed} + $? ))
	done

	chroot_dir="${DEST_DIR}/var/chroot/dhcpcd"
	move_file "${op}" \
		"${chroot_dir}/var/db/dhcpcd/duid" \
		"${DEST_DIR}/var/db/dhcpcd/duid"
	failed=$(( ${failed} + $? ))

	move_file "${op}" \
		"${chroot_dir}/var/db/dhcpcd/secret" \
		"${DEST_DIR}/var/db/dhcpcd/secret"
	failed=$(( ${failed} + $? ))

	move_file "${op}" \
		"${chroot_dir}/var/db/dhcpcd/rdm_monotonic" \
		"${DEST_DIR}/var/db/dhcpcd/rdm_monotonic"
	failed=$(( ${failed} + $? ))

	for lease in "${chroot_dir}/var/db/dhcpcd/"*.lease*; do
		[ -f "${lease}" ] || continue
		new_lease="${DEST_DIR}/var/db/dhcpcd/$(basename ${lease})"
		move_file "${op}" "${lease}" "${new_lease}"
		failed=$(( ${failed} + $? ))
	done

	# Ensure chroot is now empty
	for dir in \
		$(find ${chroot_dir} ! -type d) \
		$(find ${chroot_dir} -type d -mindepth 1 | sort -r)
	do
		echo "/var/chroot/dhcpcd${dir##${chroot_dir}}"
	done | obsolete_paths "${op}"
	failed=$(( ${failed} + $? ))

	contents_owner "${op}" "${DEST_DIR}/var/db/dhcpcd" root wheel
	failed=$(( ${failed} + $? ))

	return ${failed}
}


#
#	dhcpcdrundir
#

additem dhcpcdrundir "accidentally created /@RUNDIR@ does not exist"
do_dhcpcdrundir()
{
	[ -n "$1" ] || err 3 "USAGE: do_dhcpcdrundir fix|check"
	local op="$1"
	local failed=0

	if [ -d "${DEST_DIR}/@RUNDIR@" ]; then
		if [ "${op}" = "check" ]; then
			msg "Remove erroneously created /@RUNDIR@"
			failed=1
		elif ! ${RM} -r "${DEST_DIR}/@RUNDIR@"; then
			msg "Failed to remove ${DEST_DIR}/@RUNDIR@"
			failed=1
		else
			msg "Removed erroneously created ${DEST_DIR}/@RUNDIR@"
		fi
	fi
	return ${failed}
}


#
#	envsys
#

additem envsys "envsys configuration is up to date"
do_envsys()
{
	[ -n "$1" ] || err 3 "USAGE: do_envsys fix|check"
	local op="$1"
	local failed=0
	local etcsets=$(getetcsets)

	populate_dir "$op" true "${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 \
		envsys.conf
	failed=$(( ${failed} + $? ))

	local powerd_scripts="$(select_set_files /etc/powerd/scripts/ \
	    "/etc/powerd/scripts/\([^[:space:]/]*\)" ${etcsets})"

	populate_dir "$op" true "${SRC_DIR}/etc/powerd/scripts" \
		"${DEST_DIR}/etc/powerd/scripts" \
		555 \
		${powerd_scripts}
	failed=$(( ${failed} + $? ))

	return ${failed}
}


#
#	fontconfig
#

additem fontconfig "X11 font configuration is up to date"
do_fontconfig()
{
	[ -n "$1" ] || err 3 "USAGE: do_fontconfig fix|check"
	local op="$1"
	local failed=0

	# First, check for updates we can handle.
	if ! $SOURCEMODE; then
		FONTCONFIG_DIR="${SRC_DIR}/etc/fonts/conf.avail"
	else
		FONTCONFIG_DIR="${XSRC_DIR}/external/mit/fontconfig/dist/conf.d"
	fi

	if [ ! -d "${FONTCONFIG_DIR}" ]; then
		msg "${FONTCONFIG_DIR} is not a directory; skipping check"
		return 0
	fi
	local regular_fonts="
10-autohint.conf
10-scale-bitmap-fonts.conf
10-sub-pixel-bgr.conf
10-sub-pixel-none.conf
10-sub-pixel-rgb.conf
10-sub-pixel-vbgr.conf
10-sub-pixel-vrgb.conf
10-unhinted.conf
11-lcdfilter-default.conf
11-lcdfilter-legacy.conf
11-lcdfilter-light.conf
20-unhint-small-vera.conf
25-unhint-nonlatin.conf
30-metric-aliases.conf
40-nonlatin.conf
45-generic.conf
45-latin.conf
49-sansserif.conf
50-user.conf
51-local.conf
60-generic.conf
60-latin.conf
65-fonts-persian.conf
65-khmer.conf
65-nonlatin.conf
69-unifont.conf
70-no-bitmaps.conf
70-yes-bitmaps.conf
80-delicious.conf
90-synthetic.conf
"
	populate_dir "$op" false "${FONTCONFIG_DIR}" \
	    "${DEST_DIR}/etc/fonts/conf.avail" \
	    444 \
	    ${regular_fonts}
	failed=$(( ${failed} + $? ))

	if ! $SOURCEMODE; then
		FONTS_DIR="${SRC_DIR}/etc/fonts"
	else
		FONTS_DIR="${SRC_DIR}/external/mit/xorg/lib/fontconfig/etc"
	fi

	populate_dir "$op" false "${FONTS_DIR}" "${DEST_DIR}/etc/fonts" 444 \
		fonts.conf
	failed=$(( ${failed} + $? ))

	# We can't modify conf.d easily; someone might have removed a file.

	# Look for old files that need to be deleted.
	local obsolete_fonts="
10-autohint.conf
10-no-sub-pixel.conf
10-sub-pixel-bgr.conf
10-sub-pixel-vbgr.conf
10-sub-pixel-vrgb.conf
10-unhinted.conf
25-unhint-nonlatin.conf
65-khmer.conf
70-no-bitmaps.conf
70-yes-bitmaps.conf
"
	local failed_fonts=""
	for i in ${obsolete_fonts}; do
	    if [ -f "${DEST_DIR}/etc/fonts/conf.d/$i" ]; then
		    conf_d_failed=1
		    failed_fonts="$failed_fonts $i"
	    fi
	done

	if [ -n "$failed_fonts" ]; then
		msg \
    "Broken fontconfig configuration found; please delete these files:"
		msg "[$failed_fonts]"
		failed=$(( ${failed} + 1 ))
	fi

	return ${failed}
}


#
#	gid
#

additem gid "required groups in /etc/group"
do_gid()
{
	[ -n "$1" ] || err 3 "USAGE: do_gid fix|check"

	check_ids "$1" groups "${DEST_DIR}/etc/group" \
	    "${SRC_DIR}/etc/group" 14 \
	    named ntpd sshd SKIP _pflogd _rwhod staff _proxy _timedc \
	    _sdpd _httpd _mdnsd _tests _tcpdump _tss _gpio _rtadvd SKIP \
	    _unbound _nsd nvmm _dhcpcd
}


#
#	gpio
#

additem gpio "gpio configuration is up to date"
do_gpio()
{
	[ -n "$1" ] || err 3 "USAGE: do_gpio fix|check"
	local op="$1"
	local failed=0

	populate_dir "$op" true "${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 \
		gpio.conf
	failed=$(( ${failed} + $? ))

	return ${failed}
}


#
#	hosts
#

additem hosts "/etc/hosts being up to date"
do_hosts()
{
	[ -n "$1" ] || err 3 "USAGE: do_hosts fix|check"

	modify_file "$1" "${DEST_DIR}/etc/hosts" "${SCRATCHDIR}/hosts" '
		/^(127\.0\.0\.1|::1)[ 	]+[^\.]*$/ {
			print $0, "localhost."
			next
		}
		{ print }
	'
	return $?
}


#
#	iscsi
#

additem iscsi "/etc/iscsi is populated"
do_iscsi()
{
	[ -n "$1" ] || err 3 "USAGE: do_iscsi fix|check"

	populate_dir "${op}" true \
	    "${SRC_DIR}/etc/iscsi" "${DEST_DIR}/etc/iscsi" 600 auths
	populate_dir "${op}" true \
	    "${SRC_DIR}/etc/iscsi" "${DEST_DIR}/etc/iscsi" 644 targets
	return $?
}


#
#	mailerconf
#

adddisableditem mailerconf "update /etc/mailer.conf after sendmail removal"
do_mailerconf()
{
	[ -n "$1" ] || err 3 "USAGE: do_mailterconf fix|check"
	local op="$1"

	local failed=0
	mta_path="$(${AWK} '/^sendmail[ \t]/{print$2}' \
		"${DEST_DIR}/etc/mailer.conf")"
	old_sendmail_path="/usr/libexec/sendmail/sendmail"
	if [ "${mta_path}" = "${old_sendmail_path}" ]; then
	    if [ "$op" = check ]; then
		msg "mailer.conf points to obsolete ${old_sendmail_path}"
		failed=1;
	    else
		populate_dir "${op}" false \
		"${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 mailer.conf
		failed=$?
	    fi
	fi

	return ${failed}
}


#
#	makedev
#

additem makedev "/dev/MAKEDEV being up to date"
do_makedev()
{
	[ -n "$1" ] || err 3 "USAGE: do_makedev fix|check"
	local failed=0

	if [ -f "${SRC_DIR}/etc/MAKEDEV.tmpl" ]; then
			# generate MAKEDEV from source if source is available
		env MACHINE="${MACHINE}" \
		    MACHINE_ARCH="${MACHINE_ARCH}" \
		    NETBSDSRCDIR="${SRC_DIR}" \
		    ${AWK} -f "${SRC_DIR}/etc/MAKEDEV.awk" \
		    "${SRC_DIR}/etc/MAKEDEV.tmpl" > "${SCRATCHDIR}/MAKEDEV"
	fi

	find_file_in_dirlist MAKEDEV "MAKEDEV" \
	    "${SCRATCHDIR}" "${SRC_DIR}/dev" \
	    || return 1
			# ${dir} is set by find_file_in_dirlist()
	find_makedev
	compare_dir "$1" "${dir}" "${MAKEDEV_DIR}" 555 MAKEDEV
	failed=$(( ${failed} + $? ))

	find_file_in_dirlist MAKEDEV.local "MAKEDEV.local" \
	    "${SRC_DIR}/etc" "${SRC_DIR}/dev" \
	    || return 1
			# ${dir} is set by find_file_in_dirlist()
	compare_dir "$1" "${dir}" "${DEST_DIR}/dev" 555 MAKEDEV.local
	failed=$(( ${failed} + $? ))

	return ${failed}
}


#
#	man.conf
#

additem manconf "check for a mandoc usage in /etc/man.conf"
do_manconf()
{
	[ -n "$1" ] || err 3 "USAGE: do_manconf fix|check"
	local op="$1"
	local failed=0

	[ -f "${DEST_DIR}/etc/man.conf" ] || return 0
	if ${GREP} -w "mandoc" "${DEST_DIR}/etc/man.conf" >/dev/null 2>&1;
	then
		failed=0;
	else
		failed=1
		notfixed=""
		if [ "${op}" = "fix" ]; then
			notfixed="${NOT_FIXED}"
		fi
		msg "The file /etc/man.conf has not been adapted to mandoc usage; you"
		msg "probably want to copy a new version over. ${notfixed}"
	fi

	return ${failed}
}


#
#	motd
#

additem motd "contents of motd"
do_motd()
{
	[ -n "$1" ] || err 3 "USAGE: do_motd fix|check"

	if ${GREP} -i 'http://www.NetBSD.org/Misc/send-pr.html' \
		"${DEST_DIR}/etc/motd" >/dev/null 2>&1 \
	    || ${GREP} -i 'https*://www.NetBSD.org/support/send-pr.html' \
		"${DEST_DIR}/etc/motd" >/dev/null 2>&1
	then
		tmp1="$(mktemp /tmp/postinstall.motd.XXXXXXXX)"
		tmp2="$(mktemp /tmp/postinstall.motd.XXXXXXXX)"
		${SED} '1,2d' <"${SRC_DIR}/etc/motd" >"${tmp1}"
		${SED} '1,2d' <"${DEST_DIR}/etc/motd" >"${tmp2}"

		if [ "$1" = check ]; then
			cmp -s "${tmp1}" "${tmp2}"
			result=$?
			if [ "${result}" -ne 0 ]; then
				msg \
    "Bug reporting messages do not seem to match the installed release"
			fi
		else
			head -n 2 "${DEST_DIR}/etc/motd" >"${tmp1}"
			${SED} '1,2d' <"${SRC_DIR}/etc/motd" >>"${tmp1}"
			cp "${tmp1}" "${DEST_DIR}/etc/motd"
			result=0
		fi

		${RM} -f "${tmp1}" "${tmp2}"
	else
		result=0
	fi

	return ${result}
}


#
#	mtree
#

additem mtree "/etc/mtree/ being up to date"
do_mtree()
{
	[ -n "$1" ] || err 3 "USAGE: do_mtree fix|check"
	local failed=0

	compare_dir "$1" "${SRC_DIR}/etc/mtree" "${DEST_DIR}/etc/mtree" 444 special
	failed=$(( ${failed} + $? ))

	if ! $SOURCEMODE; then
		MTREE_DIR="${SRC_DIR}/etc/mtree"
	else
		${RM} -rf "${SCRATCHDIR}/obj"
		mkdir "${SCRATCHDIR}/obj"
		${MAKE} -s -C "${SRC_DIR}/etc/mtree" TOOL_AWK="${AWK}" \
		    MAKEOBJDIR="${SCRATCHDIR}/obj" emit_dist_file > \
		    "${SCRATCHDIR}/NetBSD.dist"
		MTREE_DIR="${SCRATCHDIR}"
		${RM} -rf "${SCRATCHDIR}/obj"
	fi
	compare_dir "$1" "${MTREE_DIR}" "${DEST_DIR}/etc/mtree" 444 NetBSD.dist
	failed=$(( ${failed} + $? ))

	return ${failed}
}


#
#	named
#
handle_named_conf()
{
	local op="$1"
	local option="dnssec-enable"
	local failed=0
	local conf

	shift

	for conf; do
		local c=$(readlink -f "${conf}")
		if ! ${GREP} -qs "${option}" "${c}"
		then
			continue
		fi

		if [ "${op}" = "fix" ]; then
			${SED} -e "/${option}/d" "${c}" > "${c}.new"
			failed=$(( ${failed} + $? ))
			mv "${c}.new" "${c}"
			failed=$(( ${failed} + $? ))
			msg "Removed obsolete '${option}' in ${c}"
		else
			msg "'${option}' option in ${c} should be removed"
			failed=$(( ${failed} + 1 ))
		fi
	done

	return ${failed}
}

additem named "named configuration update"
do_named()
{
	local oldconf="${DEST_DIR}/etc/namedb/named.conf"
	local conf="${DEST_DIR}/etc/named.conf"
	[ -n "$1" ] || err 3 "USAGE: do_named fix|check"
	local op="$1"

	move_file "${op}" "${oldconf}" "${conf}"
	handle_named_conf "${op}" "${oldconf}" "${conf}"

	compare_dir "${op}" "${SRC_DIR}/etc/namedb" "${DEST_DIR}/etc/namedb" \
		644 \
		root.cache

	local od="${DEST_DIR}/usr/libexec/named"
	if [ -d "$od" ]; then
		rm -fr "$od"
		msg "Removed obsolete '${od}'"
	fi
}


#
#	opensslcertsconf
#

additem opensslcertsconf "ensure TLS trust anchor configuration exists"
do_opensslcertsconf()
{
	local certsdir certsconf defaultconf manualmsg

	[ -n "$1" ] || err 3 "USAGE: do_opensslcertsconf fix|check"

	certsdir="${DEST_DIR}/etc/openssl/certs"
	certsconf="${DEST_DIR}/etc/openssl/certs.conf"
	defaultconf="${DEST_DIR}/usr/share/examples/certctl/certs.conf"

	case $1 in
	check)	if [ ! -r "$certsconf" ]; then
			msg "/etc/openssl/certs.conf missing; see certctl(8)"
			return 1
		fi
		;;
	fix)	# If /etc/openssl/certs.conf is already there, nothing
		# to do.
		if [ -r "$certsconf" ]; then
			return 0
		fi

		# If /etc/openssl/certs is a symlink, or exists but is
		# not a directory, or is a directory but is nonempty,
		# then either it's managed by someone else or something
		# fishy is afoot.  So set it manual in that case.
		# Otherwise, install the default config file.
		if [ -h "$certsdir" ] ||
		    [ -e "$certsdir" -a ! -d "$certsdir" ] ||
		    ([ -d "$certsdir" ] &&
			find -f "$certsdir" -- \
			    -maxdepth 0 -type d -empty -exit 1)
		then
			msg "/etc/openssl/certs appears manually configured"
			manualmsg="[existing /etc/openssl/certs configuration"
			manualmsg="$manualmsg detected by postinstall(8)]"
                        # Change the commented-out `#manual' line to
                        # uncommented `manual', or print an error
                        # message if there is no `#manual' line and put
                        # `manual' at the end.
                        awk -v defaultconf="$defaultconf" \
			    -v manualmsg="$manualmsg" '
				BEGIN {
					manual = 0
				}
				/^#manual/ && !manual {
					manual = 1
					sub(/^#/, "")
					print
					print "#", manualmsg
					next
				}
				{
					print
				}
				END {
					if (!manual) {
						printf "warning: %s %s?\n", \
						    "corrupt", defaultconf \
						    >"/dev/stderr"
						print "manual"
						print "#", manualmsg
					}
				}
			' <$defaultconf >${certsconf}.tmp
		else
			msg "installing default /etc/openssl/certs.conf"
			cat <$defaultconf >${certsconf}.tmp
		fi && mv -f -- "${certsconf}.tmp" "$certsconf"
		;;
	*)	err 3 "USAGE: do_opensslcerts fix|check"
		;;
	esac
}


#
#	opensslcertsrehash
#

additem opensslcertsrehash "make /etc/openssl/certs cache of TLS trust anchors"
do_opensslcertsrehash()
{
	local mtreekeys scratchdir

	[ -n "$1" ] || err 3 "USAGE: do_opensslcertsrehash fix|check"

	if [ ! -r "${DEST_DIR}/etc/openssl/certs.conf" ]; then
		msg "/etc/openssl/certs.conf missing; see certctl(8)"
		return 1
	fi

	case $1 in
	check)	# Create a scratch rehash for comparison.
		mtreekeys="type,link"
		scratchdir="${SCRATCHDIR}/opensslcerts"
		/usr/sbin/certctl -c "$scratchdir" rehash || return $?

		# This will create ${scratchdir}/.certctl unless the
		# configuration is manual.  If the configuration is
		# manual, stop here; nothing to do.  certctl(8) will
		# have already printed a message about that.
		#
		# XXX Grody to rely on the internal structure used by
		# certctl(8), but less bad than having two versions of
		# the config parsing logic.
		if [ ! -f "${scratchdir}/.certctl" ]; then
			return 0
		fi

		# Do a dry run of rehashing into the real
		# /etc/openssl/certs.  This curious extra step ensures
		# that we report a failure if /etc/openssl/certs
		# appears to be managed manually, but `manual' was not
		# specified in /etc/openssl/certs.conf.
		/usr/sbin/certctl -n rehash || return $?

		# Compare the trees with mtree(8).  Inconveniently,
		# mtree returns status zero even if there are missing
		# or extra files.  So instead of examining the return
		# status, test for any output.  Empty output means
		# everything matches; otherwise the mismatch, missing,
		# or extra files are output.
		mtree -p "$scratchdir" -c -k "$mtreekeys" \
		| mtree -p "${DEST_DIR}/etc/openssl/certs" 2>&1 \
		| {
			while read -r line; do
				# mismatch, missing, or extra
				msg "/etc/openssl/certs needs rehash"
				exit 1
			done
			exit 0
		}
		;;
	fix)	# This runs openssl(1), which is not available as a
		# build-time tool.  So for now, restrict it to running
		# on the installed system.
		case $DEST_DIR in
		''|/)	;;
		*)	msg "opensslcertsrehash limited to DEST_DIR=/"
			return 1
			;;
		esac
		/usr/sbin/certctl rehash
		;;
	*)	err 3 "USAGE: do_opensslcerts fix|check"
		;;
	esac
}


#
#	pam
#

additem pam "/etc/pam.d is populated"
do_pam()
{
	[ -n "$1" ] || err 3 "USAGE: do_pam fix|check"
	local op="$1"
	local failed=0

	populate_dir "${op}" true "${SRC_DIR}/etc/pam.d" \
		"${DEST_DIR}/etc/pam.d" 644 \
		README cron display_manager ftpd gdm imap kde login other \
		passwd pop3 ppp racoon rexecd rsh sshd su system telnetd \
		xdm xserver

	failed=$(( ${failed} + $? ))

	return ${failed}
}


#
#	periodic
#

additem periodic "/etc/{daily,weekly,monthly,security} being up to date"
do_periodic()
{
	[ -n "$1" ] || err 3 "USAGE: do_periodic fix|check"

	compare_dir "$1" "${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 \
		daily weekly monthly security
}


#
#	pf
#

additem pf "pf configuration being up to date"
do_pf()
{
	[ -n "$1" ] || err 3 "USAGE: do_pf fix|check"
	local op="$1"
	local failed=0

	find_file_in_dirlist pf.os "pf.os" \
	    "${SRC_DIR}/dist/pf/etc" "${SRC_DIR}/etc" \
	    || return 1
			# ${dir} is set by find_file_in_dirlist()
	populate_dir "${op}" true \
	    "${dir}" "${DEST_DIR}/etc" 644 \
	    pf.conf
	failed=$(( ${failed} + $? ))

	compare_dir "${op}" "${dir}" "${DEST_DIR}/etc" 444 pf.os
	failed=$(( ${failed} + $? ))

	return ${failed}
}


#
#	ptyfsoldnodes
#

additem ptyfsoldnodes "remove legacy device nodes when using ptyfs"
do_ptyfsoldnodes()
{
	[ -n "$1" ] || err 3 "USAGE: do_ptyfsoldnodes fix|check"
	local op="$1"

	# Check whether ptyfs is in use
	local failed=0;
	if ! ${GREP} -E "^ptyfs" "${DEST_DIR}/etc/fstab" > /dev/null; then
		msg "ptyfs is not in use"
		return 0
	fi

	if [ ! -e "${DEST_DIR}/dev/pts" ]; then
		msg "ptyfs is not properly configured: missing /dev/pts"
		return 1
	fi

	# Find the device major numbers for the pty master and slave
	# devices, by parsing the output from "MAKEDEV -s pty0".
	#
	# Output from MAKEDEV looks like this:
	# ./ttyp0 type=char device=netbsd,5,0 mode=666 gid=0 uid=0
	# ./ptyp0 type=char device=netbsd,6,0 mode=666 gid=0 uid=0
	#
	# Output from awk, used in the eval statement, looks like this:
	# maj_ptym=6; maj_ptys=5;
	#
	local maj_ptym maj_ptys
	find_makedev
	eval "$(
	    ${HOST_SH} "${MAKEDEV_DIR}/MAKEDEV" -s pty0 2>/dev/null \
	    | ${AWK} '\
	    BEGIN { before_re = ".*device=[a-zA-Z]*,"; after_re = ",.*"; }
	    /ptyp0/ { maj_ptym = gensub(before_re, "", 1, $0);
		      maj_ptym = gensub(after_re, "", 1, maj_ptym); }
	    /ttyp0/ { maj_ptys = gensub(before_re, "", 1, $0);
		      maj_ptys = gensub(after_re, "", 1, maj_ptys); }
	    END { print "maj_ptym=" maj_ptym "; maj_ptys=" maj_ptys ";"; }
	    '
	    )"
	#msg "Major numbers are maj_ptym=${maj_ptym} maj_ptys=${maj_ptys}"
	if [ -z "$maj_ptym" ] || [ -z "$maj_ptys" ]; then
		msg "Cannot find device major numbers for pty master and slave"
		return 1
	fi

	# look for /dev/[pt]ty[p-zP-T][0-9a-zA-Z], and check that they
	# have the expected device major numbers.  ttyv* is typically not a
	# pty device, but we check it anyway.
	#
	# The "for d1" loop is intended to avoid overflowing ARG_MAX;
	# otherwise we could have used a single glob pattern.
	#
	# If there are no files that match a particular pattern,
	# then stat prints something like:
	#    stat: /dev/[pt]tyx?: lstat: No such file or directory
	# and we ignore it.  XXX: We also ignore other error messages.
	#
	local d1 major node
	local tmp="$(mktemp /tmp/postinstall.ptyfs.XXXXXXXX)"

	for d1 in p q r s t u v w x y z P Q R S T; do
		${STAT} -f "%Hr %N" "${DEST_DIR}/dev/"[pt]ty${d1}? 2>&1
	done \
	| while read -r major node ; do
		case "$major" in
		${maj_ptym}|${maj_ptys}) echo "$node" ;;
		esac
	done > "${tmp}"

	local desc="legacy device node"
	while read node; do
		if [ "${op}" = "check" ]; then
			msg "Remove ${desc} ${node}"
			failed=1
		else # "fix"
			if ${RM} "${node}"; then
				msg "Removed ${desc} ${node}"
			else
				warn "Failed to remove ${desc} ${node}"
				failed=1
			fi
		fi
	done < "${tmp}"
	${RM} "${tmp}"

	return ${failed}
}


#
#	pwd_mkdb
#

additem pwd_mkdb "passwd database version"
do_pwd_mkdb()
{
	[ -n "$1" ] || err 3 "USAGE: do_pwd_mkdb fix|check"
	local op="$1"
	local failed=0

	# XXX Ideally, we should figure out the endianness of the
	# target machine, and add "-E B"/"-E L" to the db(1) flags,
	# and "-B"/"-L" to the pwd_mkdb(8) flags if the target is not
	# the same as the host machine.  It probably doesn't matter,
	# because we don't expect "postinstall fix pwd_mkdb" to be
	# invoked during a cross build.

	set -- $(${DB} -q -Sb -Ub -To -N hash "${DEST_DIR}/etc/pwd.db" \
		'VERSION\0')
	case "$2" in
	'\001\000\000\000') return 0 ;; # version 1, little-endian
	'\000\000\000\001') return 0 ;; # version 1, big-endian
	esac

	if [ "${op}" = "check" ]; then
		msg "Update format of passwd database"
		failed=1
	elif ! ${PWD_MKDB} -V 1 -d "${DEST_DIR:-/}" \
			"${DEST_DIR}/etc/master.passwd";
	then
		msg "Can't update format of passwd database"
		failed=1
	else
		msg "Updated format of passwd database"
	fi

	return ${failed}
}


#
#	rc
#

# There is no info in src/distrib or /etc/mtree which rc* files
# can be overwritten unconditionally on upgrade. See PR/54741.
rc_644_files="
rc
rc.subr
rc.shutdown
"

rc_obsolete_vars="
amd amd_master
btcontrol btcontrol_devices
critical_filesystems critical_filesystems_beforenet
mountcritlocal mountcritremote
network ip6forwarding
network nfsiod_flags
sdpd sdpd_control
sdpd sdpd_groupname
sdpd sdpd_username
sysctl defcorename
"

update_rc()
{
	local op=$1
	local dir=$2
	local name=$3
	local bindir=$4
	local rcdir=$5

	if [ ! -x "${DEST_DIR}/${bindir}/${name}" ]; then
		return 0
	fi

	if ! find_file_in_dirlist "${name}" "${name}" \
	    "${rcdir}" "${SRC_DIR}/etc/rc.d"; then
		return 1
	fi
	populate_dir "${op}" false "${dir}" "${DEST_DIR}/etc/rc.d" 555 "${name}"
	return $?
}

# select non-obsolete files in a sets file
# $1: directory pattern
# $2: file pattern
# $3: filename
select_set_files()
{
	local qdir="$(echo $1 | ${SED} -e s@/@\\\\/@g -e s/\\./\\\\./g)"
	${SED} -n -e /obsolete/d \
	    -e "/^\.${qdir}/s@^.$2[[:space:]].*@\1@p" $3
}

# select obsolete files in a sets file
# $1: directory pattern
# $2: file pattern
# $3: setname
select_obsolete_files()
{
	if $SOURCEMODE; then
		${SED} -n -e "/obsolete/s@\.$1$2[[:space:]].*@\1@p" \
		    "${SRC_DIR}/distrib/sets/lists/$3/mi"
		return
	fi

	# On upgrade builds we don't extract the "etc" set so we
	# try to use the source set instead. See PR/54730 for
	# ways to better handle this.

	local obsolete_dir

	if [ $3 = "etc" ] ;then
		obsolete_dir="${SRC_DIR}/var/db/obsolete"
	else
		obsolete_dir="${DEST_DIR}/var/db/obsolete"
	fi
	${SED} -n -e "s@\.$1$2\$@\1@p" "${obsolete_dir}/$3"
}

getetcsets()
{
	if $SOURCEMODE; then
		echo "${SRC_DIR}/distrib/sets/lists/etc/mi"
	else
		echo "${SRC_DIR}/etc/mtree/set.etc"
	fi
}

additem rc "/etc/rc* and /etc/rc.d/ being up to date"
do_rc()
{
	[ -n "$1" ] || err 3 "USAGE: do_rc fix|check"
	local op="$1"
	local failed=0
	local generated_scripts=""
	local etcsets=$(getetcsets)
	if [ "${MKX11}" != "no" ]; then
		generated_scripts="${generated_scripts} xdm xfs"
	fi

	# Directories of external programs that have rc files (in bsd)
	local rc_external_files="blocklist nsd unbound"

	# rc* files in /etc/
	# XXX: at least rc.conf and rc.local shouldn't be updated. PR/54741
	#local rc_644_files="$(select_set_files /etc/rc \
	#    "/etc/\(rc[^[:space:]/]*\)" ${etcsets})"

	# no-obsolete rc files in /etc/rc.d
	local rc_555_files="$(select_set_files /etc/rc.d/ \
	    "/etc/rc\.d/\([^[:space:]]*\)" ${etcsets} | \
	    exclude ${rc_external_files})"

	# obsolete rc file in /etc/rc.d
	local rc_obsolete_files="$(select_obsolete_files /etc/rc.d/ \
	    "\([^[:space:]]*\)" etc)"

	compare_dir "${op}" "${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 \
		${rc_644_files}
	failed=$(( ${failed} + $? ))

	local extra_scripts
	if ! $SOURCEMODE; then
		extra_scripts="${generated_scripts}"
	else
		extra_scripts=""
	fi

	compare_dir "${op}" "${SRC_DIR}/etc/rc.d" "${DEST_DIR}/etc/rc.d" 555 \
		${rc_555_files} \
		${extra_scripts}
	failed=$(( ${failed} + $? ))

	local i rc_file
	for i in ${rc_external_files}; do
	    case $i in
	    *d) rc_file=${i};;
	    *)	rc_file=${i}d;;
	    esac

	    update_rc "${op}" "${dir}" ${rc_file} /sbin \
		"${SRC_DIR}/external/bsd/$i/etc/rc.d"
	    failed=$(( ${failed} + $? ))
	done

	if $SOURCEMODE && [ -n "${generated_scripts}" ]; then
		# generate scripts
		mkdir "${SCRATCHDIR}/rc"
		for f in ${generated_scripts}; do
			${SED} -e "s,@X11ROOTDIR@,${X11ROOTDIR},g" \
			    < "${SRC_DIR}/etc/rc.d/${f}.in" \
			    > "${SCRATCHDIR}/rc/${f}"
		done
		compare_dir "${op}" "${SCRATCHDIR}/rc" \
		    "${DEST_DIR}/etc/rc.d" 555 \
		    ${generated_scripts}
		failed=$(( ${failed} + $? ))
	fi

		# check for obsolete rc.d files
	for f in ${rc_obsolete_files}; do
		local fd="/etc/rc.d/${f}"
		[ -e "${DEST_DIR}${fd}" ] && echo "${fd}"
	done | obsolete_paths "${op}"
	failed=$(( ${failed} + $? ))

		# check for obsolete rc.conf(5) variables
	set -- ${rc_obsolete_vars}
	while [ $# -gt 1 ]; do
		if rcconf_is_set "${op}" "$1" "$2" 1; then
			failed=1
		fi
		shift 2
	done

	return ${failed}
}


#
#	sendmail
#

adddisableditem sendmail "remove obsolete sendmail configuration files and scripts"
do_sendmail()
{
	[ -n "$1" ] || err 3 "USAGE: do_sendmail fix|check"
	op="$1"
	failed=0

	# Don't complain if the "sendmail" package is installed because the
	# files might still be in use.
	if /usr/sbin/pkg_info -qe sendmail >/dev/null 2>&1; then
		return 0
	fi

	for f in /etc/mail/helpfile /etc/mail/local-host-names \
	    /etc/mail/sendmail.cf /etc/mail/submit.cf /etc/rc.d/sendmail \
	    /etc/rc.d/smmsp /usr/share/misc/sendmail.hf \
	    $( ( find "${DEST_DIR}/usr/share/sendmail" -type f ; \
	         find "${DEST_DIR}/usr/share/sendmail" -type d \
	       ) | unprefix "${DEST_DIR}" ) \
	    /var/log/sendmail.st \
	    /var/spool/clientmqueue \
	    /var/spool/mqueue
	do
		[ -e "${DEST_DIR}${f}" ] && echo "${f}"
	done | obsolete_paths "${op}"
	failed=$(( ${failed} + $? ))

	return ${failed}
}


#
#	ssh
#

additem ssh "ssh configuration update"
do_ssh()
{
	[ -n "$1" ] || err 3 "USAGE: do_ssh fix|check"
	local op="$1"

	local failed=0
	local etcssh="${DEST_DIR}/etc/ssh"
	if ! check_dir "${op}" "${etcssh}" 755; then
		failed=1
	fi

	if [ ${failed} -eq 0 ]; then
		for f in \
			    ssh_known_hosts ssh_known_hosts2 \
			    ssh_host_dsa_key ssh_host_dsa_key.pub \
			    ssh_host_rsa_key ssh_host_rsa_key.pub \
			    ssh_host_key ssh_host_key.pub \
		    ; do
			if ! move_file "${op}" \
			    "${DEST_DIR}/etc/${f}" "${etcssh}/${f}" ; then
				failed=1
			fi
		done
		for f in sshd.conf ssh.conf ; do
				# /etc/ssh/ssh{,d}.conf -> ssh{,d}_config
				#
			if ! move_file "${op}" \
			    "${etcssh}/${f}" "${etcssh}/${f%.conf}_config" ;
			then
				failed=1
			fi
				# /etc/ssh{,d}.conf -> /etc/ssh/ssh{,d}_config
				#
			if ! move_file "${op}" \
			    "${DEST_DIR}/etc/${f}" \
			    "${etcssh}/${f%.conf}_config" ;
			then
				failed=1
			fi
		done
	fi

	local sshdconf=""
	local f
	for f in \
	    "${etcssh}/sshd_config" \
	    "${etcssh}/sshd.conf" \
	    "${DEST_DIR}/etc/sshd.conf" ; do
		if [ -f "${f}" ]; then
			sshdconf="${f}"
			break
		fi
	done
	if [ -n "${sshdconf}" ]; then
		modify_file "${op}" "${sshdconf}" "${SCRATCHDIR}/sshdconf" '
			/^[^#$]/ {
				kw = tolower($1)
				if (kw == "hostkey" &&
				    $2 ~ /^\/etc\/+ssh_host(_[dr]sa)?_key$/ ) {
					sub(/\/etc\/+/, "/etc/ssh/")
				}
				if (kw == "rhostsauthentication" ||
				    kw == "verifyreversemapping" ||
				    kw == "reversemappingcheck") {
					sub(/^/, "# DEPRECATED:\t")
				}
			}
			{ print }
		'
		failed=$(( ${failed} + $? ))
	fi

	if ! find_file_in_dirlist moduli "moduli" \
	    "${SRC_DIR}/crypto/external/bsd/openssh/dist" "${SRC_DIR}/etc" ; then
		failed=1
			# ${dir} is set by find_file_in_dirlist()
	elif ! compare_dir "${op}" "${dir}" "${DEST_DIR}/etc" 444 moduli; then
		failed=1
	fi

	if ! check_dir "${op}" "${DEST_DIR}/var/chroot/sshd" 755 ; then
		failed=1
	fi

	if rcconf_is_set "${op}" sshd sshd_conf_dir 1; then
		failed=1
	fi

	return ${failed}
}


#
#	tcpdumpchroot
#

additem tcpdumpchroot "remove /var/chroot/tcpdump/etc/protocols"
do_tcpdumpchroot()
{
	[ -n "$1" ] || err 3 "USAGE: do_tcpdumpchroot fix|check"
	local op="$1"

	local failed=0;
	if [ -r "${DEST_DIR}/var/chroot/tcpdump/etc/protocols" ]; then
		if [ "${op}" = "fix" ]; then
			${RM} "${DEST_DIR}/var/chroot/tcpdump/etc/protocols"
			failed=$(( ${failed} + $? ))
			rmdir "${DEST_DIR}/var/chroot/tcpdump/etc"
			failed=$(( ${failed} + $? ))
		else
			failed=1
		fi
	fi
	return ${failed}
}


#
#	uid
#

additem uid "required users in /etc/master.passwd"
do_uid()
{
	[ -n "$1" ] || err 3 "USAGE: do_uid fix|check"

	check_ids "$1" users "${DEST_DIR}/etc/master.passwd" \
	    "${SRC_DIR}/etc/master.passwd" 12 \
	    postfix SKIP named ntpd sshd SKIP _pflogd _rwhod SKIP _proxy \
	    _timedc _sdpd _httpd _mdnsd _tests _tcpdump _tss SKIP _rtadvd \
	    SKIP _unbound _nsd SKIP _dhcpcd
}


#
#	varrwho
#

additem varrwho "required ownership of files in /var/rwho"
do_varrwho()
{
	[ -n "$1" ] || err 3 "USAGE: do_varrwho fix|check"

	contents_owner "$1" "${DEST_DIR}/var/rwho" _rwhod _rwhod
}


#
#	varshm
#

additem varshm "check for a tmpfs mounted on /var/shm"
do_varshm()
{
	[ -n "$1" ] || err 3 "USAGE: do_varshm fix|check"
	op="$1"
	failed=0

	[ -f "${DEST_DIR}/etc/fstab" ] || return 0
	if ${GREP} -E "^var_shm_symlink" "${DEST_DIR}/etc/rc.conf" >/dev/null 2>&1;
	then
		failed=0;
	elif ${GREP} -w "/var/shm" "${DEST_DIR}/etc/fstab" >/dev/null 2>&1;
	then
		failed=0;
	else
		if [ "${op}" = "check" ]; then
			failed=1
			msg "No /var/shm mount found in ${DEST_DIR}/etc/fstab"
		elif [ "${op}" = "fix" ]; then
			printf '\ntmpfs\t/var/shm\ttmpfs\trw,-m1777,-sram%%25\n' \
				>> "${DEST_DIR}/etc/fstab"
			msg "Added tmpfs with 25% ram limit as /var/shm"

		fi
	fi

	return ${failed}
}


#
#	wscons
#

additem wscons "wscons configuration file update"
do_wscons()
{
	[ -n "$1" ] || err 3 "USAGE: do_wscons fix|check"
	op="$1"

	[ -f "${DEST_DIR}/etc/wscons.conf" ] || return 0

	failed=0
	notfixed=""
	if [ "${op}" = "fix" ]; then
		notfixed="${NOT_FIXED}"
	fi
	while read _type _arg1 _rest; do
		if [ "${_type}" = "mux" -a "${_arg1}" = "1" ]; then
			msg \
    "Obsolete wscons.conf(5) entry \""${_type} ${_arg1}"\" found.${notfixed}"
			failed=1
		fi
	done < "${DEST_DIR}/etc/wscons.conf"

	return ${failed}
}


#
#	x11
#

additem x11 "x11 configuration update"
do_x11()
{
	[ -n "$1" ] || err 3 "USAGE: do_x11 fix|check"
	local p="$1"

	local failed=0
	local etcx11="${DEST_DIR}/etc/X11"
	local libx11=""
	if [ ! -d "${etcx11}" ]; then
		msg "${etcx11} is not a directory; skipping check"
		return 0
	fi
	if [ -d "${DEST_DIR}/usr/X11R6/." ]
	then
		libx11="${DEST_DIR}/usr/X11R6/lib/X11"
		if [ ! -d "${libx11}" ]; then
			msg "${libx11} is not a directory; skipping check"
			return 0
		fi
	fi

	local notfixed=""
	if [ "${op}" = "fix" ]; then
		notfixed="${NOT_FIXED}"
	fi

	local d
	# check if /usr/X11R6/lib/X11 needs to migrate to /etc/X11
	if [ -n "${libx11}" ]; then
		for d in \
		    fs lbxproxy proxymngr rstart twm xdm xinit xserver xsm \
		    ; do
			sd="${libx11}/${d}"
			ld="/etc/X11/${d}"
			td="${DEST_DIR}${ld}"
			if [ -h "${sd}" ]; then
				continue
			elif [ -d "${sd}" ]; then
				tdfiles="$(find "${td}" \! -type d)"
				if [ -n "${tdfiles}" ]; then
					msg "${sd} exists yet ${td} already" \
					    "contains files${notfixed}"
				else
					msg "Migrate ${sd} to ${td}${notfixed}"
				fi
				failed=1
			elif [ -e "${sd}" ]; then
				msg "Unexpected file ${sd}${notfixed}"
				continue
			else
				continue
			fi
		done
	fi

	# check if xdm resources have been updated
	if [ -r ${etcx11}/xdm/Xresources ] && \
	    ! ${GREP} -q 'inpColor:' ${etcx11}/xdm/Xresources; then
		msg "Update ${etcx11}/xdm/Xresources${notfixed}"
		failed=1
	fi

	return ${failed}
}


#
#	xkb
#
# /usr/X11R7/lib/X11/xkb/symbols/pc used to be a directory, but changed
# to a file on 2009-06-12.  Fixing this requires removing the directory
# (which we can do) and re-extracting the xbase set (which we can't do),
# or at least adding that one file (which we may be able to do if X11SRCDIR
# is available).
#

additem xkb "clean up for xkbdata to xkeyboard-config upgrade"
do_xkb()
{
	[ -n "$1" ] || err 3 "USAGE: do_xkb fix|check"
	local op="$1"
	local failed=0

	local pcpath="/usr/X11R7/lib/X11/xkb/symbols/pc"
	local pcsrcdir="${X11SRCDIR}/external/mit/xkeyboard-config/dist/symbols"

	local filemsg="\
${pcpath} was a directory, should be a file.
    To fix, extract the xbase set again."

	local notfixed=""
	if [ "${op}" = "fix" ]; then
		notfixed="${NOT_FIXED}"
	fi

	if [ ! -d "${DEST_DIR}${pcpath}" ]; then
		return 0
	fi

	# Delete obsolete files in the directory, and the directory
	# itself.  If the directory contains unexpected extra files
	# then it will not be deleted.
	( [ -f "${DEST_DIR}"/var/db/obsolete/xbase ] \
	    &&  ${SORT} -ru "${DEST_DIR}"/var/db/obsolete/xbase \
	    | ${GREP} -E "^\\.?${pcpath}/" ;
	    echo "${pcpath}" ) \
	| obsolete_paths "${op}"
	failed=$(( ${failed} + $? ))

	# If the directory was removed above, then try to replace it with
	# a file.
	if [ -d "${DEST_DIR}${pcpath}" ]; then
		msg "${filemsg}${notfixed}"
		failed=$(( ${failed} + 1 ))
	else
		if ! find_file_in_dirlist pc "${pcpath}" \
			"${pcsrcdir}" "${SRC_DIR}${pcpath%/*}"
		then
			msg "${filemsg}${notfixed}"
			failed=$(( ${failed} + 1 ))
		else
			# ${dir} is set by find_file_in_dirlist()
			populate_dir "${op}" true \
				"${dir}" "${DEST_DIR}${pcpath%/*}" 444 \
				pc
			failed=$(( ${failed} + $? ))
		fi
	fi

	return $failed
}


#
#	obsolete_stand
#	obsolete_stand_debug
#

obsolete_stand_internal()
{
	local prefix="$1"
	shift
	[ -n "$1" ] || err 3 "USAGE: do_obsolete_stand fix|check"
	local op="$1"
	local failed=0
	local dir

	for dir in \
	    ${prefix}/stand/${MACHINE} \
	    ${prefix}/stand/${MACHINE}-4xx \
	    ${prefix}/stand/${MACHINE}-booke \
	    ${prefix}/stand/${MACHINE}-xen \
	    ${prefix}/stand/${MACHINE}pae-xen
	do
		[ -d "${DEST_DIR}${dir}" ] && obsolete_stand "${dir}"
	done | obsolete_paths "${op}"
	failed=$(( ${failed} + $? ))

	return ${failed}
}

adddisableditem obsolete_stand "remove obsolete files from /stand"
do_obsolete_stand()
{
	obsolete_stand_internal "" "$@"
	return $?
}

adddisableditem obsolete_stand_debug "remove obsolete files from /usr/libdata/debug/stand"
do_obsolete_stand_debug()
{
	obsolete_stand_internal "/usr/libdata/debug" "$@"
	return $?
}


#
#	obsolete
#
# NOTE: This item is last to allow other items to move obsolete files.
#

listarchsubdirs()
{
	if ! $SOURCEMODE; then
		echo "@ARCHSUBDIRS@"
	else
		${SED} -n -e '/ARCHDIR_SUBDIR/s/[[:space:]]//gp' \
		    "${SRC_DIR}/compat/archdirs.mk"
	fi
}

getarchsubdirs()
{
	local m
	local i

	case ${MACHINE_ARCH} in
	*arm*|*aarch64*)	m=arm;;
	x86_64)			m=amd64;;
	*)			m=${MACHINE_ARCH};;
	esac

	for i in $(listarchsubdirs); do
		echo $i
	done | ${SORT} -u | ${SED} -n -e "/=${m}/s@.*=${m}/\(.*\)@\1@p"
}

getcompatlibdirs()
{
	local i

	for i in $(getarchsubdirs); do
		if [ -d "${DEST_DIR}/usr/lib/$i" ]; then
			echo /usr/lib/$i
		fi
	done
}

additem obsolete "remove obsolete file sets and minor libraries"
do_obsolete()
{
	[ -n "$1" ] || err 3 "USAGE: do_obsolete fix|check"
	local op="$1"
	local failed=0
	local i

	${SORT} -ru "${DEST_DIR}"/var/db/obsolete/* | obsolete_paths "${op}"
	failed=$(( ${failed} + $? ))

	(
		obsolete_libs /lib
		obsolete_libs /usr/lib
		obsolete_libs /usr/lib/i18n
		obsolete_libs /usr/X11R6/lib
		obsolete_libs /usr/X11R7/lib
		for i in $(getcompatlibdirs); do
			obsolete_libs $i
		done
	) | obsolete_paths "${op}"
	failed=$(( ${failed} + $? ))

	return ${failed}
}


#
#	end of items
#	------------
#


help()
{
	cat << _USAGE_
Usage: ${PROGNAME} [-a ARCH] [-d DEST_DIR] [-m MACHINE] [-s SRC_ARG] [-x XSRC_DIR] OPERATION ...
       ${PROGNAME} -?

	Perform post-installation checks and/or fixes on a system's
	configuration files.
	If no items are provided, a default set of checks or fixes is applied.

	Options:
	-?		Display this help, and exit.
	-a ARCH		Set \$MACHINE_ARCH to ARCH.	[${MACHINE_ARCH}]
	-d DEST_DIR	Destination directory to check. [${DEST_DIR:-/}]
	-m MACHINE	Set \$MACHINE to MACHINE.	[${MACHINE}]
	-s SRC_ARG	Location of the source files.  This may be any of
			the following:
			-s SRC_DIR	A directory that contains a NetBSD
					source tree.
			-s TGZ_DIR	A directory in which one or both of
					"etc.tgz" and "xetc.tgz" have been
					extracted.
			-s TGZ_FILE	A distribution set file such as
					"etc.tgz" or "xetc.tgz".
					May be specified multipled times.
							[${SRC_DIR:-/usr/src}]
	-x XSRC_DIR	Location of the X11 source files.  This must be
			a directory that contains a NetBSD xsrc tree.
							[${XSRC_DIR:-/usr/src/../xsrc}]

	Supported values for OPERATION:
	help		Display this help, and exit.
	list		List available items.
	check ITEM ...	Perform post-installation checks on ITEMs.
	diff [-bcenpuw] ITEM ...
			Similar to 'check' but also output difference of files,
			using diff with the provided options.
	fix ITEM ...	Apply fixes that 'check' determines need to be applied.
	usage		Display this help, and exit.
_USAGE_
}

usage()
{
	help 1>&2
	exit 2
}


list()
{
	local i
	echo "Default set of items (to apply if no items are provided by user):"
	echo " Item                 Description"
	echo " ----                 -----------"
	for i in ${defaultitems}; do
		eval desc=\"\${desc_${i}}\"
		printf " %-20s %s\n" "${i}" "${desc}"
	done
	echo "Items disabled by default (must be requested explicitly):"
	echo " Item                 Description"
	echo " ----                 -----------"
	for i in ${otheritems}; do
		eval desc=\"\${desc_${i}}\"
		printf " %-20s %s\n" "${i}" "${desc}"
	done
}


main()
{
	DIRMODE=false		# true if "-s" specified a directory
	ITEMS=			# items to check|diff|fix. [${defaultitems}]
	N_SRC_ARGS=0		# number of "-s" args in SRC_ARGLIST
	SOURCEMODE=false	# true if "-s" specified a source directory
	SRC_ARGLIST=		# quoted list of one or more "-s" args
	SRC_DIR="${SRC_ARG}"	# set default value for early usage()
	TGZLIST=		# quoted list list of tgz files
	TGZMODE=false		# true if "-s" specifies a tgz file
	XSRC_DIR="${SRC_ARG}/../xsrc"
	XSRC_DIR_FIX=

	case "$(uname -s)" in
	Darwin)
		# case sensitive match for case insensitive fs
		file_exists_exact=file_exists_exact
		;;
	*)
		file_exists_exact=:
		;;
	esac

		# Validate options.
		#
	while getopts :a:d:m:s:x: ch; do
		case "${ch}" in
		a)
			MACHINE_ARCH="${OPTARG}"
			;;
		d)
			DEST_DIR="${OPTARG}"
			;;
		m)
			MACHINE="${OPTARG}"
			;;
		s)
			qarg="$(shell_quote "${OPTARG}")"
			N_SRC_ARGS=$(( $N_SRC_ARGS + 1 ))
			SRC_ARGLIST="${SRC_ARGLIST}${SRC_ARGLIST:+ }-s ${qarg}"
			if [ -f "${OPTARG}" ]; then
				# arg refers to a *.tgz file.
				# This may happen twice, for both
				# etc.tgz and xetc.tgz, so we build up a
				# quoted list in TGZLIST.
				TGZMODE=true
				TGZLIST="${TGZLIST}${TGZLIST:+ }${qarg}"
				# Note that, when TGZMODE is true,
				# SRC_ARG is used only for printing
				# human-readable messages.
				SRC_ARG="${TGZLIST}"
			elif [ -d "${OPTARG}" ]; then
				# arg refers to a directory.
				# It might be a source directory, or a
				# directory where the sets have already
				# been extracted.
				DIRMODE=true
				SRC_ARG="${OPTARG}"
				if [ -f "${OPTARG}/etc/Makefile" ]; then
					SOURCEMODE=true
				fi
			else
				err 2 "Invalid argument for -s option"
			fi
			;;
		x)
			if [ -d "${OPTARG}" ]; then
				# arg refers to a directory.
				XSRC_DIR="${OPTARG}"
				XSRC_DIR_FIX="-x ${OPTARG} "
			else
				err 2 "Not a directory for -x option"
			fi
			;;
		"?")
			if [ "${OPTARG}" = "?" ]; then
				help
				return	# no further processing or validation
			fi
			warn "Unknown option -${OPTARG}"
			usage
			;;

		:)
			warn "Missing argument for option -${OPTARG}"
			usage
			;;

		*)
			err 3 "Unimplemented option -${ch}"
			;;
		esac
	done
	shift $((${OPTIND} - 1))
	if [ $# -eq 0 ] ; then
		warn "Missing operation"
		usage
	fi
	op="$1"
	shift

	if [ "$N_SRC_ARGS" -gt 1 ] && $DIRMODE; then
		err 2 "Multiple -s args are allowed only with tgz files"
	fi
	if [ "$N_SRC_ARGS" -eq 0 ]; then
		# The default SRC_ARG was set elsewhere
		DIRMODE=true
		SOURCEMODE=true
		SRC_ARGLIST="-s $(shell_quote "${SRC_ARG}")"
	fi

		# Validate 'diff' first, as it becomes 'check'
		#
	case "${op}" in

	diff)
		op=check
		DIFF_STYLE=n			# default style is RCS
		OPTIND=1
		while getopts :bcenpuw ch; do
			case "${ch}" in
			c|e|n|u)
				if [ "${DIFF_STYLE}" != "n" -a \
				    "${DIFF_STYLE}" != "${ch}" ]; then
					warn "diff: conflicting output style: -${ch}"
					usage
				fi
				DIFF_STYLE="${ch}"
				;;
			b|p|w)
				DIFF_OPT="${DIFF_OPT} -${ch}"
				;;
			"?")
				# NOTE: not supporting diff -?
				warn "diff: Unknown option -${OPTARG}"
				usage
				;;
			:)
				warn "diff: Missing argument for option -${OPTARG}"
				usage
				;;
			*)
				err 3 "diff: Unimplemented option -${ch}"
				;;
			esac
		done
		shift $((${OPTIND} - 1))
		;;

	esac

		# Validate operation and items.
		#
	case "${op}" in

	check|fix)
		ITEMS="$*"
		: ${ITEMS:="${defaultitems}"}

		# ensure that all supplied items are valid
		#
		for i in ${ITEMS}; do
			eval desc=\"\${desc_${i}}\"
			[ -n "${desc}" ] || err 2 "Unsupported ${op} '"${i}"'"
		done
		;;

	help|usage)
		help
		return	# no further processing or validation
		;;

	list)
		# processed below
		;;

	*)
		warn "Unknown operation '"${op}"'"
		usage
		;;

	esac

	#
	# If '-s' arg or args specified tgz files, extract them
	# to a scratch directory.
	#
	if $TGZMODE; then
		ETCTGZDIR="${SCRATCHDIR}/etc.tgz"
		echo "Note: Creating temporary directory ${ETCTGZDIR}"
		if ! mkdir "${ETCTGZDIR}"; then
			err 2 "Can't create ${ETCTGZDIR}"
		fi
		( # subshell to localise changes to "$@"
			eval "set -- ${TGZLIST}"
			for tgz in "$@"; do
				echo "Note: Extracting files from ${tgz}"
				cat "${tgz}" | (
					cd "${ETCTGZDIR}" &&
					tar -zxf -
				) || err 2 "Can't extract ${tgz}"
			done
		)
		SRC_DIR="${ETCTGZDIR}"
	else
		SRC_DIR="${SRC_ARG}"
	fi

	[ -d "${SRC_DIR}" ]	|| err 2 "${SRC_DIR} is not a directory"
	[ -d "${DEST_DIR}" ]	|| err 2 "${DEST_DIR} is not a directory"
	[ -n "${MACHINE}" ]	|| err 2 "\${MACHINE} is not defined"
	[ -n "${MACHINE_ARCH}" ] || err 2 "\${MACHINE_ARCH} is not defined"
	if ! $SOURCEMODE && ! [ -f "${SRC_DIR}/etc/mtree/set.etc" ]; then
		err 2 "Files from the etc.tgz set are missing"
	fi

		# If directories are /, clear them, so various messages
		# don't have leading "//".   However, this requires
		# the use of ${foo:-/} to display the variables.
		#
	[ "${SRC_DIR}" = "/" ]	&& SRC_DIR=""
	[ "${DEST_DIR}" = "/" ]	&& DEST_DIR=""

	detect_x11

		# Perform operation.
		#
	case "${op}" in

	check|fix)
		[ -n "${ITEMS}" ] || err 2 "${op}: missing items"

		echo "Source directory: ${SRC_DIR:-/}"
		if $TGZMODE; then
			echo " (extracted from: ${SRC_ARG})"
		fi
		echo "Target directory: ${DEST_DIR:-/}"
		items_passed=
		items_failed=
		for i in ${ITEMS}; do
			echo "${i} ${op}:"
			( eval do_${i} ${op} )
			if [ $? -eq 0 ]; then
				items_passed="${items_passed} ${i}"
			else
				items_failed="${items_failed} ${i}"
			fi
		done

		if [ "${op}" = "check" ]; then
			plural="checks"
		else
			plural="fixes"
		fi

		echo "${PROGNAME} ${plural} passed:${items_passed}"
		echo "${PROGNAME} ${plural} failed:${items_failed}"
		if [ -n "${items_failed}" ]; then
		    exitstatus=1;
		    if [ "${op}" = "check" ]; then
			[ "$MACHINE" = "$(uname -m)" ] && m= || m=" -m $MACHINE"
			cat <<_Fix_me_
To fix, run:
    ${HOST_SH} ${0} ${SRC_ARGLIST} ${XSRC_DIR_FIX}-d ${DEST_DIR:-/}$m fix${items_failed}
Note that this may overwrite local changes.
_Fix_me_
		    fi
		fi
		;;

	list)
		echo "Source directory: ${SRC_DIR:-/}"
		echo "Target directory: ${DEST_DIR:-/}"
		if $TGZMODE; then
			echo " (extracted from: ${SRC_ARG})"
		fi
		list
		;;

	*)
			# diff, help, usage handled during operation validation
		err 3 "Unimplemented operation '"${op}"'"
		;;

	esac
}

if [ -n "$POSTINSTALL_FUNCTION" ]; then
	eval "$POSTINSTALL_FUNCTION"
	exit 0
fi

# defaults
#
PROGNAME="${0##*/}"
SRC_ARG="/usr/src"
DEST_DIR="/"
: ${MACHINE:="$( uname -m )"}	# assume native build if $MACHINE is not set
: ${MACHINE_ARCH:="$( uname -p )"}# assume native build if not set

DIFF_STYLE=
DIFF_OPT=
NOT_FIXED=" (FIX MANUALLY)"
SCRATCHDIR="$( mkdtemp )" || err 2 "Can't create scratch directory"
trap "${RM} -rf \"\${SCRATCHDIR}\" ; exit 0" 1 2 3 15	# HUP INT QUIT TERM

umask 022
exec 3>/dev/null
exec 4>/dev/null
exitstatus=0

main "$@"
${RM} -rf "${SCRATCHDIR}"
exit $exitstatus
