xref: /netbsd-src/usr.sbin/postinstall/postinstall.in (revision 782713e6c126f1866c6d9cfdee4ceb49483b5828)
1#!/bin/sh
2#
3# $NetBSD: postinstall.in,v 1.51 2022/05/29 10:47:40 andvar Exp $
4#
5# Copyright (c) 2002-2022 The NetBSD Foundation, Inc.
6# All rights reserved.
7#
8# This code is derived from software contributed to The NetBSD Foundation
9# by Luke Mewburn.
10#
11# Redistribution and use in source and binary forms, with or without
12# modification, are permitted provided that the following conditions
13# are met:
14# 1. Redistributions of source code must retain the above copyright
15#    notice, this list of conditions and the following disclaimer.
16# 2. Redistributions in binary form must reproduce the above copyright
17#    notice, this list of conditions and the following disclaimer in the
18#    documentation and/or other materials provided with the distribution.
19#
20# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30# POSSIBILITY OF SUCH DAMAGE.
31#
32# postinstall
33#	Check for or fix configuration changes that occur
34#	over time as NetBSD evolves.
35#
36
37#
38# NOTE: Be sure to use ${DEST_DIR} prefix before all real file operations.
39#
40
41#
42# checks to add:
43#	- sysctl(8) renames (net.inet6.ip6.bindv6only -> net.inet6.ip6.v6only)
44#	- de* -> tlp* migration (/etc/ifconfig.de*, $ifconfig_de*, ...) ?
45#	- support quiet/verbose mode ?
46#	- differentiate between failures caused by missing source
47#	  and real failures
48#	- install moduli into usr/share/examples/ssh and use from there?
49#	- differentiate between "needs fix" versus "can't fix" issues
50#
51
52# This script is executed as part of a cross build.  Allow the build
53# environment to override the locations of some tools.
54: ${AWK:=awk}
55: ${DB:=db}
56: ${GREP:=grep}
57: ${HOST_SH:=sh}
58: ${MAKE:=make}
59: ${PWD_MKDB:=/usr/sbin/pwd_mkdb}
60: ${SED:=sed}
61: ${SORT:=sort}
62: ${STAT:=stat}
63
64#
65#	helper functions
66#
67
68err()
69{
70	exitval=$1
71	shift
72	echo 1>&2 "${PROGNAME}: $*"
73	if [ -n "${SCRATCHDIR}" ]; then
74	    /bin/rm -rf "${SCRATCHDIR}"
75	fi
76	exit ${exitval}
77}
78
79warn()
80{
81	echo 1>&2 "${PROGNAME}: $*"
82}
83
84msg()
85{
86	echo "	$*"
87}
88
89mkdtemp()
90{
91	# Make sure we don't loop forever if mkdir will always fail.
92	[ -d /tmp ] || err 2 /tmp is not a directory
93	[ -w /tmp ] || err 2 /tmp is not writable
94
95	_base="/tmp/_postinstall.$$"
96	_serial=0
97
98	while true; do
99		_dir="${_base}.${_serial}"
100		mkdir -m 0700 "${_dir}" && break
101		_serial=$((${_serial} + 1))
102	done
103	echo "${_dir}"
104}
105
106# Quote args to make them safe in the shell.
107# Usage: quotedlist="$(shell_quote args...)"
108#
109# After building up a quoted list, use it by evaling it inside
110# double quotes, like this:
111#    eval "set -- $quotedlist"
112# or like this:
113#    eval "\$command $quotedlist \$filename"
114#
115shell_quote()
116{(
117	local result=''
118	local arg qarg
119	LC_COLLATE=C ; export LC_COLLATE # so [a-zA-Z0-9] works in ASCII
120	for arg in "$@" ; do
121		case "${arg}" in
122		'')
123			qarg="''"
124			;;
125		*[!-./a-zA-Z0-9]*)
126			# Convert each embedded ' to '\'',
127			# then insert ' at the beginning of the first line,
128			# and append ' at the end of the last line.
129			# Finally, elide unnecessary '' pairs at the
130			# beginning and end of the result and as part of
131			# '\'''\'' sequences that result from multiple
132			# adjacent quotes in he input.
133			qarg="$(printf "%s\n" "$arg" | \
134			    ${SED:-sed} -e "s/'/'\\\\''/g" \
135				-e "1s/^/'/" -e "\$s/\$/'/" \
136				-e "1s/^''//" -e "\$s/''\$//" \
137				-e "s/'''/'/g"
138				)"
139			;;
140		*)
141			# Arg is not the empty string, and does not contain
142			# any unsafe characters.  Leave it unchanged for
143			# readability.
144			qarg="${arg}"
145			;;
146		esac
147		result="${result}${result:+ }${qarg}"
148	done
149	printf "%s\n" "$result"
150)}
151
152# Convert arg $1 to a basic regular expression (as in sed)
153# that will match the arg.  This works by inserting backslashes
154# before characters that are special in basic regular expressions.
155# It also inserts backslashes before the extra characters specified
156# in $2 (which defaults to "/,").
157# XXX: Does not handle embedded newlines.
158# Usage: regex="$(bre_quote "${string}")"
159bre_quote()
160{
161	local arg="$1"
162	local extra="${2-/,}"
163	printf "%s\n" "${arg}" | ${SED} -e 's/[][^$.*\\'"${extra}"']/\\&/g'
164}
165
166# unprefix dir
167#	Remove any dir prefix from a list of paths on stdin,
168#	and write the result to stdout.  Useful for converting
169#	from ${DEST_DIR}/path to /path.
170#
171unprefix()
172{
173	[ $# -eq 1 ] || err 3 "USAGE: unprefix dir"
174	local prefix="${1%/}"
175	prefix="$(bre_quote "${prefix}")"
176
177	${SED} -e "s,^${prefix}/,/,"
178}
179
180# additem item description
181#	Add item to list of supported items to check/fix,
182#	which are checked/fixed by default if no item is requested by user.
183#
184additem()
185{
186	[ $# -eq 2 ] || err 3 "USAGE: additem item description"
187	defaultitems="${defaultitems}${defaultitems:+ }$1"
188	eval desc_$1=\"\$2\"
189}
190
191# adddisableditem item description
192#	Add item to list of supported items to check/fix,
193#	but execute the item only if the user asks for it explicitly.
194#
195adddisableditem()
196{
197	[ $# -eq 2 ] || err 3 "USAGE: adddisableditem item description"
198	otheritems="${otheritems}${otheritems:+ }$1"
199	eval desc_$1=\"\$2\"
200}
201
202# checkdir op dir mode
203#	Ensure dir exists, and if not, create it with the appropriate mode.
204#	Returns 0 if ok, 1 otherwise.
205#
206check_dir()
207{
208	[ $# -eq 3 ] || err 3 "USAGE: check_dir op dir mode"
209	_cdop="$1"
210	_cddir="$2"
211	_cdmode="$3"
212	[ -d "${_cddir}" ] && return 0
213	if [ "${_cdop}" = "check" ]; then
214		msg "${_cddir} is not a directory"
215		return 1
216	elif ! mkdir -m "${_cdmode}" "${_cddir}" ; then
217		msg "Can't create missing ${_cddir}"
218		return 1
219	else
220		msg "Missing ${_cddir} created"
221	fi
222	return 0
223}
224
225# check_ids op type file srcfile start id ...
226#	Check if file of type "users" or "groups" contains the relevant IDs.
227#	Use srcfile as a reference for the expected contents.
228#	The specified "id" names should be given in numerical order,
229#	with the first name corresponding to numerical value "start",
230#	and with the special name "SKIP" being used to mark gaps in the
231#	sequence.
232#	Returns 0 if ok, 1 otherwise.
233#
234check_ids()
235{
236	[ $# -ge 6 ] || err 3 "USAGE: checks_ids op type file start srcfile id ..."
237	_op="$1"
238	_type="$2"
239	_file="$3"
240	_srcfile="$4"
241	_start="$5"
242	shift 5
243	#_ids="$@"
244
245	if [ ! -f "${_file}" ]; then
246		msg "${_file} doesn't exist; can't check for missing ${_type}"
247		return 1
248	fi
249	if [ ! -r "${_file}" ]; then
250		msg "${_file} is not readable; can't check for missing ${_type}"
251		return 1
252	fi
253	_notfixed=""
254	if [ "${_op}" = "fix" ]; then
255		_notfixed="${NOT_FIXED}"
256	fi
257	_missing="$(${AWK} -v start=$_start -F: '
258		BEGIN {
259			for (x = 1; x < ARGC; x++) {
260				if (ARGV[x] == "SKIP")
261					continue;
262				idlist[ARGV[x]]++;
263				value[ARGV[x]] = start + x - 1;
264			}
265			ARGC=1
266		}
267		{
268			found[$1]++
269			number[$1] = $3
270		}
271		END {
272			for (id in idlist) {
273				if (!(id in found))
274					printf("%s (missing)\n", id)
275				else if (number[id] != value[id])
276					printf("%s (%d != %d)\n", id,
277					    number[id], value[id])
278				start++;
279			}
280		}
281	' "$@" < "${_file}")"	|| return 1
282	if [ -n "${_missing}" ]; then
283		msg "Error ${_type}${_notfixed}:" $(echo ${_missing})
284		msg "Use the following as a template:"
285		set -- ${_missing}
286		while [ $# -gt 0 ]
287		do
288			${GREP} -E "^${1}:" ${_srcfile}
289			shift 2
290		done | sort -t: -k3n
291		msg "and adjust if necessary."
292		return 1
293	fi
294	return 0
295}
296
297# populate_dir op onlynew src dest mode file ...
298#	Perform op ("check" or "fix") on files in src/ against dest/
299#	If op = "check" display missing or changed files, optionally with diffs.
300#	If op != "check" copies any missing or changed files.
301#	If onlynew evaluates to true, changed files are ignored.
302#	Returns 0 if ok, 1 otherwise.
303#
304populate_dir()
305{
306	[ $# -ge 5 ] || err 3 "USAGE: populate_dir op onlynew src dest mode file ..."
307	_op="$1"
308	_onlynew="$2"
309	_src="$3"
310	_dest="$4"
311	_mode="$5"
312	shift 5
313	#_files="$@"
314
315	if [ ! -d "${_src}" ]; then
316		msg "${_src} is not a directory; skipping check"
317		return 1
318	fi
319	check_dir "${_op}" "${_dest}" 755 || return 1
320
321	_cmpdir_rv=0
322	for f in "$@"; do
323		fs="${_src}/${f}"
324		fd="${_dest}/${f}"
325		_error=""
326		if [ ! -f "${fd}" ]; then
327			_error="${fd} does not exist"
328		elif ! cmp -s "${fs}" "${fd}" ; then
329			if $_onlynew; then	# leave existing ${fd} alone
330				continue;
331			fi
332			_error="${fs} != ${fd}"
333		else
334			continue
335		fi
336		if [ "${_op}" = "check" ]; then
337			msg "${_error}"
338			if [ -n "${DIFF_STYLE}" -a -f "${fd}" ]; then
339				diff -${DIFF_STYLE} ${DIFF_OPT} "${fd}" "${fs}"
340			fi
341			_cmpdir_rv=1
342		elif ! rm -f "${fd}" ||
343		     ! cp -f "${fs}" "${fd}"; then
344			msg "Can't copy ${fs} to ${fd}"
345			_cmpdir_rv=1
346		elif ! chmod "${_mode}" "${fd}"; then
347			msg "Can't change mode of ${fd} to ${_mode}"
348			_cmpdir_rv=1
349		else
350			msg "Copied ${fs} to ${fd}"
351		fi
352	done
353	return ${_cmpdir_rv}
354}
355
356# compare_dir op src dest mode file ...
357#	Perform op ("check" or "fix") on files in src/ against dest/
358#	If op = "check" display missing or changed files, optionally with diffs.
359#	If op != "check" copies any missing or changed files.
360#	Returns 0 if ok, 1 otherwise.
361#
362compare_dir()
363{
364	[ $# -ge 4 ] || err 3 "USAGE: compare_dir op src dest mode file ..."
365	_op="$1"
366	_src="$2"
367	_dest="$3"
368	_mode="$4"
369	shift 4
370	#_files="$@"
371
372	populate_dir "$_op" false "$_src" "$_dest" "$_mode" "$@"
373}
374
375# move_file op src dest --
376#	Check (op == "check") or move (op != "check") from src to dest.
377#	Returns 0 if ok, 1 otherwise.
378#
379move_file()
380{
381	[ $# -eq 3 ] || err 3 "USAGE: move_file op src dest"
382	_fm_op="$1"
383	_fm_src="$2"
384	_fm_dest="$3"
385
386	if [ -f "${_fm_src}" -a ! -f "${_fm_dest}" ]; then
387		if [ "${_fm_op}" = "check" ]; then
388			msg "Move ${_fm_src} to ${_fm_dest}"
389			return 1
390		fi
391		if ! mv "${_fm_src}" "${_fm_dest}"; then
392			msg "Can't move ${_fm_src} to ${_fm_dest}"
393			return 1
394		fi
395		msg "Moved ${_fm_src} to ${_fm_dest}"
396	fi
397	return 0
398}
399
400# rcconf_is_set op name var [verbose] --
401#	Load the rcconf for name, and check if obsolete rc.conf(5) variable
402#	var is defined or not.
403#	Returns 0 if defined (even to ""), otherwise 1.
404#	If verbose != "", print an obsolete warning if the var is defined.
405#
406rcconf_is_set()
407{
408	[ $# -ge 3 ] || err 3 "USAGE: rcconf_is_set op name var [verbose]"
409	_rcis_op="$1"
410	_rcis_name="$2"
411	_rcis_var="$3"
412	_rcis_verbose="$4"
413	_rcis_notfixed=""
414	if [ "${_rcis_op}" = "fix" ]; then
415		_rcis_notfixed="${NOT_FIXED}"
416	fi
417	(
418		for f in \
419		    "${DEST_DIR}/etc/rc.conf" \
420		    "${DEST_DIR}/etc/rc.conf.d/${_rcis_name}"; do
421			[ -f "${f}" ] && . "${f}"
422		done
423		eval echo -n \"\${${_rcis_var}}\" 1>&3
424		if eval "[ -n \"\${${_rcis_var}}\" \
425			    -o \"\${${_rcis_var}-UNSET}\" != \"UNSET\" ]"; then
426			if [ -n "${_rcis_verbose}" ]; then
427				msg \
428    "Obsolete rc.conf(5) variable '\$${_rcis_var}' found.${_rcis_notfixed}"
429			fi
430			exit 0
431		else
432			exit 1
433		fi
434	)
435}
436
437# rcvar_is_enabled var
438#	Check if rcvar is enabled
439#
440rcvar_is_enabled()
441{
442	[ $# -eq 1 ] || err 3 "USAGE: rcvar_is_enabled var"
443	_rcie_var="$1"
444	(
445		[ -f "${DEST_DIR}/etc/rc.conf" ] && . "${DEST_DIR}/etc/rc.conf"
446		eval _rcie_val=\"\${${_rcie_var}}\"
447		case $_rcie_val in
448		#	"yes", "true", "on", or "1"
449		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
450			exit 0
451			;;
452
453		*)
454			exit 1
455			;;
456		esac
457	)
458}
459
460# find_file_in_dirlist() file message dir1 ... --
461#	Find which directory file is in, and sets ${dir} to match.
462#	Returns 0 if matched, otherwise 1 (and sets ${dir} to "").
463#
464#	Generally, check the directory for the "checking from source" case,
465#	and then the directory for the "checking from extracted etc.tgz" case.
466#
467find_file_in_dirlist()
468{
469	[ $# -ge 3 ] || err 3 "USAGE: find_file_in_dirlist file msg dir1 ..."
470
471	_file="$1" ; shift
472	_msg="$1" ; shift
473	_dir1st=	# first dir in list
474	for dir in "$@"; do
475		: ${_dir1st:="${dir}"}
476		if [ -f "${dir}/${_file}" ]; then
477			if [ "${_dir1st}" != "${dir}" ]; then
478				msg \
479    "(Checking for ${_msg} from ${dir} instead of ${_dir1st})"
480			fi
481			return 0
482		fi
483	done
484	msg "Can't find source directory for ${_msg}"
485	return 1
486}
487
488# file_exists_exact path
489#	Returns true if a file exists in the ${DEST_DIR} whose name
490#	is exactly ${path}, interpreted in a case-sensitive way
491#	even if the underlying file system is case-insensitive.
492#
493#	The path must begin with '/' or './', and is interpreted as
494#	being relative to ${DEST_DIR}.
495#
496file_exists_exact()
497{
498	[ -n "$1" ] || err 3 "USAGE: file_exists_exact path"
499	_path="${1#.}"
500	[ -h "${DEST_DIR}${_path}" ] || \
501		[ -e "${DEST_DIR}${_path}" ] || return 1
502	while [ "${_path}" != "/" -a "${_path}" != "." ] ; do
503		_dirname="$(dirname "${_path}" 2>/dev/null)"
504		_basename="$(basename "${_path}" 2>/dev/null)"
505		ls -fa "${DEST_DIR}${_dirname}" 2> /dev/null \
506			| ${GREP} -F -x "${_basename}" >/dev/null \
507			|| return 1
508		_path="${_dirname}"
509	done
510	return 0
511}
512
513# obsolete_paths op
514#	Obsolete the list of paths provided on stdin.
515#	Each path should start with '/' or './', and
516#	will be interpreted relative to ${DEST_DIR}.
517#
518obsolete_paths()
519{
520	[ -n "$1" ] || err 3 "USAGE: obsolete_paths fix|check"
521	op="$1"
522
523	failed=0
524	while read ofile; do
525		if ! ${file_exists_exact} "${ofile}"; then
526			continue
527		fi
528		ofile="${DEST_DIR}${ofile#.}"
529		cmd="rm"
530		ftype="file"
531		if [ -h "${ofile}" ]; then
532			ftype="link"
533		elif [ -d "${ofile}" ]; then
534			ftype="directory"
535			cmd="rmdir"
536		elif [ ! -e "${ofile}" ]; then
537			continue
538		fi
539		if [ "${op}" = "check" ]; then
540			msg "Remove obsolete ${ftype} ${ofile}"
541			failed=1
542		elif ! eval "${cmd} \"\${ofile}\""; then
543			msg "Can't remove obsolete ${ftype} ${ofile}"
544			failed=1
545		else
546			msg "Removed obsolete ${ftype} ${ofile}"
547		fi
548	done
549	return ${failed}
550}
551
552# obsolete_libs dir
553#	Display the minor/teeny shared libraries in dir that are considered
554#	to be obsolete.
555#
556#	The implementation supports removing obsolete major libraries
557#	if the awk variable AllLibs is set, although there is no way to
558#	enable that in the enclosing shell function as this time.
559#
560obsolete_libs()
561{
562	[ $# -eq 1 ] || err 3 "USAGE: obsolete_libs dir"
563	dir="$1"
564
565	_obsolete_libs "${dir}"
566	_obsolete_libs "/usr/libdata/debug/${dir}"
567}
568
569exclude()
570{
571	local dollar
572	case "$1" in
573	-t)
574		dollar='$'
575		shift
576		;;
577	*)
578		dollar=
579		;;
580	esac
581	if [ -z "$*" ]; then
582		cat
583	else
584		eval ${GREP} -v -E "'(^$(echo $* | \
585		    ${SED} -e s/\\./\\\\./g -e 's/ /'${dollar}'|^/'g)${dollar})'"
586	fi
587}
588
589#
590# find all the target symlinks of shared libraries and exclude them
591# from consideration for removal
592#
593exclude_libs()
594{
595	local target="$(ls -l -d lib*.so.* 2> /dev/null \
596	    | ${AWK} '{ print $11; }' \
597	    | ${SED} -e 's@.*/@@' | ${SORT} -u)"
598	exclude -t ${target}
599}
600
601_obsolete_libs()
602{
603	dir="$1"
604
605	(
606
607	if [ ! -e "${DEST_DIR}/${dir}" ]
608	then
609		return 0
610	fi
611
612	cd "${DEST_DIR}/${dir}" || err 2 "can't cd to ${DEST_DIR}/${dir}"
613	echo lib*.so.* \
614	| tr ' ' '\n' \
615	| ${AWK} -v LibDir="${dir}/" '
616#{
617
618function digit(v, c, n) { return (n <= c) ? v[n] : 0 }
619
620function checklib(results, line, regex) {
621	if (! match(line, regex))
622		return
623	lib = substr(line, RSTART, RLENGTH)
624	rev = substr($0, RLENGTH+1)
625	if (! (lib in results)) {
626		results[lib] = rev
627		return
628	}
629	orevc = split(results[lib], orev, ".")
630	nrevc = split(rev, nrev, ".")
631	maxc = (orevc > nrevc) ? orevc : nrevc
632	for (i = 1; i <= maxc; i++) {
633		res = digit(orev, orevc, i) - digit(nrev, nrevc, i)
634		if (res < 0) {
635			print LibDir lib results[lib]
636			results[lib] = rev
637			return
638		} else if (res > 0) {
639			print LibDir lib rev
640			return
641		}
642	}
643}
644
645/^lib.*\.so\.[0-9]+\.[0-9]+(\.[0-9]+)?(\.debug)?$/ {
646	if (AllLibs)
647		checklib(minor, $0, "^lib.*\\.so\\.")
648	else
649		checklib(found, $0, "^lib.*\\.so\\.[0-9]+\\.")
650}
651
652/^lib.*\.so\.[0-9]+$/ {
653	if (AllLibs)
654		checklib(major, $0, "^lib.*\\.so\\.")
655}
656
657#}' | exclude_libs
658
659	)
660}
661
662# obsolete_stand dir
663#	Prints the names of all obsolete files and subdirs below the
664#	provided dir.  dir should be something like /stand/${MACHINE}.
665#	The input dir and all output paths are interpreted
666#	relative to ${DEST_DIR}.
667#
668#	Assumes that the numerically largest subdir is current, and all
669#	others are obsolete.
670#
671obsolete_stand()
672{
673	[ $# -eq 1 ] || err 3 "USAGE: obsolete_stand dir"
674	local dir="$1"
675	local subdir
676
677	if ! [ -d "${DEST_DIR}${dir}" ]; then
678		msg "${DEST_DIR}${dir} doesn't exist; can't check for obsolete files"
679		return 1
680	fi
681
682	( cd "${DEST_DIR}${dir}" && ls -1d [0-9]*[0-9]/. ) \
683	| ${GREP} -v '[^0-9./]' \
684	| sort -t. -r -n -k1,1 -k2,2 -k3,3 \
685	| tail -n +2 \
686	| while read subdir ; do
687		subdir="${subdir%/.}"
688		find "${DEST_DIR}${dir}/${subdir}" -depth -print
689	done \
690	| unprefix "${DEST_DIR}"
691}
692
693# modify_file op srcfile scratchfile awkprog
694#	Apply awkprog to srcfile sending output to scratchfile, and
695#	if appropriate replace srcfile with scratchfile.
696#
697modify_file()
698{
699	[ $# -eq 4 ] || err 3 "USAGE: modify_file op file scratch awkprog"
700
701	_mfop="$1"
702	_mffile="$2"
703	_mfscratch="$3"
704	_mfprog="$4"
705	_mffailed=0
706
707	${AWK} "${_mfprog}" < "${_mffile}" > "${_mfscratch}"
708	if ! cmp -s "${_mffile}" "${_mfscratch}"; then
709		diff "${_mffile}" "${_mfscratch}" > "${_mfscratch}.diffs"
710		if [ "${_mfop}" = "check" ]; then
711			msg "${_mffile} needs the following changes:"
712			_mffailed=1
713		elif ! rm -f "${_mffile}" ||
714		     ! cp -f "${_mfscratch}" "${_mffile}"; then
715			msg "${_mffile} changes not applied:"
716			_mffailed=1
717		else
718			msg "${_mffile} changes applied:"
719		fi
720		while read _line; do
721			msg "	${_line}"
722		done < "${_mfscratch}.diffs"
723	fi
724	return ${_mffailed}
725}
726
727
728# contents_owner op directory user group
729#	Make sure directory and contents are owned (and group-owned)
730#	as specified.
731#
732contents_owner()
733{
734	[ $# -eq 4 ] || err 3 "USAGE: contents_owner op dir user group"
735
736	_op="$1"
737	_dir="$2"
738	_user="$3"
739	_grp="$4"
740
741	if [ "${_op}" = "check" ]; then
742		_files=$(find "${_dir}" \( \( ! -user "${_user}" \) -o \
743		                \( ! -group "${_grp}" \) \) )
744		_error=$?
745		if [ ! -z "$_files" ] || [ $_error != 0 ]; then
746			msg "${_dir} and contents not all owned by" \
747			    "${_user}:${_grp}"
748			return 1
749		else
750			return 0
751		fi
752	elif [ "${_op}" = "fix" ]; then
753		find "${_dir}" \( \( ! -user "${_user}" \) -o \
754		\( ! -group "${_grp}" \) \) \
755		-exec chown "${_user}:${_grp}" -- {} \;
756	fi
757}
758
759# get_makevar var ...
760#	Retrieve the value of a user-settable system make variable
761get_makevar()
762{
763	$SOURCEMODE || err 3 "get_makevar must be used in source mode"
764	[ $# -eq 0 ] && err 3 "USAGE: get_makevar var ..."
765
766	for _var in "$@"; do
767		_value="$(echo '.include <bsd.own.mk>' | \
768		    ${MAKE} -f - -V "\${${_var}}")"
769
770		eval ${_var}=\"\${_value}\"
771	done
772}
773
774# detect_x11
775#	Detect if X11 components should be analysed and set values of
776#	relevant variables.
777detect_x11()
778{
779	if $SOURCEMODE; then
780		get_makevar MKX11 X11ROOTDIR X11SRCDIR
781	else
782		if [ -f "${SRC_DIR}/etc/mtree/set.xetc" ]; then
783			MKX11=yes
784			X11ROOTDIR=/this/value/isnt/used/yet
785		else
786			MKX11=no
787			X11ROOTDIR=
788		fi
789		X11SRCDIR=/nonexistent/xsrc
790	fi
791}
792
793#
794#	find out where MAKEDEV lives, set MAKEDEV_DIR appropriately
795#
796find_makedev()
797{
798	if [ -e "${DEST_DIR}/dev/MAKEDEV" ]; then
799		MAKEDEV_DIR="${DEST_DIR}/dev"
800	elif [ -e "${DEST_DIR}/etc/MAKEDEV" ]; then
801		MAKEDEV_DIR="${DEST_DIR}/etc"
802	else
803		MAKEDEV_DIR="${DEST_DIR}/dev"
804	fi
805}
806
807
808#
809#	items
810#	-----
811#
812# NOTE: Keep these items sorted, except for obsolete* which are listed last.
813#
814
815#
816#	atf
817#
818
819handle_atf_user()
820{
821	local op="$1"
822	local failed=0
823
824	local conf="${DEST_DIR}/etc/atf/common.conf"
825	if grep '[^#]*unprivileged-user[ \t]*=.*_atf' "${conf}" >/dev/null
826	then
827		if [ "$1" = "fix" ]; then
828			${SED} -e \
829			    "/[^#]*unprivileged-user[\ t]*=/s/_atf/_tests/" \
830			    "${conf}" >"${conf}.new"
831			failed=$(( ${failed} + $? ))
832			mv "${conf}.new" "${conf}"
833			failed=$(( ${failed} + $? ))
834			msg "Set unprivileged-user=_tests in ${conf}"
835		else
836			msg "unprivileged-user=_atf in ${conf} should be" \
837			    "unprivileged-user=_tests"
838			failed=1
839		fi
840	fi
841
842	return ${failed}
843}
844
845additem atf "install missing atf configuration files and validate them"
846do_atf()
847{
848	[ -n "$1" ] || err 3 "USAGE: do_atf fix|check"
849	op="$1"
850	failed=0
851
852	# Ensure atf configuration files are in place.
853	if find_file_in_dirlist NetBSD.conf "NetBSD.conf" \
854	    "${SRC_DIR}/external/bsd/atf/etc/atf" \
855	    "${SRC_DIR}/etc/atf"; then
856			# ${dir} is set by find_file_in_dirlist()
857		populate_dir "${op}" true "${dir}" "${DEST_DIR}/etc/atf" 644 \
858		    NetBSD.conf common.conf || failed=1
859	else
860		failed=1
861	fi
862	if find_file_in_dirlist atf-run.hooks "atf-run.hooks" \
863	    "${SRC_DIR}/external/bsd/atf/dist/tools/sample" \
864	    "${SRC_DIR}/etc/atf"; then
865			# ${dir} is set by find_file_in_dirlist()
866		populate_dir "${op}" true "${dir}" "${DEST_DIR}/etc/atf" 644 \
867		    atf-run.hooks || failed=1
868	else
869		failed=1
870	fi
871
872	# Validate the _atf to _tests user/group renaming.
873	if [ -f "${DEST_DIR}/etc/atf/common.conf" ]; then
874		handle_atf_user "${op}" || failed=1
875	else
876		failed=1
877	fi
878
879	return ${failed}
880}
881
882
883#
884#	autofsconfig
885#
886
887additem autofsconfig "automounter configuration files"
888do_autofsconfig()
889{
890	[ -n "$1" ] || err 3 "USAGE: do_autofsconfig fix|check"
891	local autofs_files="
892include_ldap
893include_nis
894special_hosts
895special_media
896special_noauto
897special_null
898"
899	op="$1"
900	failed=0
901	if [ "$op" = "fix" ]; then
902		mkdir -p "${DEST_DIR}/etc/autofs"
903	fi
904	failed=$(( ${failed} + $? ))
905	populate_dir "$op" true "${SRC_DIR}/etc" \
906	    "${DEST_DIR}/etc" \
907	    644 \
908	    auto_master
909	failed=$(( ${failed} + $? ))
910	populate_dir "$op" true "${SRC_DIR}/etc/autofs" \
911	    "${DEST_DIR}/etc/autofs" \
912	    644 \
913	    ${autofs_files}
914	return ${failed}
915}
916
917
918#
919#	blocklist
920#
921
922fixblock()
923{
924	local op="$1"
925	local target="${DEST_DIR}$2"
926
927	if [ ! -f "${target}" ]; then
928		continue
929	fi
930
931	if ${GREP} '[bB]lack' "${target}" > /dev/null; then
932		if [ "$1" = "check" ]; then
933			msg "Fix old configuration file(s)."
934			return 1
935		else
936			local p=$(${STAT} -f %Lp "${target}")
937			chmod u+w "${target}" || return 1
938			if [ "$2" = "/etc/npf.conf" ]; then
939				${SED} -i -e 's/"blacklistd"/"blocklistd"/g' "${target}"
940			else
941				${SED} -i -e 's/\([bB]\)lacklist/\1locklist/g' "${target}"
942			fi
943			chmod "${p}" "${target}"
944		fi
945	fi
946}
947
948additem blocklist "rename old files to blocklist"
949do_blocklist()
950{
951	[ -n "$1" ] || err 3 "USAGE: do_blocklist fix|check"
952	local op="$1"
953
954	# if we are actually using blocklistd
955	for i in /var/db/blacklist.db /etc/blacklistd.conf; do
956		local old="${DEST_DIR}${i}"
957		if [ ! -f "${old}" ]; then
958			continue
959		elif [ "$1" = "check" ]; then
960			msg "Rename old file(s)."
961			return 1
962		fi
963		local new=$(echo "${old}" | ${SED} s/black/block/)
964		mv "${old}" "${new}" || return 1
965	done
966
967	for i in /etc/rc.conf /etc/npf.conf /etc/blocklistd.conf \
968	    /etc/defaults/rc.conf; do
969		fixblock "${op}" "${i}" || return 1
970	done
971}
972
973
974#
975#	bluetooth
976#
977
978additem bluetooth "Bluetooth configuration is up to date"
979do_bluetooth()
980{
981	[ -n "$1" ] || err 3 "USAGE: do_bluetooth fix|check"
982	op="$1"
983	failed=0
984
985	populate_dir "${op}" true \
986		"${SRC_DIR}/etc/bluetooth" "${DEST_DIR}/etc/bluetooth" 644 \
987		hosts protocols btattach.conf btdevctl.conf
988	failed=$(( ${failed} + $? ))
989
990	move_file "${op}" "${DEST_DIR}/var/db/btdev.xml" \
991			"${DEST_DIR}/var/db/btdevctl.plist"
992	failed=$(( ${failed} + $? ))
993
994	notfixed=""
995	if [ "${op}" = "fix" ]; then
996		notfixed="${NOT_FIXED}"
997	fi
998	for _v in btattach btconfig btdevctl; do
999		if rcvar_is_enabled "${_v}"; then
1000			msg \
1001    "${_v} is obsolete in rc.conf(5)${notfixed}: use bluetooth=YES"
1002			failed=$(( ${failed} + 1 ))
1003		fi
1004	done
1005
1006	return ${failed}
1007}
1008
1009
1010#
1011#	catpages
1012#
1013
1014obsolete_catpages()
1015{
1016	basedir="$2"
1017	section="$3"
1018	mandir="${basedir}/man${section}"
1019	catdir="${basedir}/cat${section}"
1020	test -d "$mandir" || return 0
1021	test -d "$catdir" || return 0
1022	(cd "$mandir" && find . -type f) | {
1023	failed=0
1024	while read manpage; do
1025		manpage="${manpage#./}"
1026		case "$manpage" in
1027		*.Z)
1028			catname="$catdir/${manpage%.*.Z}.0"
1029			;;
1030		*.gz)
1031			catname="$catdir/${manpage%.*.gz}.0"
1032			;;
1033		*)
1034			catname="$catdir/${manpage%.*}.0"
1035			;;
1036		esac
1037		test -e "$catname" -a "$catname" -ot "$mandir/$manpage" || continue
1038		if [ "$1" = "fix" ]; then
1039			rm "$catname"
1040			failed=$(( ${failed} + $? ))
1041			msg "Removed obsolete cat page $catname"
1042		else
1043			msg "Obsolete cat page $catname"
1044			failed=1
1045		fi
1046	done
1047	exit $failed
1048	}
1049}
1050
1051additem catpages "remove outdated cat pages"
1052do_catpages()
1053{
1054	failed=0
1055	for manbase in /usr/share/man /usr/X11R6/man /usr/X11R7/man; do
1056		for sec in 1 2 3 4 5 6 7 8 9; do
1057			obsolete_catpages "$1" "${DEST_DIR}${manbase}" "${sec}"
1058			failed=$(( ${failed} + $? ))
1059			if [ "$1" = "fix" ]; then
1060				rmdir "${DEST_DIR}${manbase}/cat${sec}"/* \
1061					2>/dev/null
1062				rmdir "${DEST_DIR}${manbase}/cat${sec}" \
1063					2>/dev/null
1064			fi
1065		done
1066	done
1067	return $failed
1068}
1069
1070
1071#
1072#	ddbonpanic
1073#
1074
1075additem ddbonpanic "verify ddb.onpanic is configured in sysctl.conf"
1076do_ddbonpanic()
1077{
1078	[ -n "$1" ] || err 3 "USAGE: do_ddbonpanic fix|check"
1079
1080	if ${GREP} -E '^#*[[:space:]]*ddb\.onpanic[[:space:]]*\??=[[:space:]]*[[:digit:]]+' \
1081		"${DEST_DIR}/etc/sysctl.conf" >/dev/null 2>&1
1082	then
1083		result=0
1084	else
1085		if [ "$1" = check ]; then
1086			msg \
1087    "The ddb.onpanic behaviour is not explicitly specified in /etc/sysctl.conf"
1088			result=1
1089		else
1090			echo >> "${DEST_DIR}/etc/sysctl.conf"
1091			${SED} < "${SRC_DIR}/etc/sysctl.conf" \
1092			   -e '/^ddb\.onpanic/q' | \
1093			       ${SED} -e '1,/^$/d' >> \
1094			    "${DEST_DIR}/etc/sysctl.conf"
1095			result=$?
1096		fi
1097	fi
1098	return ${result}
1099}
1100
1101
1102#
1103#	defaults
1104#
1105
1106additem defaults "/etc/defaults/ being up to date"
1107do_defaults()
1108{
1109	[ -n "$1" ] || err 3 "USAGE: do_defaults fix|check"
1110	local op="$1"
1111	local failed=0
1112	local etcsets=$(getetcsets)
1113
1114	local rc_exclude_scripts=""
1115	if $SOURCEMODE; then
1116		# For most architectures rc.conf(5) should be the same as the
1117		# one obtained from a source directory, except for the ones
1118		# that have an append file for it.
1119		local rc_conf_app="${SRC_DIR}/etc/etc.${MACHINE}/rc.conf.append"
1120		if [ -f "${rc_conf_app}" ]; then
1121			rc_exclude_scripts="rc.conf"
1122
1123			# Generate and compare the correct rc.conf(5) file
1124			mkdir "${SCRATCHDIR}/defaults"
1125
1126			cat "${SRC_DIR}/etc/defaults/rc.conf" "${rc_conf_app}" \
1127			    > "${SCRATCHDIR}/defaults/rc.conf"
1128
1129			compare_dir "${op}" "${SCRATCHDIR}/defaults" \
1130			    "${DEST_DIR}/etc/defaults" \
1131			    444 \
1132			    "rc.conf"
1133			failed=$(( ${failed} + $? ))
1134		fi
1135	fi
1136
1137	find_file_in_dirlist pf.boot.conf "pf.boot.conf" \
1138	    "${SRC_DIR}/usr.sbin/pf/etc/defaults" "${SRC_DIR}/etc/defaults" \
1139	    || return 1
1140	# ${dir} is set by find_file_in_dirlist()
1141	compare_dir "$op" "${dir}" "${DEST_DIR}/etc/defaults" 444 pf.boot.conf
1142	failed=$(( ${failed} + $? ))
1143
1144	rc_exclude_scripts="${rc_exclude_scripts} pf.boot.conf"
1145
1146	local rc_default_conf_files="$(select_set_files /etc/defaults/ \
1147	    "/etc/defaults/\([^[:space:]]*\.conf\)" ${etcsets} | \
1148	    exclude ${rc_exclude_scripts})"
1149	compare_dir "$op" "${SRC_DIR}/etc/defaults" "${DEST_DIR}/etc/defaults" \
1150		444 \
1151		${rc_default_conf_files}
1152	failed=$(( ${failed} + $? ))
1153
1154
1155	return ${failed}
1156}
1157
1158
1159#
1160#	dhcpcd
1161#
1162
1163additem dhcpcd "dhcpcd configuration is up to date"
1164do_dhcpcd()
1165{
1166	[ -n "$1" ] || err 3 "USAGE: do_dhcpcd fix|check"
1167	op="$1"
1168	failed=0
1169
1170	find_file_in_dirlist dhcpcd.conf "dhcpcd.conf" \
1171	    "${SRC_DIR}/external/bsd/dhcpcd/dist/src" \
1172	    "${SRC_DIR}/etc" || return 1
1173			# ${dir} is set by find_file_in_dirlist()
1174	populate_dir "$op" true "${dir}" "${DEST_DIR}/etc" 644 dhcpcd.conf
1175	failed=$(( ${failed} + $? ))
1176
1177	check_dir "${op}" "${DEST_DIR}/var/db/dhcpcd" 755
1178	failed=$(( ${failed} + $? ))
1179
1180	move_file "${op}" \
1181		"${DEST_DIR}/etc/dhcpcd.duid" \
1182		"${DEST_DIR}/var/db/dhcpcd/duid"
1183	failed=$(( ${failed} + $? ))
1184
1185	move_file "${op}" \
1186		"${DEST_DIR}/etc/dhcpcd.secret" \
1187		"${DEST_DIR}/var/db/dhcpcd/secret"
1188	failed=$(( ${failed} + $? ))
1189
1190	move_file "${op}" \
1191		"${DEST_DIR}/var/db/dhcpcd-rdm.monotonic" \
1192		"${DEST_DIR}/var/db/dhcpcd/rdm_monotonic"
1193	failed=$(( ${failed} + $? ))
1194
1195	for lease in "${DEST_DIR}/var/db/dhcpcd-"*.lease*; do
1196		[ -f "${lease}" ] || continue
1197		new_lease=$(basename "${lease}" | ${SED} -e 's/dhcpcd-//')
1198		new_lease="${DEST_DIR}/var/db/dhcpcd/${new_lease}"
1199		move_file "${op}" "${lease}" "${new_lease}"
1200		failed=$(( ${failed} + $? ))
1201	done
1202
1203	chroot_dir="${DEST_DIR}/var/chroot/dhcpcd"
1204	move_file "${op}" \
1205		"${chroot_dir}/var/db/dhcpcd/duid" \
1206		"${DEST_DIR}/var/db/dhcpcd/duid"
1207	failed=$(( ${failed} + $? ))
1208
1209	move_file "${op}" \
1210		"${chroot_dir}/var/db/dhcpcd/secret" \
1211		"${DEST_DIR}/var/db/dhcpcd/secret"
1212	failed=$(( ${failed} + $? ))
1213
1214	move_file "${op}" \
1215		"${chroot_dir}/var/db/dhcpcd/rdm_monotonic" \
1216		"${DEST_DIR}/var/db/dhcpcd/rdm_monotonic"
1217	failed=$(( ${failed} + $? ))
1218
1219	for lease in "${chroot_dir}/var/db/dhcpcd/"*.lease*; do
1220		[ -f "${lease}" ] || continue
1221		new_lease="${DEST_DIR}/var/db/dhcpcd/$(basename ${lease})"
1222		move_file "${op}" "${lease}" "${new_lease}"
1223		failed=$(( ${failed} + $? ))
1224	done
1225
1226	# Ensure chroot is now empty
1227	for dir in \
1228		$(find ${chroot_dir} ! -type d) \
1229		$(find ${chroot_dir} -type d -mindepth 1 | sort -r)
1230	do
1231		echo "/var/chroot/dhcpcd${dir##${chroot_dir}}"
1232	done | obsolete_paths "${op}"
1233	failed=$(( ${failed} + $? ))
1234
1235	contents_owner "${op}" "${DEST_DIR}/var/db/dhcpcd" root wheel
1236	failed=$(( ${failed} + $? ))
1237
1238	return ${failed}
1239}
1240
1241
1242#
1243#	dhcpcdrundir
1244#
1245
1246additem dhcpcdrundir "accidentally created /@RUNDIR@ does not exist"
1247do_dhcpcdrundir()
1248{
1249	[ -n "$1" ] || err 3 "USAGE: do_dhcpcdrundir fix|check"
1250	op="$1"
1251	failed=0
1252
1253	if [ -d "${DEST_DIR}/@RUNDIR@" ]; then
1254		if [ "${op}" = "check" ]; then
1255			msg "Remove erroneously created /@RUNDIR@"
1256			failed=1
1257		elif ! rm -r "${DEST_DIR}/@RUNDIR@"; then
1258			msg "Failed to remove ${DEST_DIR}/@RUNDIR@"
1259			failed=1
1260		else
1261			msg "Removed erroneously created ${DEST_DIR}/@RUNDIR@"
1262		fi
1263	fi
1264	return ${failed}
1265}
1266
1267
1268#
1269#	envsys
1270#
1271
1272additem envsys "envsys configuration is up to date"
1273do_envsys()
1274{
1275	[ -n "$1" ] || err 3 "USAGE: do_envsys fix|check"
1276	local op="$1"
1277	local failed=0
1278	local etcsets=$(getetcsets)
1279
1280	populate_dir "$op" true "${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 \
1281		envsys.conf
1282	failed=$(( ${failed} + $? ))
1283
1284	local powerd_scripts="$(select_set_files /etc/powerd/scripts/ \
1285	    "/etc/powerd/scripts/\([^[:space:]/]*\)" ${etcsets})"
1286
1287	populate_dir "$op" true "${SRC_DIR}/etc/powerd/scripts" \
1288		"${DEST_DIR}/etc/powerd/scripts" \
1289		555 \
1290		${powerd_scripts}
1291	failed=$(( ${failed} + $? ))
1292
1293	return ${failed}
1294}
1295
1296
1297#
1298#	fontconfig
1299#
1300
1301additem fontconfig "X11 font configuration is up to date"
1302do_fontconfig()
1303{
1304	[ -n "$1" ] || err 3 "USAGE: do_fontconfig fix|check"
1305	op="$1"
1306	failed=0
1307
1308	# First, check for updates we can handle.
1309	if ! $SOURCEMODE; then
1310		FONTCONFIG_DIR="${SRC_DIR}/etc/fonts/conf.avail"
1311	else
1312		FONTCONFIG_DIR="${XSRC_DIR}/external/mit/fontconfig/dist/conf.d"
1313	fi
1314
1315	if [ ! -d "${FONTCONFIG_DIR}" ]; then
1316		msg "${FONTCONFIG_DIR} is not a directory; skipping check"
1317		return 0
1318	fi
1319	local regular_fonts="
132010-autohint.conf
132110-no-sub-pixel.conf
132210-scale-bitmap-fonts.conf
132310-sub-pixel-bgr.conf
132410-sub-pixel-rgb.conf
132510-sub-pixel-vbgr.conf
132610-sub-pixel-vrgb.conf
132710-unhinted.conf
132811-lcdfilter-default.conf
132911-lcdfilter-legacy.conf
133011-lcdfilter-light.conf
133120-unhint-small-vera.conf
133225-unhint-nonlatin.conf
133330-metric-aliases.conf
133440-nonlatin.conf
133545-generic.conf
133645-latin.conf
133749-sansserif.conf
133850-user.conf
133951-local.conf
134060-generic.conf
134160-latin.conf
134265-fonts-persian.conf
134365-khmer.conf
134465-nonlatin.conf
134569-unifont.conf
134670-no-bitmaps.conf
134770-yes-bitmaps.conf
134880-delicious.conf
134990-synthetic.conf
1350"
1351	populate_dir "$op" false "${FONTCONFIG_DIR}" \
1352	    "${DEST_DIR}/etc/fonts/conf.avail" \
1353	    444 \
1354	    ${regular_fonts}
1355	failed=$(( ${failed} + $? ))
1356
1357	if ! $SOURCEMODE; then
1358		FONTS_DIR="${SRC_DIR}/etc/fonts"
1359	else
1360		FONTS_DIR="${SRC_DIR}/external/mit/xorg/lib/fontconfig/etc"
1361	fi
1362
1363	populate_dir "$op" false "${FONTS_DIR}" "${DEST_DIR}/etc/fonts" 444 \
1364		fonts.conf
1365	failed=$(( ${failed} + $? ))
1366
1367	# We can't modify conf.d easily; someone might have removed a file.
1368
1369	# Look for old files that need to be deleted.
1370	obsolete_fonts="
137110-autohint.conf
137210-no-sub-pixel.conf
137310-sub-pixel-bgr.conf
137410-sub-pixel-rgb.conf
137510-sub-pixel-vbgr.conf
137610-sub-pixel-vrgb.conf
137710-unhinted.conf
137825-unhint-nonlatin.conf
137965-khmer.conf
138070-no-bitmaps.conf
138170-yes-bitmaps.conf
1382"
1383	failed_fonts=""
1384	for i in ${obsolete_fonts}; do
1385	    if [ -f "${DEST_DIR}/etc/fonts/conf.d/$i" ]; then
1386		    conf_d_failed=1
1387		    failed_fonts="$failed_fonts $i"
1388	    fi
1389	done
1390
1391	if [ -n "$failed_fonts" ]; then
1392		msg \
1393    "Broken fontconfig configuration found; please delete these files:"
1394		msg "[$failed_fonts]"
1395		failed=$(( ${failed} + 1 ))
1396	fi
1397
1398	return ${failed}
1399}
1400
1401
1402#
1403#	gid
1404#
1405
1406additem gid "required groups in /etc/group"
1407do_gid()
1408{
1409	[ -n "$1" ] || err 3 "USAGE: do_gid fix|check"
1410
1411	check_ids "$1" groups "${DEST_DIR}/etc/group" \
1412	    "${SRC_DIR}/etc/group" 14 \
1413	    named ntpd sshd SKIP _pflogd _rwhod staff _proxy _timedc \
1414	    _sdpd _httpd _mdnsd _tests _tcpdump _tss _gpio _rtadvd SKIP \
1415	    _unbound _nsd nvmm _dhcpcd
1416}
1417
1418
1419#
1420#	gpio
1421#
1422
1423additem gpio "gpio configuration is up to date"
1424do_gpio()
1425{
1426	[ -n "$1" ] || err 3 "USAGE: do_gpio fix|check"
1427	op="$1"
1428	failed=0
1429
1430	populate_dir "$op" true "${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 \
1431		gpio.conf
1432	failed=$(( ${failed} + $? ))
1433
1434	return ${failed}
1435}
1436
1437
1438#
1439#	hosts
1440#
1441
1442additem hosts "/etc/hosts being up to date"
1443do_hosts()
1444{
1445	[ -n "$1" ] || err 3 "USAGE: do_hosts fix|check"
1446
1447	modify_file "$1" "${DEST_DIR}/etc/hosts" "${SCRATCHDIR}/hosts" '
1448		/^(127\.0\.0\.1|::1)[ 	]+[^\.]*$/ {
1449			print $0, "localhost."
1450			next
1451		}
1452		{ print }
1453	'
1454	return $?
1455}
1456
1457
1458#
1459#	iscsi
1460#
1461
1462additem iscsi "/etc/iscsi is populated"
1463do_iscsi()
1464{
1465	[ -n "$1" ] || err 3 "USAGE: do_iscsi fix|check"
1466
1467	populate_dir "${op}" true \
1468	    "${SRC_DIR}/etc/iscsi" "${DEST_DIR}/etc/iscsi" 600 auths
1469	populate_dir "${op}" true \
1470	    "${SRC_DIR}/etc/iscsi" "${DEST_DIR}/etc/iscsi" 644 targets
1471	return $?
1472}
1473
1474
1475#
1476#	mailerconf
1477#
1478
1479adddisableditem mailerconf "update /etc/mailer.conf after sendmail removal"
1480do_mailerconf()
1481{
1482	[ -n "$1" ] || err 3 "USAGE: do_mailterconf fix|check"
1483	op="$1"
1484
1485	failed=0
1486	mta_path="$(${AWK} '/^sendmail[ \t]/{print$2}' \
1487		"${DEST_DIR}/etc/mailer.conf")"
1488	old_sendmail_path="/usr/libexec/sendmail/sendmail"
1489	if [ "${mta_path}" = "${old_sendmail_path}" ]; then
1490	    if [ "$op" = check ]; then
1491		msg "mailer.conf points to obsolete ${old_sendmail_path}"
1492		failed=1;
1493	    else
1494		populate_dir "${op}" false \
1495		"${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 mailer.conf
1496		failed=$?
1497	    fi
1498	fi
1499
1500	return ${failed}
1501}
1502
1503
1504#
1505#	makedev
1506#
1507
1508additem makedev "/dev/MAKEDEV being up to date"
1509do_makedev()
1510{
1511	[ -n "$1" ] || err 3 "USAGE: do_makedev fix|check"
1512	failed=0
1513
1514	if [ -f "${SRC_DIR}/etc/MAKEDEV.tmpl" ]; then
1515			# generate MAKEDEV from source if source is available
1516		env MACHINE="${MACHINE}" \
1517		    MACHINE_ARCH="${MACHINE_ARCH}" \
1518		    NETBSDSRCDIR="${SRC_DIR}" \
1519		    ${AWK} -f "${SRC_DIR}/etc/MAKEDEV.awk" \
1520		    "${SRC_DIR}/etc/MAKEDEV.tmpl" > "${SCRATCHDIR}/MAKEDEV"
1521	fi
1522
1523	find_file_in_dirlist MAKEDEV "MAKEDEV" \
1524	    "${SCRATCHDIR}" "${SRC_DIR}/dev" \
1525	    || return 1
1526			# ${dir} is set by find_file_in_dirlist()
1527	find_makedev
1528	compare_dir "$1" "${dir}" "${MAKEDEV_DIR}" 555 MAKEDEV
1529	failed=$(( ${failed} + $? ))
1530
1531	find_file_in_dirlist MAKEDEV.local "MAKEDEV.local" \
1532	    "${SRC_DIR}/etc" "${SRC_DIR}/dev" \
1533	    || return 1
1534			# ${dir} is set by find_file_in_dirlist()
1535	compare_dir "$1" "${dir}" "${DEST_DIR}/dev" 555 MAKEDEV.local
1536	failed=$(( ${failed} + $? ))
1537
1538	return ${failed}
1539}
1540
1541
1542#
1543#	man.conf
1544#
1545
1546additem manconf "check for a mandoc usage in /etc/man.conf"
1547do_manconf()
1548{
1549	[ -n "$1" ] || err 3 "USAGE: do_manconf fix|check"
1550	op="$1"
1551	failed=0
1552
1553	[ -f "${DEST_DIR}/etc/man.conf" ] || return 0
1554	if ${GREP} -w "mandoc" "${DEST_DIR}/etc/man.conf" >/dev/null 2>&1;
1555	then
1556		failed=0;
1557	else
1558		failed=1
1559		notfixed=""
1560		if [ "${op}" = "fix" ]; then
1561			notfixed="${NOT_FIXED}"
1562		fi
1563		msg "The file /etc/man.conf has not been adapted to mandoc usage; you"
1564		msg "probably want to copy a new version over. ${notfixed}"
1565	fi
1566
1567	return ${failed}
1568}
1569
1570
1571#
1572#	motd
1573#
1574
1575additem motd "contents of motd"
1576do_motd()
1577{
1578	[ -n "$1" ] || err 3 "USAGE: do_motd fix|check"
1579
1580	if ${GREP} -i 'http://www.NetBSD.org/Misc/send-pr.html' \
1581		"${DEST_DIR}/etc/motd" >/dev/null 2>&1 \
1582	    || ${GREP} -i 'https*://www.NetBSD.org/support/send-pr.html' \
1583		"${DEST_DIR}/etc/motd" >/dev/null 2>&1
1584	then
1585		tmp1="$(mktemp /tmp/postinstall.motd.XXXXXXXX)"
1586		tmp2="$(mktemp /tmp/postinstall.motd.XXXXXXXX)"
1587		${SED} '1,2d' <"${SRC_DIR}/etc/motd" >"${tmp1}"
1588		${SED} '1,2d' <"${DEST_DIR}/etc/motd" >"${tmp2}"
1589
1590		if [ "$1" = check ]; then
1591			cmp -s "${tmp1}" "${tmp2}"
1592			result=$?
1593			if [ "${result}" -ne 0 ]; then
1594				msg \
1595    "Bug reporting messages do not seem to match the installed release"
1596			fi
1597		else
1598			head -n 2 "${DEST_DIR}/etc/motd" >"${tmp1}"
1599			${SED} '1,2d' <"${SRC_DIR}/etc/motd" >>"${tmp1}"
1600			cp "${tmp1}" "${DEST_DIR}/etc/motd"
1601			result=0
1602		fi
1603
1604		rm -f "${tmp1}" "${tmp2}"
1605	else
1606		result=0
1607	fi
1608
1609	return ${result}
1610}
1611
1612
1613#
1614#	mtree
1615#
1616
1617additem mtree "/etc/mtree/ being up to date"
1618do_mtree()
1619{
1620	[ -n "$1" ] || err 3 "USAGE: do_mtree fix|check"
1621	failed=0
1622
1623	compare_dir "$1" "${SRC_DIR}/etc/mtree" "${DEST_DIR}/etc/mtree" 444 special
1624	failed=$(( ${failed} + $? ))
1625
1626	if ! $SOURCEMODE; then
1627		MTREE_DIR="${SRC_DIR}/etc/mtree"
1628	else
1629		/bin/rm -rf "${SCRATCHDIR}/obj"
1630		mkdir "${SCRATCHDIR}/obj"
1631		${MAKE} -s -C "${SRC_DIR}/etc/mtree" TOOL_AWK="${AWK}" \
1632		    MAKEOBJDIR="${SCRATCHDIR}/obj" emit_dist_file > \
1633		    "${SCRATCHDIR}/NetBSD.dist"
1634		MTREE_DIR="${SCRATCHDIR}"
1635		/bin/rm -rf "${SCRATCHDIR}/obj"
1636	fi
1637	compare_dir "$1" "${MTREE_DIR}" "${DEST_DIR}/etc/mtree" 444 NetBSD.dist
1638	failed=$(( ${failed} + $? ))
1639
1640	return ${failed}
1641}
1642
1643
1644#
1645#	named
1646#
1647
1648additem named "named configuration update"
1649do_named()
1650{
1651	[ -n "$1" ] || err 3 "USAGE: do_named fix|check"
1652	op="$1"
1653
1654	move_file "${op}" \
1655		"${DEST_DIR}/etc/namedb/named.conf" \
1656		"${DEST_DIR}/etc/named.conf"
1657
1658	compare_dir "${op}" "${SRC_DIR}/etc/namedb" "${DEST_DIR}/etc/namedb" \
1659		644 \
1660		root.cache
1661}
1662
1663
1664#
1665#	pam
1666#
1667
1668additem pam "/etc/pam.d is populated"
1669do_pam()
1670{
1671	[ -n "$1" ] || err 3 "USAGE: do_pam fix|check"
1672	op="$1"
1673	failed=0
1674
1675	populate_dir "${op}" true "${SRC_DIR}/etc/pam.d" \
1676		"${DEST_DIR}/etc/pam.d" 644 \
1677		README cron display_manager ftpd gdm imap kde login other \
1678		passwd pop3 ppp racoon rexecd rsh sshd su system telnetd \
1679		xdm xserver
1680
1681	failed=$(( ${failed} + $? ))
1682
1683	return ${failed}
1684}
1685
1686
1687#
1688#	periodic
1689#
1690
1691additem periodic "/etc/{daily,weekly,monthly,security} being up to date"
1692do_periodic()
1693{
1694	[ -n "$1" ] || err 3 "USAGE: do_periodic fix|check"
1695
1696	compare_dir "$1" "${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 \
1697		daily weekly monthly security
1698}
1699
1700
1701#
1702#	pf
1703#
1704
1705additem pf "pf configuration being up to date"
1706do_pf()
1707{
1708	[ -n "$1" ] || err 3 "USAGE: do_pf fix|check"
1709	op="$1"
1710	failed=0
1711
1712	find_file_in_dirlist pf.os "pf.os" \
1713	    "${SRC_DIR}/dist/pf/etc" "${SRC_DIR}/etc" \
1714	    || return 1
1715			# ${dir} is set by find_file_in_dirlist()
1716	populate_dir "${op}" true \
1717	    "${dir}" "${DEST_DIR}/etc" 644 \
1718	    pf.conf
1719	failed=$(( ${failed} + $? ))
1720
1721	compare_dir "${op}" "${dir}" "${DEST_DIR}/etc" 444 pf.os
1722	failed=$(( ${failed} + $? ))
1723
1724	return ${failed}
1725}
1726
1727
1728#
1729#	ptyfsoldnodes
1730#
1731
1732additem ptyfsoldnodes "remove legacy device nodes when using ptyfs"
1733do_ptyfsoldnodes()
1734{
1735	[ -n "$1" ] || err 3 "USAGE: do_ptyfsoldnodes fix|check"
1736	_ptyfs_op="$1"
1737
1738	# Check whether ptyfs is in use
1739	failed=0;
1740	if ! ${GREP} -E "^ptyfs" "${DEST_DIR}/etc/fstab" > /dev/null; then
1741		msg "ptyfs is not in use"
1742		return 0
1743	fi
1744
1745	if [ ! -e "${DEST_DIR}/dev/pts" ]; then
1746		msg "ptyfs is not properly configured: missing /dev/pts"
1747		return 1
1748	fi
1749
1750	# Find the device major numbers for the pty master and slave
1751	# devices, by parsing the output from "MAKEDEV -s pty0".
1752	#
1753	# Output from MAKEDEV looks like this:
1754	# ./ttyp0 type=char device=netbsd,5,0 mode=666 gid=0 uid=0
1755	# ./ptyp0 type=char device=netbsd,6,0 mode=666 gid=0 uid=0
1756	#
1757	# Output from awk, used in the eval statement, looks like this:
1758	# maj_ptym=6; maj_ptys=5;
1759	#
1760	find_makedev
1761	eval "$(
1762	    ${HOST_SH} "${MAKEDEV_DIR}/MAKEDEV" -s pty0 2>/dev/null \
1763	    | ${AWK} '\
1764	    BEGIN { before_re = ".*device=[a-zA-Z]*,"; after_re = ",.*"; }
1765	    /ptyp0/ { maj_ptym = gensub(before_re, "", 1, $0);
1766		      maj_ptym = gensub(after_re, "", 1, maj_ptym); }
1767	    /ttyp0/ { maj_ptys = gensub(before_re, "", 1, $0);
1768		      maj_ptys = gensub(after_re, "", 1, maj_ptys); }
1769	    END { print "maj_ptym=" maj_ptym "; maj_ptys=" maj_ptys ";"; }
1770	    '
1771	    )"
1772	#msg "Major numbers are maj_ptym=${maj_ptym} maj_ptys=${maj_ptys}"
1773	if [ -z "$maj_ptym" ] || [ -z "$maj_ptys" ]; then
1774		msg "Cannot find device major numbers for pty master and slave"
1775		return 1
1776	fi
1777
1778	# look for /dev/[pt]ty[p-zP-T][0-9a-zA-Z], and check that they
1779	# have the expected device major numbers.  ttyv* is typically not a
1780	# pty device, but we check it anyway.
1781	#
1782	# The "for d1" loop is intended to avoid overflowing ARG_MAX;
1783	# otherwise we could have used a single glob pattern.
1784	#
1785	# If there are no files that match a particular pattern,
1786	# then stat prints something like:
1787	#    stat: /dev/[pt]tyx?: lstat: No such file or directory
1788	# and we ignore it.  XXX: We also ignore other error messages.
1789	#
1790	_ptyfs_tmp="$(mktemp /tmp/postinstall.ptyfs.XXXXXXXX)"
1791	for d1 in p q r s t u v w x y z P Q R S T; do
1792		${STAT} -f "%Hr %N" "${DEST_DIR}/dev/"[pt]ty${d1}? 2>&1
1793	done \
1794	| while read -r major node ; do
1795		case "$major" in
1796		${maj_ptym}|${maj_ptys}) echo "$node" ;;
1797		esac
1798	done >"${_ptyfs_tmp}"
1799
1800	_desc="legacy device node"
1801	while read node; do
1802		if [ "${_ptyfs_op}" = "check" ]; then
1803			msg "Remove ${_desc} ${node}"
1804			failed=1
1805		else # "fix"
1806			if rm "${node}"; then
1807				msg "Removed ${_desc} ${node}"
1808			else
1809				warn "Failed to remove ${_desc} ${node}"
1810				failed=1
1811			fi
1812		fi
1813	done < "${_ptyfs_tmp}"
1814	rm "${_ptyfs_tmp}"
1815
1816	return ${failed}
1817}
1818
1819
1820#
1821#	pwd_mkdb
1822#
1823
1824additem pwd_mkdb "passwd database version"
1825do_pwd_mkdb()
1826{
1827	[ -n "$1" ] || err 3 "USAGE: do_pwd_mkdb fix|check"
1828	op="$1"
1829	failed=0
1830
1831	# XXX Ideally, we should figure out the endianness of the
1832	# target machine, and add "-E B"/"-E L" to the db(1) flags,
1833	# and "-B"/"-L" to the pwd_mkdb(8) flags if the target is not
1834	# the same as the host machine.  It probably doesn't matter,
1835	# because we don't expect "postinstall fix pwd_mkdb" to be
1836	# invoked during a cross build.
1837
1838	set -- $(${DB} -q -Sb -Ub -To -N hash "${DEST_DIR}/etc/pwd.db" \
1839		'VERSION\0')
1840	case "$2" in
1841	'\001\000\000\000') return 0 ;; # version 1, little-endian
1842	'\000\000\000\001') return 0 ;; # version 1, big-endian
1843	esac
1844
1845	if [ "${op}" = "check" ]; then
1846		msg "Update format of passwd database"
1847		failed=1
1848	elif ! ${PWD_MKDB} -V 1 -d "${DEST_DIR:-/}" \
1849			"${DEST_DIR}/etc/master.passwd";
1850	then
1851		msg "Can't update format of passwd database"
1852		failed=1
1853	else
1854		msg "Updated format of passwd database"
1855	fi
1856
1857	return ${failed}
1858}
1859
1860
1861#
1862#	rc
1863#
1864
1865# There is no info in src/distrib or /etc/mtree which rc* files
1866# can be overwritten unconditionally on upgrade. See PR/54741.
1867rc_644_files="
1868rc
1869rc.subr
1870rc.shutdown
1871"
1872
1873rc_obsolete_vars="
1874amd amd_master
1875btcontrol btcontrol_devices
1876critical_filesystems critical_filesystems_beforenet
1877mountcritlocal mountcritremote
1878network ip6forwarding
1879network nfsiod_flags
1880sdpd sdpd_control
1881sdpd sdpd_groupname
1882sdpd sdpd_username
1883sysctl defcorename
1884"
1885
1886update_rc()
1887{
1888	local op=$1
1889	local dir=$2
1890	local name=$3
1891	local bindir=$4
1892	local rcdir=$5
1893
1894	if [ ! -x "${DEST_DIR}/${bindir}/${name}" ]; then
1895		return 0
1896	fi
1897
1898	if ! find_file_in_dirlist "${name}" "${name}" \
1899	    "${rcdir}" "${SRC_DIR}/etc/rc.d"; then
1900		return 1
1901	fi
1902	populate_dir "${op}" false "${dir}" "${DEST_DIR}/etc/rc.d" 555 "${name}"
1903	return $?
1904}
1905
1906# select non-obsolete files in a sets file
1907# $1: directory pattern
1908# $2: file pattern
1909# $3: filename
1910select_set_files()
1911{
1912	local qdir="$(echo $1 | ${SED} -e s@/@\\\\/@g -e s/\\./\\\\./g)"
1913	${SED} -n -e /obsolete/d \
1914	    -e "/^\.${qdir}/s@^.$2[[:space:]].*@\1@p" $3
1915}
1916
1917# select obsolete files in a sets file
1918# $1: directory pattern
1919# $2: file pattern
1920# $3: setname
1921select_obsolete_files()
1922{
1923	if $SOURCEMODE; then
1924		${SED} -n -e "/obsolete/s@\.$1$2[[:space:]].*@\1@p" \
1925		    "${SRC_DIR}/distrib/sets/lists/$3/mi"
1926		return
1927	fi
1928
1929	# On upgrade builds we don't extract the "etc" set so we
1930	# try to use the source set instead. See PR/54730 for
1931	# ways to better handle this.
1932
1933	local obsolete_dir
1934
1935	if [ $3 = "etc" ] ;then
1936		obsolete_dir="${SRC_DIR}/var/db/obsolete"
1937	else
1938		obsolete_dir="${DEST_DIR}/var/db/obsolete"
1939	fi
1940	${SED} -n -e "s@\.$1$2\$@\1@p" "${obsolete_dir}/$3"
1941}
1942
1943getetcsets()
1944{
1945	if $SOURCEMODE; then
1946		echo "${SRC_DIR}/distrib/sets/lists/etc/mi"
1947	else
1948		echo "${SRC_DIR}/etc/mtree/set.etc"
1949	fi
1950}
1951
1952additem rc "/etc/rc* and /etc/rc.d/ being up to date"
1953do_rc()
1954{
1955	[ -n "$1" ] || err 3 "USAGE: do_rc fix|check"
1956	local op="$1"
1957	local failed=0
1958	local generated_scripts=""
1959	local etcsets=$(getetcsets)
1960	if [ "${MKX11}" != "no" ]; then
1961		generated_scripts="${generated_scripts} xdm xfs"
1962	fi
1963
1964	# Directories of external programs that have rc files (in bsd)
1965	local rc_external_files="blocklist nsd unbound"
1966
1967	# rc* files in /etc/
1968	# XXX: at least rc.conf and rc.local shouldn't be updated. PR/54741
1969	#local rc_644_files="$(select_set_files /etc/rc \
1970	#    "/etc/\(rc[^[:space:]/]*\)" ${etcsets})"
1971
1972	# no-obsolete rc files in /etc/rc.d
1973	local rc_555_files="$(select_set_files /etc/rc.d/ \
1974	    "/etc/rc\.d/\([^[:space:]]*\)" ${etcsets} | \
1975	    exclude ${rc_external_files})"
1976
1977	# obsolete rc file in /etc/rc.d
1978	local rc_obsolete_files="$(select_obsolete_files /etc/rc.d/ \
1979	    "\([^[:space:]]*\)" etc)"
1980
1981	compare_dir "${op}" "${SRC_DIR}/etc" "${DEST_DIR}/etc" 644 \
1982		${rc_644_files}
1983	failed=$(( ${failed} + $? ))
1984
1985	local extra_scripts
1986	if ! $SOURCEMODE; then
1987		extra_scripts="${generated_scripts}"
1988	else
1989		extra_scripts=""
1990	fi
1991
1992	compare_dir "${op}" "${SRC_DIR}/etc/rc.d" "${DEST_DIR}/etc/rc.d" 555 \
1993		${rc_555_files} \
1994		${extra_scripts}
1995	failed=$(( ${failed} + $? ))
1996
1997	for i in ${rc_external_files}; do
1998	    local rc_file
1999	    case $i in
2000	    *d) rc_file=${i};;
2001	    *)	rc_file=${i}d;;
2002	    esac
2003
2004	    update_rc "${op}" "${dir}" ${rc_file} /sbin \
2005		"${SRC_DIR}/external/bsd/$i/etc/rc.d"
2006	    failed=$(( ${failed} + $? ))
2007	done
2008
2009	if $SOURCEMODE && [ -n "${generated_scripts}" ]; then
2010		# generate scripts
2011		mkdir "${SCRATCHDIR}/rc"
2012		for f in ${generated_scripts}; do
2013			${SED} -e "s,@X11ROOTDIR@,${X11ROOTDIR},g" \
2014			    < "${SRC_DIR}/etc/rc.d/${f}.in" \
2015			    > "${SCRATCHDIR}/rc/${f}"
2016		done
2017		compare_dir "${op}" "${SCRATCHDIR}/rc" \
2018		    "${DEST_DIR}/etc/rc.d" 555 \
2019		    ${generated_scripts}
2020		failed=$(( ${failed} + $? ))
2021	fi
2022
2023		# check for obsolete rc.d files
2024	for f in ${rc_obsolete_files}; do
2025		local fd="/etc/rc.d/${f}"
2026		[ -e "${DEST_DIR}${fd}" ] && echo "${fd}"
2027	done | obsolete_paths "${op}"
2028	failed=$(( ${failed} + $? ))
2029
2030		# check for obsolete rc.conf(5) variables
2031	set -- ${rc_obsolete_vars}
2032	while [ $# -gt 1 ]; do
2033		if rcconf_is_set "${op}" "$1" "$2" 1; then
2034			failed=1
2035		fi
2036		shift 2
2037	done
2038
2039	return ${failed}
2040}
2041
2042
2043#
2044#	sendmail
2045#
2046
2047adddisableditem sendmail "remove obsolete sendmail configuration files and scripts"
2048do_sendmail()
2049{
2050	[ -n "$1" ] || err 3 "USAGE: do_sendmail fix|check"
2051	op="$1"
2052	failed=0
2053
2054	# Don't complain if the "sendmail" package is installed because the
2055	# files might still be in use.
2056	if /usr/sbin/pkg_info -qe sendmail >/dev/null 2>&1; then
2057		return 0
2058	fi
2059
2060	for f in /etc/mail/helpfile /etc/mail/local-host-names \
2061	    /etc/mail/sendmail.cf /etc/mail/submit.cf /etc/rc.d/sendmail \
2062	    /etc/rc.d/smmsp /usr/share/misc/sendmail.hf \
2063	    $( ( find "${DEST_DIR}/usr/share/sendmail" -type f ; \
2064	         find "${DEST_DIR}/usr/share/sendmail" -type d \
2065	       ) | unprefix "${DEST_DIR}" ) \
2066	    /var/log/sendmail.st \
2067	    /var/spool/clientmqueue \
2068	    /var/spool/mqueue
2069	do
2070		[ -e "${DEST_DIR}${f}" ] && echo "${f}"
2071	done | obsolete_paths "${op}"
2072	failed=$(( ${failed} + $? ))
2073
2074	return ${failed}
2075}
2076
2077
2078#
2079#	ssh
2080#
2081
2082additem ssh "ssh configuration update"
2083do_ssh()
2084{
2085	[ -n "$1" ] || err 3 "USAGE: do_ssh fix|check"
2086	op="$1"
2087
2088	failed=0
2089	_etcssh="${DEST_DIR}/etc/ssh"
2090	if ! check_dir "${op}" "${_etcssh}" 755; then
2091		failed=1
2092	fi
2093
2094	if [ ${failed} -eq 0 ]; then
2095		for f in \
2096			    ssh_known_hosts ssh_known_hosts2 \
2097			    ssh_host_dsa_key ssh_host_dsa_key.pub \
2098			    ssh_host_rsa_key ssh_host_rsa_key.pub \
2099			    ssh_host_key ssh_host_key.pub \
2100		    ; do
2101			if ! move_file "${op}" \
2102			    "${DEST_DIR}/etc/${f}" "${_etcssh}/${f}" ; then
2103				failed=1
2104			fi
2105		done
2106		for f in sshd.conf ssh.conf ; do
2107				# /etc/ssh/ssh{,d}.conf -> ssh{,d}_config
2108				#
2109			if ! move_file "${op}" \
2110			    "${_etcssh}/${f}" "${_etcssh}/${f%.conf}_config" ;
2111			then
2112				failed=1
2113			fi
2114				# /etc/ssh{,d}.conf -> /etc/ssh/ssh{,d}_config
2115				#
2116			if ! move_file "${op}" \
2117			    "${DEST_DIR}/etc/${f}" \
2118			    "${_etcssh}/${f%.conf}_config" ;
2119			then
2120				failed=1
2121			fi
2122		done
2123	fi
2124
2125	sshdconf=""
2126	for f in \
2127	    "${_etcssh}/sshd_config" \
2128	    "${_etcssh}/sshd.conf" \
2129	    "${DEST_DIR}/etc/sshd.conf" ; do
2130		if [ -f "${f}" ]; then
2131			sshdconf="${f}"
2132			break
2133		fi
2134	done
2135	if [ -n "${sshdconf}" ]; then
2136		modify_file "${op}" "${sshdconf}" "${SCRATCHDIR}/sshdconf" '
2137			/^[^#$]/ {
2138				kw = tolower($1)
2139				if (kw == "hostkey" &&
2140				    $2 ~ /^\/etc\/+ssh_host(_[dr]sa)?_key$/ ) {
2141					sub(/\/etc\/+/, "/etc/ssh/")
2142				}
2143				if (kw == "rhostsauthentication" ||
2144				    kw == "verifyreversemapping" ||
2145				    kw == "reversemappingcheck") {
2146					sub(/^/, "# DEPRECATED:\t")
2147				}
2148			}
2149			{ print }
2150		'
2151		failed=$(( ${failed} + $? ))
2152	fi
2153
2154	if ! find_file_in_dirlist moduli "moduli" \
2155	    "${SRC_DIR}/crypto/external/bsd/openssh/dist" "${SRC_DIR}/etc" ; then
2156		failed=1
2157			# ${dir} is set by find_file_in_dirlist()
2158	elif ! compare_dir "${op}" "${dir}" "${DEST_DIR}/etc" 444 moduli; then
2159		failed=1
2160	fi
2161
2162	if ! check_dir "${op}" "${DEST_DIR}/var/chroot/sshd" 755 ; then
2163		failed=1
2164	fi
2165
2166	if rcconf_is_set "${op}" sshd sshd_conf_dir 1; then
2167		failed=1
2168	fi
2169
2170	return ${failed}
2171}
2172
2173
2174#
2175#	tcpdumpchroot
2176#
2177
2178additem tcpdumpchroot "remove /var/chroot/tcpdump/etc/protocols"
2179do_tcpdumpchroot()
2180{
2181	[ -n "$1" ] || err 3 "USAGE: do_tcpdumpchroot fix|check"
2182
2183	failed=0;
2184	if [ -r "${DEST_DIR}/var/chroot/tcpdump/etc/protocols" ]; then
2185		if [ "$1" = "fix" ]; then
2186			rm "${DEST_DIR}/var/chroot/tcpdump/etc/protocols"
2187			failed=$(( ${failed} + $? ))
2188			rmdir "${DEST_DIR}/var/chroot/tcpdump/etc"
2189			failed=$(( ${failed} + $? ))
2190		else
2191			failed=1
2192		fi
2193	fi
2194	return ${failed}
2195}
2196
2197
2198#
2199#	uid
2200#
2201
2202additem uid "required users in /etc/master.passwd"
2203do_uid()
2204{
2205	[ -n "$1" ] || err 3 "USAGE: do_uid fix|check"
2206
2207	check_ids "$1" users "${DEST_DIR}/etc/master.passwd" \
2208	    "${SRC_DIR}/etc/master.passwd" 12 \
2209	    postfix SKIP named ntpd sshd SKIP _pflogd _rwhod SKIP _proxy \
2210	    _timedc _sdpd _httpd _mdnsd _tests _tcpdump _tss SKIP _rtadvd \
2211	    SKIP _unbound _nsd SKIP _dhcpcd
2212}
2213
2214
2215#
2216#	varrwho
2217#
2218
2219additem varrwho "required ownership of files in /var/rwho"
2220do_varrwho()
2221{
2222	[ -n "$1" ] || err 3 "USAGE: do_varrwho fix|check"
2223
2224	contents_owner "$1" "${DEST_DIR}/var/rwho" _rwhod _rwhod
2225}
2226
2227
2228#
2229#	varshm
2230#
2231
2232additem varshm "check for a tmpfs mounted on /var/shm"
2233do_varshm()
2234{
2235	[ -n "$1" ] || err 3 "USAGE: do_varshm fix|check"
2236	op="$1"
2237	failed=0
2238
2239	[ -f "${DEST_DIR}/etc/fstab" ] || return 0
2240	if ${GREP} -E "^var_shm_symlink" "${DEST_DIR}/etc/rc.conf" >/dev/null 2>&1;
2241	then
2242		failed=0;
2243	elif ${GREP} -w "/var/shm" "${DEST_DIR}/etc/fstab" >/dev/null 2>&1;
2244	then
2245		failed=0;
2246	else
2247		if [ "${op}" = "check" ]; then
2248			failed=1
2249			msg "No /var/shm mount found in ${DEST_DIR}/etc/fstab"
2250		elif [ "${op}" = "fix" ]; then
2251			printf '\ntmpfs\t/var/shm\ttmpfs\trw,-m1777,-sram%%25\n' \
2252				>> "${DEST_DIR}/etc/fstab"
2253			msg "Added tmpfs with 25% ram limit as /var/shm"
2254
2255		fi
2256	fi
2257
2258	return ${failed}
2259}
2260
2261
2262#
2263#	wscons
2264#
2265
2266additem wscons "wscons configuration file update"
2267do_wscons()
2268{
2269	[ -n "$1" ] || err 3 "USAGE: do_wscons fix|check"
2270	op="$1"
2271
2272	[ -f "${DEST_DIR}/etc/wscons.conf" ] || return 0
2273
2274	failed=0
2275	notfixed=""
2276	if [ "${op}" = "fix" ]; then
2277		notfixed="${NOT_FIXED}"
2278	fi
2279	while read _type _arg1 _rest; do
2280		if [ "${_type}" = "mux" -a "${_arg1}" = "1" ]; then
2281			msg \
2282    "Obsolete wscons.conf(5) entry \""${_type} ${_arg1}"\" found.${notfixed}"
2283			failed=1
2284		fi
2285	done < "${DEST_DIR}/etc/wscons.conf"
2286
2287	return ${failed}
2288}
2289
2290
2291#
2292#	x11
2293#
2294
2295additem x11 "x11 configuration update"
2296do_x11()
2297{
2298	[ -n "$1" ] || err 3 "USAGE: do_x11 fix|check"
2299	op="$1"
2300
2301	failed=0
2302	_etcx11="${DEST_DIR}/etc/X11"
2303	_libx11=""
2304	if [ ! -d "${_etcx11}" ]; then
2305		msg "${_etcx11} is not a directory; skipping check"
2306		return 0
2307	fi
2308	if [ -d "${DEST_DIR}/usr/X11R6/." ]
2309	then
2310		_libx11="${DEST_DIR}/usr/X11R6/lib/X11"
2311		if [ ! -d "${_libx11}" ]; then
2312			msg "${_libx11} is not a directory; skipping check"
2313			return 0
2314		fi
2315	fi
2316
2317	_notfixed=""
2318	if [ "${op}" = "fix" ]; then
2319		_notfixed="${NOT_FIXED}"
2320	fi
2321
2322	# check if /usr/X11R6/lib/X11 needs to migrate to /etc/X11
2323	if [ -n "${_libx11}" ]; then
2324		for d in \
2325			    fs lbxproxy proxymngr rstart twm xdm xinit xserver xsm \
2326		    ; do
2327			sd="${_libx11}/${d}"
2328			ld="/etc/X11/${d}"
2329			td="${DEST_DIR}${ld}"
2330			if [ -h "${sd}" ]; then
2331				continue
2332			elif [ -d "${sd}" ]; then
2333				tdfiles="$(find "${td}" \! -type d)"
2334				if [ -n "${tdfiles}" ]; then
2335					msg "${sd} exists yet ${td} already" \
2336					    "contains files${_notfixed}"
2337				else
2338					msg "Migrate ${sd} to ${td}${_notfixed}"
2339				fi
2340				failed=1
2341			elif [ -e "${sd}" ]; then
2342				msg "Unexpected file ${sd}${_notfixed}"
2343				continue
2344			else
2345				continue
2346			fi
2347		done
2348	fi
2349
2350	# check if xdm resources have been updated
2351	if [ -r ${_etcx11}/xdm/Xresources ] && \
2352	    ! ${GREP} 'inpColor:' ${_etcx11}/xdm/Xresources > /dev/null; then
2353		msg "Update ${_etcx11}/xdm/Xresources${_notfixed}"
2354		failed=1
2355	fi
2356
2357	return ${failed}
2358}
2359
2360
2361#
2362#	xkb
2363#
2364# /usr/X11R7/lib/X11/xkb/symbols/pc used to be a directory, but changed
2365# to a file on 2009-06-12.  Fixing this requires removing the directory
2366# (which we can do) and re-extracting the xbase set (which we can't do),
2367# or at least adding that one file (which we may be able to do if X11SRCDIR
2368# is available).
2369#
2370
2371additem xkb "clean up for xkbdata to xkeyboard-config upgrade"
2372do_xkb()
2373{
2374	[ -n "$1" ] || err 3 "USAGE: do_xkb fix|check"
2375	op="$1"
2376	failed=0
2377
2378	pcpath="/usr/X11R7/lib/X11/xkb/symbols/pc"
2379	pcsrcdir="${X11SRCDIR}/external/mit/xkeyboard-config/dist/symbols"
2380
2381	filemsg="\
2382${pcpath} was a directory, should be a file.
2383    To fix, extract the xbase set again."
2384
2385	_notfixed=""
2386	if [ "${op}" = "fix" ]; then
2387		_notfixed="${NOT_FIXED}"
2388	fi
2389
2390	if [ ! -d "${DEST_DIR}${pcpath}" ]; then
2391		return 0
2392	fi
2393
2394	# Delete obsolete files in the directory, and the directory
2395	# itself.  If the directory contains unexpected extra files
2396	# then it will not be deleted.
2397	( [ -f "${DEST_DIR}"/var/db/obsolete/xbase ] \
2398	    &&  ${SORT} -ru "${DEST_DIR}"/var/db/obsolete/xbase \
2399	    | ${GREP} -E "^\\.?${pcpath}/" ;
2400	    echo "${pcpath}" ) \
2401	| obsolete_paths "${op}"
2402	failed=$(( ${failed} + $? ))
2403
2404	# If the directory was removed above, then try to replace it with
2405	# a file.
2406	if [ -d "${DEST_DIR}${pcpath}" ]; then
2407		msg "${filemsg}${_notfixed}"
2408		failed=$(( ${failed} + 1 ))
2409	else
2410		if ! find_file_in_dirlist pc "${pcpath}" \
2411			"${pcsrcdir}" "${SRC_DIR}${pcpath%/*}"
2412		then
2413			msg "${filemsg}${_notfixed}"
2414			failed=$(( ${failed} + 1 ))
2415		else
2416			# ${dir} is set by find_file_in_dirlist()
2417			populate_dir "${op}" true \
2418				"${dir}" "${DEST_DIR}${pcpath%/*}" 444 \
2419				pc
2420			failed=$(( ${failed} + $? ))
2421		fi
2422	fi
2423
2424	return $failed
2425}
2426
2427
2428#
2429#	obsolete_stand
2430#	obsolete_stand_debug
2431#
2432
2433obsolete_stand_internal()
2434{
2435	local prefix="$1"
2436	shift
2437	[ -n "$1" ] || err 3 "USAGE: do_obsolete_stand fix|check"
2438	local op="$1"
2439	local failed=0
2440
2441	for dir in \
2442	    ${prefix}/stand/${MACHINE} \
2443	    ${prefix}/stand/${MACHINE}-4xx \
2444	    ${prefix}/stand/${MACHINE}-booke \
2445	    ${prefix}/stand/${MACHINE}-xen \
2446	    ${prefix}/stand/${MACHINE}pae-xen
2447	do
2448		[ -d "${DEST_DIR}${dir}" ] && obsolete_stand "${dir}"
2449	done | obsolete_paths "${op}"
2450	failed=$(( ${failed} + $? ))
2451
2452	return ${failed}
2453}
2454
2455adddisableditem obsolete_stand "remove obsolete files from /stand"
2456do_obsolete_stand()
2457{
2458	obsolete_stand_internal "" "$@"
2459	return $?
2460}
2461
2462adddisableditem obsolete_stand_debug "remove obsolete files from /usr/libdata/debug/stand"
2463do_obsolete_stand_debug()
2464{
2465	obsolete_stand_internal "/usr/libdata/debug" "$@"
2466	return $?
2467}
2468
2469
2470#
2471#	obsolete
2472#
2473# NOTE: This item is last to allow other items to move obsolete files.
2474#
2475
2476listarchsubdirs()
2477{
2478	if ! $SOURCEMODE; then
2479		echo "@ARCHSUBDIRS@"
2480	else
2481		${SED} -n -e '/ARCHDIR_SUBDIR/s/[[:space:]]//gp' \
2482		    "${SRC_DIR}/compat/archdirs.mk"
2483	fi
2484}
2485
2486getarchsubdirs()
2487{
2488	local m
2489	case ${MACHINE_ARCH} in
2490	*arm*|*aarch64*)	m=arm;;
2491	x86_64)			m=amd64;;
2492	*)			m=${MACHINE_ARCH};;
2493	esac
2494
2495	for i in $(listarchsubdirs); do
2496		echo $i
2497	done | ${SORT} -u | ${SED} -n -e "/=${m}/s@.*=${m}/\(.*\)@\1@p"
2498}
2499
2500getcompatlibdirs()
2501{
2502	for i in $(getarchsubdirs); do
2503		if [ -d "${DEST_DIR}/usr/lib/$i" ]; then
2504			echo /usr/lib/$i
2505		fi
2506	done
2507}
2508
2509additem obsolete "remove obsolete file sets and minor libraries"
2510do_obsolete()
2511{
2512	[ -n "$1" ] || err 3 "USAGE: do_obsolete fix|check"
2513	op="$1"
2514	failed=0
2515
2516	${SORT} -ru "${DEST_DIR}"/var/db/obsolete/* | obsolete_paths "${op}"
2517	failed=$(( ${failed} + $? ))
2518
2519	(
2520		obsolete_libs /lib
2521		obsolete_libs /usr/lib
2522		obsolete_libs /usr/lib/i18n
2523		obsolete_libs /usr/X11R6/lib
2524		obsolete_libs /usr/X11R7/lib
2525		for i in $(getcompatlibdirs); do
2526			obsolete_libs $i
2527		done
2528	) | obsolete_paths "${op}"
2529	failed=$(( ${failed} + $? ))
2530
2531	return ${failed}
2532}
2533
2534
2535#
2536#	end of items
2537#	------------
2538#
2539
2540
2541help()
2542{
2543	cat << _USAGE_
2544Usage: ${PROGNAME} [-a ARCH] [-d DEST_DIR] [-m MACHINE] [-s SRC_ARG] [-x XSRC_DIR] OPERATION ...
2545       ${PROGNAME} -?
2546
2547	Perform post-installation checks and/or fixes on a system's
2548	configuration files.
2549	If no items are provided, a default set of checks or fixes is applied.
2550
2551	Options:
2552	-?		Display this help, and exit.
2553	-a ARCH		Set \$MACHINE_ARCH to ARCH.	[${MACHINE_ARCH}]
2554	-d DEST_DIR	Destination directory to check. [${DEST_DIR:-/}]
2555	-m MACHINE	Set \$MACHINE to MACHINE.	[${MACHINE}]
2556	-s SRC_ARG	Location of the source files.  This may be any of
2557			the following:
2558			-s SRC_DIR	A directory that contains a NetBSD
2559					source tree.
2560			-s TGZ_DIR	A directory in which one or both of
2561					"etc.tgz" and "xetc.tgz" have been
2562					extracted.
2563			-s TGZ_FILE	A distribution set file such as
2564					"etc.tgz" or "xetc.tgz".
2565					May be specified multipled times.
2566							[${SRC_DIR:-/usr/src}]
2567	-x XSRC_DIR	Location of the X11 source files.  This must be
2568			a directory that contains a NetBSD xsrc tree.
2569							[${XSRC_DIR:-/usr/src/../xsrc}]
2570
2571	Supported values for OPERATION:
2572	help		Display this help, and exit.
2573	list		List available items.
2574	check ITEM ...	Perform post-installation checks on ITEMs.
2575	diff [-bcenpuw] ITEM ...
2576			Similar to 'check' but also output difference of files,
2577			using diff with the provided options.
2578	fix ITEM ...	Apply fixes that 'check' determines need to be applied.
2579	usage		Display this help, and exit.
2580_USAGE_
2581}
2582
2583usage()
2584{
2585	help 1>&2
2586	exit 2
2587}
2588
2589
2590list()
2591{
2592	echo "Default set of items (to apply if no items are provided by user):"
2593	echo " Item                 Description"
2594	echo " ----                 -----------"
2595	for i in ${defaultitems}; do
2596		eval desc=\"\${desc_${i}}\"
2597		printf " %-20s %s\n" "${i}" "${desc}"
2598	done
2599	echo "Items disabled by default (must be requested explicitly):"
2600	echo " Item                 Description"
2601	echo " ----                 -----------"
2602	for i in ${otheritems}; do
2603		eval desc=\"\${desc_${i}}\"
2604		printf " %-20s %s\n" "${i}" "${desc}"
2605	done
2606}
2607
2608
2609main()
2610{
2611	DIRMODE=false		# true if "-s" specified a directory
2612	ITEMS=			# items to check|diff|fix. [${defaultitems}]
2613	N_SRC_ARGS=0		# number of "-s" args in SRC_ARGLIST
2614	SOURCEMODE=false	# true if "-s" specified a source directory
2615	SRC_ARGLIST=		# quoted list of one or more "-s" args
2616	SRC_DIR="${SRC_ARG}"	# set default value for early usage()
2617	TGZLIST=		# quoted list list of tgz files
2618	TGZMODE=false		# true if "-s" specifies a tgz file
2619	XSRC_DIR="${SRC_ARG}/../xsrc"
2620	XSRC_DIR_FIX=
2621
2622	case "$(uname -s)" in
2623	Darwin)
2624		# case sensitive match for case insensitive fs
2625		file_exists_exact=file_exists_exact
2626		;;
2627	*)
2628		file_exists_exact=:
2629		;;
2630	esac
2631
2632		# Validate options.
2633		#
2634	while getopts :a:d:m:s:x: ch; do
2635		case "${ch}" in
2636		a)
2637			MACHINE_ARCH="${OPTARG}"
2638			;;
2639		d)
2640			DEST_DIR="${OPTARG}"
2641			;;
2642		m)
2643			MACHINE="${OPTARG}"
2644			;;
2645		s)
2646			qarg="$(shell_quote "${OPTARG}")"
2647			N_SRC_ARGS=$(( $N_SRC_ARGS + 1 ))
2648			SRC_ARGLIST="${SRC_ARGLIST}${SRC_ARGLIST:+ }-s ${qarg}"
2649			if [ -f "${OPTARG}" ]; then
2650				# arg refers to a *.tgz file.
2651				# This may happen twice, for both
2652				# etc.tgz and xetc.tgz, so we build up a
2653				# quoted list in TGZLIST.
2654				TGZMODE=true
2655				TGZLIST="${TGZLIST}${TGZLIST:+ }${qarg}"
2656				# Note that, when TGZMODE is true,
2657				# SRC_ARG is used only for printing
2658				# human-readable messages.
2659				SRC_ARG="${TGZLIST}"
2660			elif [ -d "${OPTARG}" ]; then
2661				# arg refers to a directory.
2662				# It might be a source directory, or a
2663				# directory where the sets have already
2664				# been extracted.
2665				DIRMODE=true
2666				SRC_ARG="${OPTARG}"
2667				if [ -f "${OPTARG}/etc/Makefile" ]; then
2668					SOURCEMODE=true
2669				fi
2670			else
2671				err 2 "Invalid argument for -s option"
2672			fi
2673			;;
2674		x)
2675			if [ -d "${OPTARG}" ]; then
2676				# arg refers to a directory.
2677				XSRC_DIR="${OPTARG}"
2678				XSRC_DIR_FIX="-x ${OPTARG} "
2679			else
2680				err 2 "Not a directory for -x option"
2681			fi
2682			;;
2683		"?")
2684			if [ "${OPTARG}" = "?" ]; then
2685				help
2686				return	# no further processing or validation
2687			fi
2688			warn "Unknown option -${OPTARG}"
2689			usage
2690			;;
2691
2692		:)
2693			warn "Missing argument for option -${OPTARG}"
2694			usage
2695			;;
2696
2697		*)
2698			err 3 "Unimplemented option -${ch}"
2699			;;
2700		esac
2701	done
2702	shift $((${OPTIND} - 1))
2703	if [ $# -eq 0 ] ; then
2704		warn "Missing operation"
2705		usage
2706	fi
2707	op="$1"
2708	shift
2709
2710	if [ "$N_SRC_ARGS" -gt 1 ] && $DIRMODE; then
2711		err 2 "Multiple -s args are allowed only with tgz files"
2712	fi
2713	if [ "$N_SRC_ARGS" -eq 0 ]; then
2714		# The default SRC_ARG was set elsewhere
2715		DIRMODE=true
2716		SOURCEMODE=true
2717		SRC_ARGLIST="-s $(shell_quote "${SRC_ARG}")"
2718	fi
2719
2720		# Validate 'diff' first, as it becomes 'check'
2721		#
2722	case "${op}" in
2723
2724	diff)
2725		op=check
2726		DIFF_STYLE=n			# default style is RCS
2727		OPTIND=1
2728		while getopts :bcenpuw ch; do
2729			case "${ch}" in
2730			c|e|n|u)
2731				if [ "${DIFF_STYLE}" != "n" -a \
2732				    "${DIFF_STYLE}" != "${ch}" ]; then
2733					warn "diff: conflicting output style: -${ch}"
2734					usage
2735				fi
2736				DIFF_STYLE="${ch}"
2737				;;
2738			b|p|w)
2739				DIFF_OPT="${DIFF_OPT} -${ch}"
2740				;;
2741			"?")
2742				# NOTE: not supporting diff -?
2743				warn "diff: Unknown option -${OPTARG}"
2744				usage
2745				;;
2746			:)
2747				warn "diff: Missing argument for option -${OPTARG}"
2748				usage
2749				;;
2750			*)
2751				err 3 "diff: Unimplemented option -${ch}"
2752				;;
2753			esac
2754		done
2755		shift $((${OPTIND} - 1))
2756		;;
2757
2758	esac
2759
2760		# Validate operation and items.
2761		#
2762	case "${op}" in
2763
2764	check|fix)
2765		ITEMS="$*"
2766		: ${ITEMS:="${defaultitems}"}
2767
2768		# ensure that all supplied items are valid
2769		#
2770		for i in ${ITEMS}; do
2771			eval desc=\"\${desc_${i}}\"
2772			[ -n "${desc}" ] || err 2 "Unsupported ${op} '"${i}"'"
2773		done
2774		;;
2775
2776	help|usage)
2777		help
2778		return	# no further processing or validation
2779		;;
2780
2781	list)
2782		# processed below
2783		;;
2784
2785	*)
2786		warn "Unknown operation '"${op}"'"
2787		usage
2788		;;
2789
2790	esac
2791
2792	#
2793	# If '-s' arg or args specified tgz files, extract them
2794	# to a scratch directory.
2795	#
2796	if $TGZMODE; then
2797		ETCTGZDIR="${SCRATCHDIR}/etc.tgz"
2798		echo "Note: Creating temporary directory ${ETCTGZDIR}"
2799		if ! mkdir "${ETCTGZDIR}"; then
2800			err 2 "Can't create ${ETCTGZDIR}"
2801		fi
2802		( # subshell to localise changes to "$@"
2803			eval "set -- ${TGZLIST}"
2804			for tgz in "$@"; do
2805				echo "Note: Extracting files from ${tgz}"
2806				cat "${tgz}" | (
2807					cd "${ETCTGZDIR}" &&
2808					tar -zxf -
2809				) || err 2 "Can't extract ${tgz}"
2810			done
2811		)
2812		SRC_DIR="${ETCTGZDIR}"
2813	else
2814		SRC_DIR="${SRC_ARG}"
2815	fi
2816
2817	[ -d "${SRC_DIR}" ]	|| err 2 "${SRC_DIR} is not a directory"
2818	[ -d "${DEST_DIR}" ]	|| err 2 "${DEST_DIR} is not a directory"
2819	[ -n "${MACHINE}" ]	|| err 2 "\${MACHINE} is not defined"
2820	[ -n "${MACHINE_ARCH}" ] || err 2 "\${MACHINE_ARCH} is not defined"
2821	if ! $SOURCEMODE && ! [ -f "${SRC_DIR}/etc/mtree/set.etc" ]; then
2822		err 2 "Files from the etc.tgz set are missing"
2823	fi
2824
2825		# If directories are /, clear them, so various messages
2826		# don't have leading "//".   However, this requires
2827		# the use of ${foo:-/} to display the variables.
2828		#
2829	[ "${SRC_DIR}" = "/" ]	&& SRC_DIR=""
2830	[ "${DEST_DIR}" = "/" ]	&& DEST_DIR=""
2831
2832	detect_x11
2833
2834		# Perform operation.
2835		#
2836	case "${op}" in
2837
2838	check|fix)
2839		[ -n "${ITEMS}" ] || err 2 "${op}: missing items"
2840
2841		echo "Source directory: ${SRC_DIR:-/}"
2842		if $TGZMODE; then
2843			echo " (extracted from: ${SRC_ARG})"
2844		fi
2845		echo "Target directory: ${DEST_DIR:-/}"
2846		items_passed=
2847		items_failed=
2848		for i in ${ITEMS}; do
2849			echo "${i} ${op}:"
2850			( eval do_${i} ${op} )
2851			if [ $? -eq 0 ]; then
2852				items_passed="${items_passed} ${i}"
2853			else
2854				items_failed="${items_failed} ${i}"
2855			fi
2856		done
2857
2858		if [ "${op}" = "check" ]; then
2859			plural="checks"
2860		else
2861			plural="fixes"
2862		fi
2863
2864		echo "${PROGNAME} ${plural} passed:${items_passed}"
2865		echo "${PROGNAME} ${plural} failed:${items_failed}"
2866		if [ -n "${items_failed}" ]; then
2867		    exitstatus=1;
2868		    if [ "${op}" = "check" ]; then
2869			[ "$MACHINE" = "$(uname -m)" ] && m= || m=" -m $MACHINE"
2870			cat <<_Fix_me_
2871To fix, run:
2872    ${HOST_SH} ${0} ${SRC_ARGLIST} ${XSRC_DIR_FIX}-d ${DEST_DIR:-/}$m fix${items_failed}
2873Note that this may overwrite local changes.
2874_Fix_me_
2875		    fi
2876		fi
2877		;;
2878
2879	list)
2880		echo "Source directory: ${SRC_DIR:-/}"
2881		echo "Target directory: ${DEST_DIR:-/}"
2882		if $TGZMODE; then
2883			echo " (extracted from: ${SRC_ARG})"
2884		fi
2885		list
2886		;;
2887
2888	*)
2889			# diff, help, usage handled during operation validation
2890		err 3 "Unimplemented operation '"${op}"'"
2891		;;
2892
2893	esac
2894}
2895
2896if [ -n "$POSTINSTALL_FUNCTION" ]; then
2897	eval "$POSTINSTALL_FUNCTION"
2898	exit 0
2899fi
2900
2901# defaults
2902#
2903PROGNAME="${0##*/}"
2904SRC_ARG="/usr/src"
2905DEST_DIR="/"
2906: ${MACHINE:="$( uname -m )"}	# assume native build if $MACHINE is not set
2907: ${MACHINE_ARCH:="$( uname -p )"}# assume native build if not set
2908
2909DIFF_STYLE=
2910DIFF_OPT=
2911NOT_FIXED=" (FIX MANUALLY)"
2912SCRATCHDIR="$( mkdtemp )" || err 2 "Can't create scratch directory"
2913trap "/bin/rm -rf \"\${SCRATCHDIR}\" ; exit 0" 1 2 3 15	# HUP INT QUIT TERM
2914
2915umask 022
2916exec 3>/dev/null
2917exec 4>/dev/null
2918exitstatus=0
2919
2920main "$@"
2921/bin/rm -rf "${SCRATCHDIR}"
2922exit $exitstatus
2923