Module Name:    src
Committed By:   christos
Date:           Fri Jun 14 01:06:33 UTC 2019

Modified Files:
        src/usr.sbin/postinstall: Makefile
Added Files:
        src/usr.sbin/postinstall: postinstall.in
Removed Files:
        src/usr.sbin/postinstall: postinstall

Log Message:
build dynamically the list of compat archsubdirs.


To generate a diff of this commit:
cvs rdiff -u -r1.1 -r1.2 src/usr.sbin/postinstall/Makefile
cvs rdiff -u -r1.232 -r0 src/usr.sbin/postinstall/postinstall
cvs rdiff -u -r0 -r1.1 src/usr.sbin/postinstall/postinstall.in

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/usr.sbin/postinstall/Makefile
diff -u src/usr.sbin/postinstall/Makefile:1.1 src/usr.sbin/postinstall/Makefile:1.2
--- src/usr.sbin/postinstall/Makefile:1.1	Sun Apr 17 11:15:49 2005
+++ src/usr.sbin/postinstall/Makefile	Thu Jun 13 21:06:33 2019
@@ -1,9 +1,23 @@
-# $NetBSD: Makefile,v 1.1 2005/04/17 15:15:49 lukem Exp $
+# $NetBSD: Makefile,v 1.2 2019/06/14 01:06:33 christos Exp $
+
+.include <bsd.own.mk>
 
 FILES=		postinstall
 MAN=		postinstall.8
 
 FILESDIR=	/usr/sbin
 FILESMODE=	${BINMODE}
+FILESBUILD=	yes
+
+ARCHSUBDIRS	!= ${MAKE} -f ${NETBSDSRCDIR}/compat/archdirs.mk \
+	ACTIVE_CC=clang -V ARCHDIR_SUBDIR | ${TOOL_SED} -e 's@[a-z0-9_]*/@@g'
+
+.SUFFIXES: .in
+.in:
+	${_MKTARGET_CREATE}
+	@rm -f ${.TARGET}
+	${TOOL_SED} -e "s#@ARCHSUBDIRS@#${ARCHSUBDIRS}#" < ${.ALLSRC} > ${.TARGET}
+
+CLEANFILES+= postinstall
 
 .include <bsd.prog.mk>

Added files:

Index: src/usr.sbin/postinstall/postinstall.in
diff -u /dev/null src/usr.sbin/postinstall/postinstall.in:1.1
--- /dev/null	Thu Jun 13 21:06:33 2019
+++ src/usr.sbin/postinstall/postinstall.in	Thu Jun 13 21:06:33 2019
@@ -0,0 +1,2562 @@
+#!/bin/sh
+#
+# $NetBSD: postinstall.in,v 1.1 2019/06/14 01:06:33 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}"
+}
+
+_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\\.")
+}
+
+#}'
+
+	)
+}
+
+# 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
+		if [ ! -z "`find "${_dir}" \( ! -user "${_user}" \) -o \
+		    \( ! -group "${_grp}" \)`" ]; 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}" \) \) -a -print0 \
+		| xargs -0 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}
+}
+
+#
+#	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
+
+	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"
+	op="$1"
+	failed=0
+
+	populate_dir "$op" true "${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 \
+		envsys.conf
+	failed=$(( ${failed} + $? ))
+
+	populate_dir "$op" true "${SRC_DIR}/etc/powerd/scripts" \
+		"${DEST_DIR}/etc/powerd/scripts" 555 sensor_battery \
+		sensor_drive sensor_fan sensor_indicator sensor_power \
+		sensor_resistance sensor_temperature sensor_voltage
+	failed=$(( ${failed} + $? ))
+
+	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
+
+	populate_dir "$op" false "${FONTCONFIG_DIR}" "${DEST_DIR}/etc/fonts/conf.avail" 444 \
+		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
+	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.
+
+	conf_d_failed=0
+	# Look for old files that need to be deleted.
+	if [ -f "${DEST_DIR}/etc/fonts/conf.d/10-unhinted.conf" -a \
+	     -f "${DEST_DIR}/etc/fonts/conf.d/10-autohint.conf" ]; then
+		conf_d_failed=1
+		failed=$(( ${failed} + 1 ))
+	fi
+
+	if [ "$conf_d_failed" = 1 ]; then
+		msg \
+    "Broken fontconfig configuration found; please delete these files"
+		msg \
+    "in the ${DEST_DIR}/etc/fonts/conf.d/ subdirectory:"
+		msg \
+    "   10-autohint.conf 10-no-sub-pixel.conf 10-sub-pixel-bgr.conf"
+		msg \
+    "   10-sub-pixel-rgb.conf 10-sub-pixel-vbgr.conf"
+		msg \
+    "   10-sub-pixel-vrgb.conf 10-unhinted.conf 25-unhint-nonlatin.conf"
+		msg \
+    "   65-khmer.conf 70-no-bitmaps.conf 70-yes-bitmaps.conf"
+		msg \
+    "(This warning only appears if both the 10-unhinted.conf and"
+		msg \
+    "10-autohint.conf files are present."
+	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
+}
+
+#
+#	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 'http://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
+#
+
+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
+}
+
+exclude()
+{
+	if [ -z "$*" ]; then
+		cat
+	else
+		eval ${GREP} -v -E "'(^$(echo $* | sed -e 's/ /|^/'g))'"
+	fi
+}
+
+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/
+	local rc_444_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="$(${SED} -n \
+	    -e '/obsolete/s@./etc/rc.d/\([^[:space:]]*\)[[:space:]].*@\1@p' \
+	    ${etcsets})"
+
+	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
+}
+
+
+#
+#	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
+#
+adddisableditem obsolete_stand "remove obsolete files from /stand"
+do_obsolete_stand()
+{
+	[ -n "$1" ] || err 3 "USAGE: do_obsolete_stnd  fix|check"
+	op="$1"
+	failed=0
+
+	for dir in \
+	    /stand/${MACHINE} \
+	    /stand/${MACHINE}-4xx \
+	    /stand/${MACHINE}-booke \
+	    /stand/${MACHINE}-xen \
+	    /stand/${MACHINE}pae-xen
+	do
+		[ -d "${DESTDIR}${dir}" ] && obsolete_stand "${dir}"
+	done | obsolete_paths "${op}"
+	failed=$(( ${failed} + $? ))
+
+	return ${failed}
+}
+
+getarchsubdirs() {
+	if ! $SOURCEMODE; then
+		echo "@ARCHSUBDIRS@"
+		return
+	fi
+	local m
+	case ${MACHINE_ARCH} in
+	*arm*|*aarch64*)	m=arm;;
+	x86_64)			m=amd64;;
+	*)			m=${MACHINE_ARCH};;
+	esac
+
+	${SED} -n -e "/[=[:space:]]${m}/s@.*[=[:space:]]${m}/\(.*\)@\1@p" \
+	    ${SRC_DIR}/compat/archdirs.mk | ${SORT} -u
+}
+
+getcompatlibdirs() {
+	for i in $(getarchsubdirs); do
+		if [ -d /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
+}
+
+# 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

Reply via email to