xref: /openbsd-src/usr.sbin/sysmerge/sysmerge.sh (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1#!/bin/ksh -
2#
3# $OpenBSD: sysmerge.sh,v 1.142 2014/07/18 10:43:29 ajacoutot Exp $
4#
5# Copyright (c) 2008-2014 Antoine Jacoutot <ajacoutot@openbsd.org>
6# Copyright (c) 1998-2003 Douglas Barton <DougB@FreeBSD.org>
7#
8# Permission to use, copy, modify, and distribute this software for any
9# purpose with or without fee is hereby granted, provided that the above
10# copyright notice and this permission notice appear in all copies.
11#
12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19#
20
21umask 0022
22
23unset AUTO_INSTALLED_FILES BATCHMODE DIFFMODE EGSUM ETCSUM NEED_NEWALIASES
24unset NEWGRP NEWUSR NEED_REBOOT NOSIGCHECK SRCDIR SRCSUM TGZ XETCSUM XTGZ
25
26# forced variables
27WRKDIR=$(mktemp -d -p ${TMPDIR:=/var/tmp} sysmerge.XXXXXXXXXX) || exit 1
28SWIDTH=$(stty size | awk '{w=$2} END {if (w==0) {w=80} print w}')
29RELINT=$(uname -r | tr -d '.')
30if [ -z "${VISUAL}" ]; then
31	EDIT="${EDITOR:=/usr/bin/vi}"
32else
33	EDIT="${VISUAL}"
34fi
35
36# sysmerge specific variables (overridable)
37MERGE_CMD="${MERGE_CMD:=sdiff -as -w ${SWIDTH} -o}"
38REPORT="${REPORT:=${WRKDIR}/sysmerge.log}"
39DBDIR="${DBDIR:=/usr/share/sysmerge}"
40
41# system-wide variables (overridable)
42PAGER="${PAGER:=/usr/bin/more}"
43
44# clean leftovers created by make in src
45clean_src() {
46	[[ -n ${SRCDIR} ]] && \
47		cd ${SRCDIR}/gnu/usr.sbin/sendmail/cf/cf && make cleandir >/dev/null
48}
49
50# restore sum files from backups or remove the newly generated ones if
51# they did not exist
52restore_sum() {
53	local i _i
54	for i in ${DESTDIR}/${DBDIR}/.{${SRCSUM},${ETCSUM},${XETCSUM},${EGSUM}}.bak; do
55		_i=$(basename ${i} .bak)
56		if [ -f "${i}" ]; then
57			mv ${i} ${DESTDIR}/${DBDIR}/${_i#.}
58		elif [ -f "${DESTDIR}/${DBDIR}/${_i#.}" ]; then
59			rm ${DESTDIR}/${DBDIR}/${_i#.}
60		fi
61	done
62	rm -f ${WRKDIR}/*sum
63}
64
65usage() {
66	echo "usage: ${0##*/} [-bdS] [-s [src | etcXX.tgz]] [-x xetcXX.tgz]" >&2
67}
68
69warn() {
70	echo "**** WARNING: $@"
71}
72
73report() {
74	echo "$@" >> ${REPORT}
75}
76
77# remove newly created work directory and exit with status 1
78error_rm_wrkdir() {
79	(($#)) && echo "**** ERROR: $@"
80	restore_sum
81	clean_src
82	# do not remove the entire WRKDIR in case sysmerge stopped half
83	# way since it contains our backup files
84	rm -rf ${TEMPROOT}
85	rm -f ${WRKDIR}/*.tgz
86	rm -f ${WRKDIR}/SHA256.sig
87	rmdir ${WRKDIR} 2>/dev/null
88	exit 1
89}
90
91trap "error_rm_wrkdir; exit 1" 1 2 3 13 15
92
93if (($(id -u) != 0)); then
94	usage
95	error_rm_wrkdir "need root privileges"
96fi
97
98# extract (x)etcXX.tgz and create cksum file(s);
99# stores sum filename in ETCSUM or XETCSUM (see eval);
100extract_sets() {
101	[[ -n ${SRCDIR} ]] && return
102	local _e _x _set _tgz
103
104	[[ -f ${WRKDIR}/${TGZ##*/} ]] && _e=etc
105	[[ -f ${WRKDIR}/${XTGZ##*/} ]] && _x=xetc
106
107	for _set in ${_e} ${_x}; do
108		typeset -u _SETSUM=${_set}sum
109		eval ${_SETSUM}=${_set}sum
110		[[ ${_set} == etc ]] && _tgz=${WRKDIR}/${TGZ##*/}
111		[[ ${_set} == xetc ]] && _tgz=${WRKDIR}/${XTGZ##*/}
112
113		tar -tzf "${_tgz}" .${DBDIR}/${_set}sum >/dev/null ||
114			error_rm_wrkdir "${_tgz##*/}: badly formed \"${_set}\" set, lacks .${DBDIR}/${_set}sum"
115
116		(cd ${TEMPROOT} && tar -xzphf "${_tgz}" && \
117			find . -type f | xargs sha256 -h ${WRKDIR}/${_set}sum) || \
118			error_rm_wrkdir "failed to extract ${_tgz} and create checksum file"
119		rm "${_tgz}"
120	done
121}
122
123# fetch and verify sets, abort on failure
124sm_fetch_and_verify() {
125	[[ -n ${SRCDIR} ]] && return
126	local _file _sigdone _url;
127	local _key="/etc/signify/openbsd-${RELINT}-base.pub"
128
129	for _url in ${TGZ} ${XTGZ}; do
130		[[ -f ${_url} ]] && _url="file://$(readlink -f ${_url})"
131		_file=${WRKDIR}/${_url##*/}
132		[[ ${_url} == @(file|ftp|http|https)://*/*[!/] ]] ||
133			error_rm_wrkdir "${_url}: invalid URL"
134		echo "===> Fetching ${_url}"
135		/usr/bin/ftp -Vm -k "${FTP_KEEPALIVE-0}" -o "${_file}" "${_url}" >/dev/null || \
136			error_rm_wrkdir "could not retrieve ${_url##*/}"
137		if [ -z "${NOSIGCHECK}" ]; then
138			if [ -z ${_sigdone} ]; then
139				echo "===> Fetching ${_url%/*}/SHA256.sig"
140				/usr/bin/ftp -Vm -k "${FTP_KEEPALIVE-0}" -o "${WRKDIR}/SHA256.sig" "${_url%/*}/SHA256.sig" >/dev/null || \
141					error_rm_wrkdir "could not retrieve SHA256.sig"
142				[[ ${TGZ%/*} == ${XTGZ%/*} ]] && _sigdone=1
143			fi
144			echo "===> Verifying ${_url##*/} against ${_key}"
145			(cd ${WRKDIR} && /usr/bin/signify -qC -p ${_key} -x SHA256.sig ${_url##*/}) || \
146				error_rm_wrkdir "${_url##*/}: signature/checksum failed"
147		fi
148	done
149
150	[[ -z ${NOSIGCHECK} ]] && rm ${WRKDIR}/SHA256.sig
151}
152
153# prepare TEMPROOT content from a src dir and create cksum file
154prepare_src() {
155	[[ -z ${SRCDIR} ]] && return
156	SRCSUM=srcsum
157	# 2>/dev/null: distribution-etc-root-var complains /var/tmp is world writable
158	(cd ${SRCDIR}/etc && \
159	 make DESTDIR=${TEMPROOT} distribution-etc-root-var >/dev/null 2>&1 && \
160	 cd ${TEMPROOT} && find . -type f | xargs sha256 -h ${WRKDIR}/${SRCSUM}) || \
161		error_rm_wrkdir "failed to populate from ${SRCDIR} and create checksum file"
162}
163
164sm_populate() {
165	local cf i _array _d _r _D _R CF_DIFF CF_FILES CURSUM IGNORE_FILES
166	echo "===> Populating temporary root under ${TEMPROOT}"
167	mkdir -p ${TEMPROOT}
168
169	if [ ! -d ${DESTDIR}/${DBDIR} ]; then
170		mkdir -p ${DESTDIR}/${DBDIR} || exit 1
171	fi
172
173	# automatically install missing user(s) and group(s) from the
174	# new master.passwd and group files:
175	# - after extracting the sets (so we have the new files)
176	# - before running distribution-etc-root-var (using files from SRCDIR)
177	extract_sets
178	install_user_group
179	prepare_src
180
181	for i in ${SRCSUM} ${ETCSUM} ${XETCSUM}; do
182		if [ -f ${DESTDIR}/${DBDIR}/${i} ]; then
183			# delete file in temproot if it has not changed since last release
184			# and is present in current installation
185			if [ -z "${DIFFMODE}" ]; then
186				# 2>/dev/null: if file got removed manually but is still in the sum file
187				_R=$(cd ${TEMPROOT} && \
188					sha256 -c ${DESTDIR}/${DBDIR}/${i} 2>/dev/null | awk '/OK/ { print $2 }' | sed 's/[:]//')
189				for _r in ${_R}; do
190					if [ -f ${DESTDIR}/${_r} -a -f ${TEMPROOT}/${_r} ]; then
191						rm -f ${TEMPROOT}/${_r}
192					fi
193				done
194			fi
195
196			# set auto-upgradable files
197			_D=$(diff -u ${WRKDIR}/${i} ${DESTDIR}/${DBDIR}/${i} | grep -E '^\+' | sed '1d' | awk '{print $3}')
198			for _d in ${_D}; do
199				# 2>/dev/null: if file got removed manually but is still in the sum file
200				CURSUM=$(cd ${DESTDIR:=/} && sha256 ${_d} 2>/dev/null)
201				[ -n "$(grep "${CURSUM}" ${DESTDIR}/${DBDIR}/${i})" -a -z "$(grep "${CURSUM}" ${WRKDIR}/${i})" ] && \
202					_array="${_array} ${_d}"
203			done
204			[[ -n ${_array} ]] && set -A AUTO_UPG -- ${_array}
205
206			mv ${DESTDIR}/${DBDIR}/${i} ${DESTDIR}/${DBDIR}/.${i}.bak
207		fi
208		mv ${WRKDIR}/${i} ${DESTDIR}/${DBDIR}/${i}
209	done
210
211	# files we don't want/need to deal with
212	IGNORE_FILES="/etc/*.db
213		      /etc/group
214		      /etc/localtime
215		      /etc/mail/*.db
216		      /etc/master.passwd
217		      /etc/passwd
218		      /etc/motd
219		      /etc/myname
220		      /usr/share/sysmerge/{etc,examples,xetc}sum
221		      /var/db/locate.database
222		      /var/mail/root"
223	CF_FILES="/etc/mail/localhost.cf /etc/mail/sendmail.cf /etc/mail/submit.cf"
224	for cf in ${CF_FILES}; do
225		CF_DIFF=$(diff -q -I "##### " ${TEMPROOT}/${cf} ${DESTDIR}/${cf} 2>/dev/null)
226		[[ -z ${CF_DIFF} ]] && IGNORE_FILES="${IGNORE_FILES} ${cf}"
227	done
228	if [ -r /etc/sysmerge.ignore ]; then
229		while read i; do \
230			IGNORE_FILES="${IGNORE_FILES} $(echo ${i} | sed -e 's,\.\.,,g' -e 's,#.*,,g')"
231		done < /etc/sysmerge.ignore
232	fi
233	for i in ${IGNORE_FILES}; do
234		rm -rf ${TEMPROOT}/${i};
235	done
236}
237
238install_and_rm() {
239	if [ -f "${5}/${4##*/}" ]; then
240		mkdir -p ${BKPDIR}/${4%/*}
241		cp ${5}/${4##*/} ${BKPDIR}/${4%/*}
242	fi
243
244	if ! install -m "${1}" -o "${2}" -g "${3}" "${4}" "${5}"; then
245		rm -f ${BKPDIR}/${4%/*}/${4##*/}
246		return 1
247	fi
248	rm -f "${4}"
249}
250
251install_file() {
252	local DIR_MODE FILE_MODE FILE_OWN FILE_GRP INSTDIR
253	INSTDIR=${1#.}
254	INSTDIR=${INSTDIR%/*}
255
256	[[ -z ${INSTDIR} ]] && INSTDIR=/
257
258	DIR_MODE=$(stat -f "%OMp%OLp" "${TEMPROOT}/${INSTDIR}")
259	eval $(stat -f "FILE_MODE=%OMp%OLp FILE_OWN=%Su FILE_GRP=%Sg" ${1})
260
261	[ -n "${DESTDIR}${INSTDIR}" -a ! -d "${DESTDIR}${INSTDIR}" ] && \
262		install -d -o root -g wheel -m "${DIR_MODE}" "${DESTDIR}${INSTDIR}"
263
264	install_and_rm "${FILE_MODE}" "${FILE_OWN}" "${FILE_GRP}" "${1}" "${DESTDIR}${INSTDIR}" || return
265
266	case "${1#.}" in
267	/dev/MAKEDEV)
268		echo " (running MAKEDEV(8))"
269		(cd ${DESTDIR}/dev && /bin/sh MAKEDEV all)
270		export NEED_REBOOT=1
271		;;
272	/etc/login.conf)
273		if [ -f ${DESTDIR}/etc/login.conf.db ]; then
274			echo " (running cap_mkdb(1))"
275			cap_mkdb ${DESTDIR}/etc/login.conf
276		else
277			echo ""
278		fi
279		export NEED_REBOOT=1
280		;;
281	/etc/mail/@(access|genericstable|mailertable|virtusertable))
282		echo " (running makemap(8))"
283		/usr/libexec/sendmail/makemap hash ${DESTDIR}/${1#.} < ${DESTDIR}/${1#.}
284		;;
285	/etc/mail/aliases)
286		echo " (running newaliases(8))"
287		${DESTDIR:+chroot ${DESTDIR}} newaliases >/dev/null || export NEED_NEWALIASES=1
288		;;
289	*)
290		echo ""
291		;;
292	esac
293}
294
295install_link() {
296	local _LINKF _LINKT DIR_MODE
297	_LINKT=$(readlink ${COMPFILE})
298	_LINKF=$(dirname ${DESTDIR}${COMPFILE#.})
299
300	DIR_MODE=$(stat -f "%OMp%OLp" "${TEMPROOT}/${_LINKF}")
301	[[ ! -d ${_LINKF} ]] && \
302		install -d -o root -g wheel -m "${DIR_MODE}" "${_LINKF}"
303
304	rm -f ${COMPFILE}
305	(cd ${_LINKF} && ln -sf ${_LINKT} .)
306	return
307}
308
309install_user_group() {
310	local _g _gid _u
311	if [ -n "${SRCDIR}" ]; then
312		local _pw="${SRCDIR}/etc/master.passwd"
313		local _gr="${SRCDIR}/etc/group"
314	else
315		local _pw="${TEMPROOT}/etc/master.passwd"
316		local _gr="${TEMPROOT}/etc/group"
317	fi
318
319	# when running with '-x' only
320	[ ! -f ${_pw} -o ! -f ${_gr} ] && return
321
322	while read l; do
323		_u=$(echo ${l} | awk -F ':' '{ print $1 }')
324		if [ "${_u}" != "root" ]; then
325			if [ -z "$(grep -E "^${_u}:" ${DESTDIR}/etc/master.passwd)" ]; then
326				echo "===> Adding the ${_u} user"
327				if ${DESTDIR:+chroot ${DESTDIR}} chpass -la "${l}"; then
328					set -A NEWUSR -- ${NEWUSR[@]} ${_u}
329				fi
330			fi
331		fi
332	done < ${_pw}
333
334	while read l; do
335		_g=$(echo ${l} | awk -F ':' '{ print $1 }')
336		_gid=$(echo ${l} | awk -F ':' '{ print $3 }')
337		if [ -z "$(grep -E "^${_g}:" ${DESTDIR}/etc/group)" ]; then
338			echo "===> Adding the ${_g} group"
339			if ${DESTDIR:+chroot ${DESTDIR}} groupadd -g "${_gid}" "${_g}"; then
340				set -A NEWGRP -- ${NEWGRP[@]} ${_g}
341			fi
342		fi
343	done < ${_gr}
344}
345
346merge_loop() {
347	local INSTALL_MERGED MERGE_AGAIN
348	[[ ${MERGE_CMD} == sdiff* ]] && \
349		echo "===> Type h at the sdiff prompt (%) to get usage help\n"
350	MERGE_AGAIN=1
351	while [ -n "${MERGE_AGAIN}" ]; do
352		cp -p "${COMPFILE}" "${COMPFILE}.merged"
353		${MERGE_CMD} "${COMPFILE}.merged" \
354			"${DESTDIR}${COMPFILE#.}" "${COMPFILE}"
355		INSTALL_MERGED=v
356		while [ "${INSTALL_MERGED}" = "v" ]; do
357			echo ""
358			echo "  Use 'e' to edit the merged file"
359			echo "  Use 'i' to install the merged file"
360			echo "  Use 'n' to view a diff between the merged and new files"
361			echo "  Use 'o' to view a diff between the old and merged files"
362			echo "  Use 'r' to re-do the merge"
363			echo "  Use 'v' to view the merged file"
364			echo "  Use 'x' to delete the merged file and go back to previous menu"
365			echo "  Default is to leave the temporary file to deal with by hand"
366			echo ""
367			echo -n "===> How should I deal with the merged file? [Leave it for later] "
368			read INSTALL_MERGED
369			case "${INSTALL_MERGED}" in
370			[eE])
371				echo "editing merged file...\n"
372				${EDIT} ${COMPFILE}.merged
373				INSTALL_MERGED=v
374				;;
375			[iI])
376				mv "${COMPFILE}.merged" "${COMPFILE}"
377				echo -n "\n===> Merging ${COMPFILE#.}"
378				install_file "${COMPFILE}" || \
379					warn "problem merging ${COMPFILE#.}"
380				unset MERGE_AGAIN
381				;;
382			[nN])
383				(
384					echo "comparison between merged and new files:\n"
385					diff -u ${COMPFILE}.merged ${COMPFILE}
386				) | ${PAGER}
387				INSTALL_MERGED=v
388				;;
389			[oO])
390				(
391					echo "comparison between old and merged files:\n"
392					diff -u ${DESTDIR}${COMPFILE#.} ${COMPFILE}.merged
393				) | ${PAGER}
394				INSTALL_MERGED=v
395				;;
396			[rR])
397				rm "${COMPFILE}.merged"
398				;;
399			[vV])
400				${PAGER} "${COMPFILE}.merged"
401				;;
402			[xX])
403				rm "${COMPFILE}.merged"
404				return 1
405				;;
406			'')
407				echo "===> ${COMPFILE} will remain for your consideration"
408				unset MERGE_AGAIN
409				;;
410			*)
411				echo "invalid choice: ${INSTALL_MERGED}"
412				INSTALL_MERGED=v
413				;;
414			esac
415		done
416	done
417}
418
419diff_loop() {
420	local i CAN_INSTALL HANDLE_COMPFILE NO_INSTALLED
421	if [ -n "${BATCHMODE}" ]; then
422		HANDLE_COMPFILE=todo
423	else
424		HANDLE_COMPFILE=v
425	fi
426
427	unset NO_INSTALLED CAN_INSTALL FORCE_UPG
428
429	while [[ ${HANDLE_COMPFILE} == @(v|todo) ]]; do
430		if [ -f "${DESTDIR}${COMPFILE#.}" -a -f "${COMPFILE}" -a -z "${IS_LINK}" ]; then
431			if [ -z "${DIFFMODE}" ]; then
432				# automatically install files if current != new and current = old
433				for i in "${AUTO_UPG[@]}"; do
434					[[ ${i} == ${COMPFILE} ]] && FORCE_UPG=1
435				done
436				# automatically install files which differ only by CVS Id or that are binaries
437				if [ -z "$(diff -q -I'[$]OpenBSD:.*$' "${DESTDIR}${COMPFILE#.}" "${COMPFILE}")" -o -n "${FORCE_UPG}" -o -n "${IS_BINFILE}" ]; then
438					echo -n "===> Updating ${COMPFILE#.}"
439					if install_file "${COMPFILE}"; then
440						AUTO_INSTALLED_FILES="${AUTO_INSTALLED_FILES}${DESTDIR}${COMPFILE#.}\n"
441					else
442						warn "problem updating ${COMPFILE#.}"
443					fi
444					return
445				fi
446			fi
447			if [ "${HANDLE_COMPFILE}" = "v" ]; then
448				(
449					echo "\n========================================================================\n"
450					echo "===> Displaying differences between ${COMPFILE} and installed version:"
451					echo ""
452					diff -u "${DESTDIR}${COMPFILE#.}" "${COMPFILE}"
453				) | ${PAGER}
454				echo ""
455			fi
456		else
457			# file does not exist on the target system
458			if [ -n "${IS_LINK}" ]; then
459				if [ -n "${DIFFMODE}" ]; then
460					echo ""
461					NO_INSTALLED=1
462				else
463					if install_link; then
464						echo "===> ${COMPFILE#.} link created successfully"
465						AUTO_INSTALLED_FILES="${AUTO_INSTALLED_FILES}${DESTDIR}${COMPFILE#.}\n"
466					else
467						warn "problem creating ${COMPFILE#.} link"
468					fi
469					return
470				fi
471			fi
472			if [ -n "${DIFFMODE}" ]; then
473				echo ""
474				NO_INSTALLED=1
475			else
476				echo -n "===> Installing ${COMPFILE#.}"
477				if install_file "${COMPFILE}"; then
478					AUTO_INSTALLED_FILES="${AUTO_INSTALLED_FILES}${DESTDIR}${COMPFILE#.}\n"
479				else
480					warn "problem installing ${COMPFILE#.}"
481				fi
482				return
483			fi
484		fi
485
486		if [ -z "${BATCHMODE}" ]; then
487			echo "  Use 'd' to delete the temporary ${COMPFILE}"
488			if [ "${COMPFILE}" != ./etc/hosts ]; then
489				CAN_INSTALL=1
490				echo "  Use 'i' to install the temporary ${COMPFILE}"
491			fi
492			if [ -z "${NO_INSTALLED}" -a -z "${IS_BINFILE}" -a -z "${IS_LINK}" ]; then
493				echo "  Use 'm' to merge the temporary and installed versions"
494				echo "  Use 'v' to view the diff results again"
495			fi
496			echo ""
497			echo "  Default is to leave the temporary file to deal with by hand"
498			echo ""
499			echo -n "How should I deal with this? [Leave it for later] "
500			read HANDLE_COMPFILE
501		else
502			unset HANDLE_COMPFILE
503		fi
504
505		case "${HANDLE_COMPFILE}" in
506		[dD])
507			rm "${COMPFILE}"
508			echo "\n===> Deleting ${COMPFILE}"
509			;;
510		[iI])
511			if [ -n "${CAN_INSTALL}" ]; then
512				echo ""
513				if [ -n "${IS_LINK}" ]; then
514					if install_link; then
515						echo "===> ${COMPFILE#.} link created successfully"
516						MERGED_FILES="${MERGED_FILES}${DESTDIR}${COMPFILE#.}\n"
517					else
518						warn "problem creating ${COMPFILE#.} link"
519					fi
520				else
521					echo -n "===> Updating ${COMPFILE#.}"
522					if install_file "${COMPFILE}"; then
523						MERGED_FILES="${MERGED_FILES}${DESTDIR}${COMPFILE#.}\n"
524					else
525						warn "problem updating ${COMPFILE#.}"
526					fi
527				fi
528			else
529				echo "invalid choice: ${HANDLE_COMPFILE}\n"
530				HANDLE_COMPFILE="todo"
531			fi
532
533			;;
534		[mM])
535			if [ -z "${NO_INSTALLED}" -a -z "${IS_BINFILE}" -a -z "${IS_LINK}" ]; then
536				merge_loop && \
537					MERGED_FILES="${MERGED_FILES}${DESTDIR}${COMPFILE#.}\n" || \
538					HANDLE_COMPFILE="todo"
539			else
540				echo "invalid choice: ${HANDLE_COMPFILE}\n"
541				HANDLE_COMPFILE="todo"
542			fi
543			;;
544		[vV])
545			if [ -z "${NO_INSTALLED}" -a -z "${IS_BINFILE}" -a -z "${IS_LINK}" ]; then
546				HANDLE_COMPFILE="v"
547			else
548				echo "invalid choice: ${HANDLE_COMPFILE}\n"
549				HANDLE_COMPFILE="todo"
550			fi
551			;;
552		'')
553			echo "===> ${COMPFILE} will remain for your consideration"
554			;;
555		*)
556			echo "invalid choice: ${HANDLE_COMPFILE}\n"
557			HANDLE_COMPFILE="todo"
558			continue
559			;;
560		esac
561	done
562}
563
564sm_compare() {
565	local _c1 _c2 COMPFILE CVSID1 CVSID2
566	echo "===> Starting comparison"
567
568	cd ${TEMPROOT} || error_rm_wrkdir "cannot enter ${TEMPROOT}"
569
570	# aliases(5) needs to be handled last in case smtpd.conf(5) syntax changes
571	_c1=$(find . -type f -or -type l | grep -vE '^./etc/mail/aliases$')
572	_c2=$(find . -type f -name aliases)
573	for COMPFILE in ${_c1} ${_c2}; do
574		unset IS_BINFILE IS_LINK
575		# treat empty files the same as IS_BINFILE to avoid comparing them;
576		# only process them (i.e. install) if they don't exist on the target system
577		if [ ! -s "${COMPFILE}" ]; then
578			if [ -f "${DESTDIR}${COMPFILE#.}" ]; then
579				[[ -f ${COMPFILE} ]] && rm ${COMPFILE}
580			else
581				IS_BINFILE=1
582			fi
583		fi
584
585		# links need to be treated in a different way
586		[[ -h ${COMPFILE} ]] && IS_LINK=1
587		if [ -n "${IS_LINK}" -a -h "${DESTDIR}${COMPFILE#.}" ]; then
588			IS_LINK=1
589			# if links target are the same, remove from temproot
590			if [ "$(readlink ${COMPFILE})" = "$(readlink ${DESTDIR}${COMPFILE#.})" ]; then
591				rm "${COMPFILE}"
592			else
593				diff_loop
594			fi
595			continue
596		fi
597
598		# file not present on the system
599		if [ ! -e "${DESTDIR}${COMPFILE#.}" ]; then
600			diff_loop
601			continue
602		fi
603
604		# compare CVS $Id's first so if the file hasn't been modified,
605		# it will be deleted from temproot and ignored from comparison.
606		# several files are generated from scripts so CVS ID is not a
607		# reliable way of detecting changes; leave for a full diff.
608		if [[ -z ${DIFFMODE} && \
609			${COMPFILE} != ./etc/@(fbtab|sysctl.conf|ttys) && \
610			-z ${IS_LINK} ]]; then
611			CVSID1=$(sed -n "/[$]OpenBSD:.*Exp [$]/{p;q;}" ${DESTDIR}${COMPFILE#.} 2>/dev/null)
612			CVSID2=$(sed -n "/[$]OpenBSD:.*Exp [$]/{p;q;}" ${COMPFILE} 2>/dev/null) || CVSID2=none
613			[[ ${CVSID2} == ${CVSID1} ]] && rm "${COMPFILE}"
614		fi
615
616		if [ -f "${COMPFILE}" -a -z "${IS_LINK}" ]; then
617			# make sure files are different; if not, delete the one in temproot
618			if diff -q "${DESTDIR}${COMPFILE#.}" "${COMPFILE}" >/dev/null; then
619				rm "${COMPFILE}"
620			# xetcXX.tgz contains binary files; set IS_BINFILE to disable sdiff
621			elif diff -q "${DESTDIR}${COMPFILE#.}" "${COMPFILE}" | grep -q Binary; then
622				IS_BINFILE=1
623				diff_loop
624			else
625				diff_loop
626			fi
627		fi
628	done
629}
630
631sm_check_an_eg() {
632	EGSUM=examplessum
633	local _egmods _i _managed
634	if [ -f "${DESTDIR}/${DBDIR}/${EGSUM}" ]; then
635		EGMODS="$(sha256 -c ${DESTDIR}/${DBDIR}/${EGSUM} 2>/dev/null | grep 'FAILED$' | awk '{ print $2 }' | sed -e "s,:,,")"
636		mv ${DESTDIR}/${DBDIR}/${EGSUM} ${DESTDIR}/${DBDIR}/.${EGSUM}.bak
637	fi
638	for _i in ${EGMODS}; do
639		_managed=$(echo ${_i} | sed -e "s,etc/examples,etc,")
640		if [ -f "${DESTDIR}/${_managed}" ]; then
641			_egmods="${_egmods} ${_managed##*/}"
642		fi
643	done
644	if [ -n "${_egmods}" ]; then
645		warn "example file(s) changed for:${_egmods}"
646	else
647		# example changed but we have no corresponding file under /etc
648		unset EGMODS
649	fi
650	cd ${DESTDIR:=/} && \
651		find ./etc/examples -type f | xargs sha256 -h ${DESTDIR}/${DBDIR}/${EGSUM} || \
652		error_rm_wrkdir "failed to create ${EGSUM} checksum file"
653	if [ -f "${DESTDIR}/${DBDIR}/.${EGSUM}.bak" ]; then
654		rm ${DESTDIR}/${DBDIR}/.${EGSUM}.bak
655	fi
656}
657
658sm_post() {
659	local FILES_IN_TEMPROOT FILES_IN_BKPDIR
660
661	FILES_IN_TEMPROOT=$(find ${TEMPROOT} -type f ! -name \*.merged -size +0)
662	[[ -d ${BKPDIR} ]] && FILES_IN_BKPDIR=$(find ${BKPDIR} -type f -size +0)
663
664	if [ -n "${NEED_NEWALIASES}" ]; then
665		report "===> A new ${DESTDIR}/etc/mail/aliases file was installed."
666		report "However ${DESTDIR}/usr/bin/newaliases could not be run,"
667		report "you will need to rebuild your aliases database manually.\n"
668	fi
669
670	if [ -n "${AUTO_INSTALLED_FILES}" ]; then
671		report "===> Automatically installed file(s)"
672		report "${AUTO_INSTALLED_FILES}"
673	fi
674	if [ -n "${MERGED_FILES}" ]; then
675		report "===> Manually merged/installed file(s)"
676		report "${MERGED_FILES}"
677	fi
678	if [ -n "${FILES_IN_BKPDIR}" ]; then
679		report "===> Backup of replaced file(s) can be found under"
680		report "${BKPDIR}\n"
681	fi
682	if [ -n "${NEWUSR}" -o -n "${NEWGRP}" ]; then
683		report "===> The following user(s)/group(s) have been added"
684		[[ -n ${NEWUSR} ]] && report "user(s): ${NEWUSR[@]}"
685		[[ -n ${NEWGRP} ]] && report "group(s): ${NEWGRP[@]}"
686		report ""
687	fi
688	if [ -n "${EGMODS}" ]; then
689		report "===> Example(s) with corresponding used files modified since last run"
690		report "${EGMODS}"
691		report ""
692	fi
693	if [ -n "${FILES_IN_TEMPROOT}" ]; then
694		report "===> File(s) remaining for you to merge by hand"
695		report "${FILES_IN_TEMPROOT}"
696	fi
697
698	[[ -n ${FILES_IN_TEMPROOT} ]] && \
699		warn "some files are still left for comparison"
700
701	[[ -n ${NEED_NEWALIASES} ]] && \
702		warn "newaliases(8) failed to run properly"
703
704	[[ -n ${NEED_REBOOT} ]] && \
705		warn "some new/updated file(s) may require a reboot"
706
707	echo "===> Checking directory hierarchy permissions (running mtree(8))"
708	mtree -qdef ${DESTDIR}/etc/mtree/4.4BSD.dist -p ${DESTDIR:=/} -U >/dev/null
709	[[ -n ${XTGZ} ]] && \
710		mtree -qdef ${DESTDIR}/etc/mtree/BSD.x11.dist -p ${DESTDIR:=/} -U >/dev/null
711
712	if [ -e "${REPORT}" ]; then
713		echo "===> Output log available at ${REPORT}"
714		find ${TEMPROOT} -type f -empty | xargs -r rm
715		find ${TEMPROOT} -type d | sort -r | xargs -r rmdir 2>/dev/null
716	else
717		echo "===> Removing ${WRKDIR}"
718		rm -rf "${WRKDIR}"
719	fi
720
721	unset NEED_NEWALIASES NEED_REBOOT
722
723	clean_src
724	rm -f ${DESTDIR}/${DBDIR}/.*.bak
725}
726
727
728while getopts bdSs:x: arg; do
729	case ${arg} in
730	b)
731		BATCHMODE=1
732		;;
733	d)
734		DIFFMODE=1
735		;;
736	s)
737		if [ -d "${OPTARG}" ]; then
738			SRCDIR="${OPTARG}"
739			[[ -f ${SRCDIR}/etc/Makefile ]] || \
740				error_rm_wrkdir "${SRCDIR}: invalid \"src\" tree, missing ${SRCDIR}/etc/Makefile"
741			continue
742		fi
743		TGZ="${OPTARG}"
744		;;
745	S)
746		NOSIGCHECK=1
747		;;
748	x)
749		XTGZ="${OPTARG}"
750		;;
751	*)
752		usage
753		error_rm_wrkdir
754		;;
755	esac
756done
757
758shift $(( OPTIND -1 ))
759if (($# != 0)); then
760	usage
761	error_rm_wrkdir
762fi
763
764if [ -z "${SRCDIR}" -a -z "${TGZ}" -a -z "${XTGZ}" ]; then
765	if [ -n "${SM_PATH}" ]; then
766		TGZ="${SM_PATH}/etc${RELINT}.tgz"
767		if [ -d ${DESTDIR}/etc/X11 ]; then
768			XTGZ="${SM_PATH}/xetc${RELINT}.tgz"
769		fi
770	elif [ -f "/usr/src/etc/Makefile" ]; then
771		SRCDIR=/usr/src
772	else
773		usage
774		error_rm_wrkdir "please specify a valid path to src or (x)etcXX.tgz"
775	fi
776fi
777
778TEMPROOT="${WRKDIR}/temproot"
779BKPDIR="${WRKDIR}/backups"
780
781sm_fetch_and_verify
782sm_populate
783sm_compare
784sm_check_an_eg
785sm_post
786