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