[BACK]Return to postinstall.in CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / usr.sbin / postinstall

File: [cvs.NetBSD.org] / src / usr.sbin / postinstall / postinstall.in (download)

Revision 1.24, Mon Jun 15 14:25:40 2020 UTC (3 years, 10 months ago) by christos
Branch: MAIN
Changes since 1.23: +33 -1 lines

deal with blacklist -> blocklist

#!/bin/sh
#
# $NetBSD: postinstall.in,v 1.24 2020/06/15 14:25:40 christos Exp $
#
# Copyright (c) 2002-2015 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.
#

#
# XXX BE SURE TO USE ${DEST_DIR} PREFIX BEFORE ALL REAL FILE OPERATIONS XXX
#

#
# 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}

#
#	helper functions
#

err()
{
	exitval=$1
	shift
	echo 1>&2 "${PROGNAME}: $*"
	if [ -n "${SCRATCHDIR}" ]; then
	    /bin/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

	_base="/tmp/_postinstall.$$"
	_serial=0

	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:-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"
	_cdop="$1"
	_cddir="$2"
	_cdmode="$3"
	[ -d "${_cddir}" ] && return 0
	if [ "${_cdop}" = "check" ]; then
		msg "${_cddir} is not a directory"
		return 1
	elif ! mkdir -m "${_cdmode}" "${_cddir}" ; then
		msg "Can't create missing ${_cddir}"
		return 1
	else
		msg "Missing ${_cddir} 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 start srcfile id [...]"
	_op="$1"
	_type="$2"
	_file="$3"
	_srcfile="$4"
	_start="$5"
	shift 5
	#_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
	_notfixed=""
	if [ "${_op}" = "fix" ]; then
		_notfixed="${NOT_FIXED}"
	fi
	_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
		msg "and adjust if necessary."
		return 1
	fi
	return 0
}

# populate_dir op onlynew src dest mode file [file ...]
#	Perform op ("check" or "fix") on files in src/ against dest/
#	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 dest mode file [...]"
	_op="$1"
	_onlynew="$2"
	_src="$3"
	_dest="$4"
	_mode="$5"
	shift 5
	#_files="$@"

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

	_cmpdir_rv=0
	for f in "$@"; do
		fs="${_src}/${f}"
		fd="${_dest}/${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 dest mode file [file ...]
#	Perform op ("check" or "fix") on files in src/ against dest/
#	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 dest mode file [...]"
	_op="$1"
	_src="$2"
	_dest="$3"
	_mode="$4"
	shift 4
	#_files="$@"

	populate_dir "$_op" false "$_src" "$_dest" "$_mode" "$@"
}

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

	if [ -f "${_fm_src}" -a ! -f "${_fm_dest}" ]; then
		if [ "${_fm_op}" = "check" ]; then
			msg "Move ${_fm_src} to ${_fm_dest}"
			return 1
		fi
		if ! mv "${_fm_src}" "${_fm_dest}"; then
			msg "Can't move ${_fm_src} to ${_fm_dest}"
			return 1
		fi
		msg "Moved ${_fm_src} to ${_fm_dest}"
	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]"
	_rcis_op="$1"
	_rcis_name="$2"
	_rcis_var="$3"
	_rcis_verbose="$4"
	_rcis_notfixed=""
	if [ "${_rcis_op}" = "fix" ]; then
		_rcis_notfixed="${NOT_FIXED}"
	fi
	(
		for f in \
		    "${DEST_DIR}/etc/rc.conf" \
		    "${DEST_DIR}/etc/rc.conf.d/${_rcis_name}"; do
			[ -f "${f}" ] && . "${f}"
		done
		eval echo -n \"\${${_rcis_var}}\" 1>&3
		if eval "[ -n \"\${${_rcis_var}}\" \
			    -o \"\${${_rcis_var}-UNSET}\" != \"UNSET\" ]"; then
			if [ -n "${_rcis_verbose}" ]; then
				msg \
    "Obsolete rc.conf(5) variable '\$${_rcis_var}' found.${_rcis_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"
	_rcie_var="$1"
	(
		[ -f "${DEST_DIR}/etc/rc.conf" ] && . "${DEST_DIR}/etc/rc.conf"
		eval _rcie_val=\"\${${_rcie_var}}\"
		case $_rcie_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 [...]"

	_file="$1" ; shift
	_msg="$1" ; shift
	_dir1st=	# first dir in list
	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"
	_path="${1#.}"
	[ -h "${DEST_DIR}${_path}" ] || \
		[ -e "${DEST_DIR}${_path}" ] || return 1
	while [ "${_path}" != "/" -a "${_path}" != "." ] ; do
		_dirname="$(dirname "${_path}" 2>/dev/null)"
		_basename="$(basename "${_path}" 2>/dev/null)"
		ls -fa "${DEST_DIR}${_dirname}" 2> /dev/null \
			| ${GREP} -F -x "${_basename}" >/dev/null \
			|| 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"
	op="$1"

	failed=0
	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 AllLibs 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"
	dir="$1"

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

exclude()
{
	local dollar
	case "$1" in
	-t)
		dollar='$'
		shift
		;;
	*)
		dollar=
		;;
	esac
	if [ -z "$*" ]; then
		cat
	else
		eval ${GREP} -v -E "'(^$(echo $* | \
		    ${SED} -e s/\\./\\\\./g -e 's/ /'${dollar}'|^/'g)${dollar})'"
	fi
}

#
# find all the target symlinks of shared libaries and exclude them
# from consideration for removal
#
exclude_libs() {
	local target="$(ls -l -d lib*.so.* 2> /dev/null \
	    | ${AWK} '{ print $11; }' \
	    | ${SED} -e 's@.*/@@' | ${SORT} -u)"
	exclude -t ${target}
}

_obsolete_libs()
{
	dir="$1"

	(

	if [ ! -e "${DEST_DIR}/${dir}" ]
	then
		return 0
	fi

	cd "${DEST_DIR}/${dir}" || err 2 "can't cd to ${DEST_DIR}/${dir}"
	echo lib*.so.* \
	| tr ' ' '\n' \
	| ${AWK} -v LibDir="${dir}/" '
#{

function digit(v, c, n) { return (n <= c) ? v[n] : 0 }

function checklib(results, line, regex) {
	if (! match(line, regex))
		return
	lib = substr(line, RSTART, RLENGTH)
	rev = substr($0, RLENGTH+1)
	if (! (lib in results)) {
		results[lib] = rev
		return
	}
	orevc = split(results[lib], orev, ".")
	nrevc = split(rev, nrev, ".")
	maxc = (orevc > nrevc) ? orevc : nrevc
	for (i = 1; i <= maxc; i++) {
		res = digit(orev, orevc, i) - digit(nrev, nrevc, i)
		if (res < 0) {
			print LibDir lib results[lib]
			results[lib] = rev
			return
		} else if (res > 0) {
			print LibDir lib rev
			return
		}
	}
}

/^lib.*\.so\.[0-9]+\.[0-9]+(\.[0-9]+)?(\.debug)?$/ {
	if (AllLibs)
		checklib(minor, $0, "^lib.*\\.so\\.")
	else
		checklib(found, $0, "^lib.*\\.so\\.[0-9]+\\.")
}

/^lib.*\.so\.[0-9]+$/ {
	if (AllLibs)
		checklib(major, $0, "^lib.*\\.so\\.")
}

#}' | exclude_libs

	)
}

# 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"

	_mfop="$1"
	_mffile="$2"
	_mfscratch="$3"
	_mfprog="$4"
	_mffailed=0

	${AWK} "${_mfprog}" < "${_mffile}" > "${_mfscratch}"
	if ! cmp -s "${_mffile}" "${_mfscratch}"; then
		diff "${_mffile}" "${_mfscratch}" > "${_mfscratch}.diffs"
		if [ "${_mfop}" = "check" ]; then
			msg "${_mffile} needs the following changes:"
			_mffailed=1
		elif ! rm -f "${_mffile}" ||
		     ! cp -f "${_mfscratch}" "${_mffile}"; then
			msg "${_mffile} changes not applied:"
			_mffailed=1
		else
			msg "${_mffile} changes applied:"
		fi
		while read _line; do
			msg "	${_line}"
		done < "${_mfscratch}.diffs"
	fi
	return ${_mffailed}
}


# 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"

	_op="$1"
	_dir="$2"
	_user="$3"
	_grp="$4"

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

#
#	Bluetooth
#

additem bluetooth "Bluetooth configuration is up to date"
do_bluetooth()
{
	[ -n "$1" ] || err 3 "USAGE: do_bluetooth fix|check"
	op="$1"
	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} + $? ))

	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}
}

fixblock() {
	for i; do
		if [ ! -f "$i" ]; then
			continue
		fi
		local p=$(stat -f %Lp "$i")
		chmod u+w "$i"
		sed -i -e s/black/block/g "$i"
		chmod "$p" "$i"
	done
}

#
#	blocklist update
#
additem blocklist "rename old files to blocklist"
do_blocklist()
{
	# if we are actually using blocklistd
	if [ -f /var/db/blacklist.db ]; then
		mv /var/db/blocklist.db /var/db/blacklist.db
	fi

	# if we have fixed the rc files we are done
	if [ ! -f /etc/rc.d/blacklist ]; then
		return
	fi

	fixblock /etc/rc.conf /etc/npf.conf /etc/defaults/rc.conf
	rm -f /etc/rc.d/blacklist
}

#
#	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"
	op="$1"
	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 "accidentaly created /@RUNDIR@ does not exist"
do_dhcpcdrundir()
{
	[ -n "$1" ] || err 3 "USAGE: do_dhcpcdrundir fix|check"
	op="$1"
	failed=0

	if [ -d "${DEST_DIR}/@RUNDIR@" ]; then
		if [ "${op}" = "check" ]; then
			msg "Remove eroneously created /@RUNDIR@"
			failed=1
		elif ! rm -r "${DEST_DIR}/@RUNDIR@"; then
			msg "Failed to remove ${DEST_DIR}/@RUNDIR@"
			failed=1
		else
			msg "Removed eroneously 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}
}

#
#	autofs config files
#
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 
"
	op="$1"
	failed=0
	if [ "$op" = "fix" ]; then
		mkdir -p "${DEST_DIR}/etc/autofs"
	fi
	failed=$(( ${failed} + $? ))
	populate_dir "$op" false "${SRC_DIR}/etc" \
	    "${DEST_DIR}/etc" \
	    644 \
	    auto_master
	failed=$(( ${failed} + $? ))
	populate_dir "$op" false "${SRC_DIR}/etc/autofs" \
	    "${DEST_DIR}/etc/autofs" \
	    644 \
	    ${autofs_files}
	return ${failed}
}


#
#	X11 fontconfig
#
additem fontconfig "X11 font configuration is up to date"
do_fontconfig()
{
	[ -n "$1" ] || err 3 "USAGE: do_fontconfig fix|check"
	op="$1"
	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-no-sub-pixel.conf
10-scale-bitmap-fonts.conf
10-sub-pixel-bgr.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.
	obsolete_fonts="
10-autohint.conf
10-no-sub-pixel.conf
10-sub-pixel-bgr.conf
10-sub-pixel-rgb.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
"
	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"
	op="$1"
	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 $?
}

#
#	makedev
#
additem makedev "/dev/MAKEDEV being up to date"
do_makedev()
{
	[ -n "$1" ] || err 3 "USAGE: do_makedev   fix|check"
	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}
}

#
#	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"
	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
		/bin/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}"
		/bin/rm -rf "${SCRATCHDIR}/obj"
	fi
	compare_dir "$1" "${MTREE_DIR}" "${DEST_DIR}/etc/mtree" 444 NetBSD.dist
	failed=$(( ${failed} + $? ))

	return ${failed}
}

#
#	named
#
additem named "named configuration update"
do_named()
{
	[ -n "$1" ] || err 3 "USAGE: do_named  fix|check"
	op="$1"

	move_file "${op}" \
		"${DEST_DIR}/etc/namedb/named.conf" \
		"${DEST_DIR}/etc/named.conf"

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

#
#	pam
#
additem pam "/etc/pam.d is populated"
do_pam()
{
	[ -n "$1" ] || err 3 "USAGE: do_pam  fix|check"
	op="$1"
	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"
	op="$1"
	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}
}

#
#	pwd_mkdb
#
additem pwd_mkdb "passwd database version"
do_pwd_mkdb()
{
	[ -n "$1" ] || err 3 "USAGE: do_pwd_mkdb  fix|check"
	op="$1"
	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="blacklist 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} + $? ))

	for i in ${rc_external_files}; do
	    local rc_file 
	    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}
}

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

	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}
}

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

	failed=0
	_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

	sshdconf=""
	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}
}

#
#	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"
	op="$1"

	failed=0
	_etcx11="${DEST_DIR}/etc/X11"
	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

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

	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

	# check if xdm resources have been updated
	if [ -r ${_etcx11}/xdm/Xresources ] && \
	    ! ${GREP} 'inpColor:' ${_etcx11}/xdm/Xresources > /dev/null; 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"
	op="$1"
	failed=0

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

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

	_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
}

#
#	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
}


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

	failed=0;
	if [ -r "${DEST_DIR}/var/chroot/tcpdump/etc/protocols" ]; then
		if [ "$1" = "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}
}


#
#	atf
#
additem atf "install missing atf configuration files and validate them"
do_atf()
{
	[ -n "$1" ] || err 3 "USAGE: do_atf  fix|check"
	op="$1"
	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}" "${DEST_DIR}/etc/atf" 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}" "${DEST_DIR}/etc/atf" 644 \
		    atf-run.hooks || failed=1
	else
		failed=1
	fi

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

	return ${failed}
}

handle_atf_user()
{
	local op="$1"
	local failed=0

	local conf="${DEST_DIR}/etc/atf/common.conf"
	if grep '[^#]*unprivileged-user[ \t]*=.*_atf' "${conf}" >/dev/null
	then
		if [ "$1" = "fix" ]; then
			${SED} -e \
			    "/[^#]*unprivileged-user[\ t]*=/s/_atf/_tests/" \
			    "${conf}" >"${conf}.new"
			failed=$(( ${failed} + $? ))
			mv "${conf}.new" "${conf}"
			failed=$(( ${failed} + $? ))
			msg "Set unprivileged-user=_tests in ${conf}"
		else
			msg "unprivileged-user=_atf in ${conf} should be" \
			    "unprivileged-user=_tests"
			failed=1
		fi
	fi

	return ${failed}
}

#
#	catpages
#
obsolete_catpages()
{
	basedir="$2"
	section="$3"
	mandir="${basedir}/man${section}"
	catdir="${basedir}/cat${section}"
	test -d "$mandir" || return 0
	test -d "$catdir" || return 0
	(cd "$mandir" && find . -type f) | {
	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 [ "$1" = "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()
{
	failed=0
	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 [ "$1" = "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
}

#
#	man.conf
#
additem manconf "check for a mandoc usage in /etc/man.conf"
do_manconf()
{
	[ -n "$1" ] || err 3 "USAGE: do_manconf  fix|check"
	op="$1"
	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}
}


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

	# Check whether ptyfs is in use
	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;
	#
	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.
	#
	_ptyfs_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 >"${_ptyfs_tmp}"

	_desc="legacy device node"
	while read node; do
		if [ "${_ptyfs_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 < "${_ptyfs_tmp}"
	rm "${_ptyfs_tmp}"

	return ${failed}
}


#
#	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}
}

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

	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 "${DESTDIR}${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 $?
}

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


getarchsubdirs() {
	local m
	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() {
	for i in $(getarchsubdirs); do
		if [ -d "${DEST_DIR}/usr/lib/$i" ]; then
			echo /usr/lib/$i
		fi
	done
}

#
#	obsolete
#	(this item is last to allow other items to move obsolete files)
#
additem obsolete "remove obsolete file sets and minor libraries"
do_obsolete()
{
	[ -n "$1" ] || err 3 "USAGE: do_obsolete  fix|check"
	op="$1"
	failed=0

	${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
#	------------
#


usage()
{
	cat 1>&2 << _USAGE_
Usage: ${PROGNAME} [-s srcdir] [-x xsrcdir] [-d destdir] [-m mach] [-a arch] op [item [...]]
	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:
	-s {srcdir|tgzfile|tempdir}
			Location of the source files.  This may be any
			of the following:
			* A directory that contains a NetBSD source tree;
			* A distribution set file such as "etc.tgz" or
			  "xetc.tgz".  Pass multiple -s options to specify
			  multiple such files;
			* A temporary directory in which one or both of
			  "etc.tgz" and "xetc.tgz" have been extracted.
							[${SRC_DIR:-/usr/src}]
	-x xsrcdir      Location of the X11 source files.  This must be
			a directory that contains a NetBSD xsrc tree.
							[${XSRC_DIR:-/usr/src/../xsrc}]
	-d destdir	Destination directory to check. [${DEST_DIR:-/}]
	-m mach		MACHINE.			[${MACHINE}]
	-a arch		MACHINE_ARCH.			[${MACHINE_ARCH}]

	Operation may be one of:
		help	Display this help.
		list	List available items.
		check	Perform post-installation checks on items.
		diff [diff(1) options ...]
			Similar to 'check' but also output difference of files.
		fix	Apply fixes that 'check' determines need to be applied.
		usage	Display this usage.
_USAGE_
	exit 2
}


list()
{
	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 "  %-12s  %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 "  %-12s  %s\n" "${i}" "${desc}"
	done

}


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

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

	while getopts s:x:d:m:a: ch; do
		case "${ch}" in
		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
			;;
		d)
			DEST_DIR="${OPTARG}"
			;;
		m)
			MACHINE="${OPTARG}"
			;;
		a)
			MACHINE_ARCH="${OPTARG}"
			;;
		*)
			usage
			;;
		esac
	done
	shift $((${OPTIND} - 1))
	[ $# -gt 0 ] || usage

	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

	#
	# 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

	op="$1"
	shift

	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
					err 2 "conflicting output style: ${ch}"
				fi
				DIFF_STYLE="${ch}"
				;;
			b|p|w)
				DIFF_OPT="${DIFF_OPT} -${ch}"
				;;
			*)
				err 2 "unknown diff option"
				;;
			esac
		done
		shift $((${OPTIND} - 1))
		;;
	esac

	case "${op}" in

	usage|help)
		usage
		;;

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

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

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

		# perform each check/fix
		#
		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 ${todo}; 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

		;;

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

	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=
NOT_FIXED=" (FIX MANUALLY)"
SCRATCHDIR="$( mkdtemp )" || err 2 "Can't create scratch directory"
trap "/bin/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 "$@"
/bin/rm -rf "${SCRATCHDIR}"
exit $exitstatus