xref: /openbsd-src/usr.sbin/syspatch/syspatch.sh (revision d145eff14d75a6031852368bb8d76cd1a50275a6)
1#!/bin/ksh
2#
3# $OpenBSD: syspatch.sh,v 1.20 2016/11/01 15:51:04 ajacoutot Exp $
4#
5# Copyright (c) 2016 Antoine Jacoutot <ajacoutot@openbsd.org>
6#
7# Permission to use, copy, modify, and distribute this software for any
8# purpose with or without fee is hereby granted, provided that the above
9# copyright notice and this permission notice appear in all copies.
10#
11# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
19set -e
20
21sp_err()
22{
23	echo "${@}" 1>&2 && return 1
24}
25
26usage()
27{
28	sp_err "usage: ${0##*/} [-c | -l | -r]"
29}
30
31needs_root()
32{
33	[[ $(id -u) -ne 0 ]] && sp_err "${0##*/}: need root privileges"
34}
35
36apply_patch()
37{
38	local _explodir _file _files _patch=$1
39	[[ -n ${_patch} ]]
40
41	local _explodir=${_TMP}/${_patch}
42	mkdir -p ${_explodir}
43
44	_files="$(tar xvzphf ${_TMP}/${_patch}.tgz -C ${_explodir})"
45	create_rollback ${_patch} "${_files}"
46
47	for _file in ${_files}; do
48		if [[ ${_file} == @(bsd|bsd.mp) ]]; then
49			if ! install_kernel ${_explodir}/${_file}; then
50				rollback_patch
51				sp_err "Failed to apply ${_patch} (/${_file})"
52			fi
53		else
54			if ! install_file ${_explodir}/${_file} /${_file}; then
55				rollback_patch
56				sp_err "Failed to apply ${_patch} (/${_file})"
57			fi
58		fi
59	done
60}
61
62apply_patches()
63{
64	needs_root
65	local _m _patch _patches="$(ls_missing)"
66	[[ -n ${_patches} ]] || return 0 # nothing to do
67
68	for _patch in ${_patches}; do
69		fetch_and_verify "${_patch}"
70		apply_patch "${_patch}"
71	done
72
73	# non-fatal: the syspatch tarball should have correct permissions
74	for _m in 4.4BSD BSD.x11; do
75		mtree -qdef /etc/mtree/${_m}.dist -p / -U >/dev/null || true
76	done
77}
78
79create_rollback()
80{
81	local _file _patch=$1 _rbfiles
82	[[ -n ${_patch} ]]
83	shift
84	local _files="${@}"
85	[[ -n ${_files} ]]
86
87	[[ -d ${_PDIR}/${_REL} ]] || install -d ${_PDIR}/${_REL}
88
89	for _file in ${_files}; do
90		[[ -f /${_file} ]] || continue
91		_rbfiles="${_rbfiles} ${_file}"
92	done
93
94	if ! (cd / &&
95		# GENERIC.MP: substitute bsd.mp->bsd and bsd.sp->bsd
96		if ${_BSDMP} &&
97			tar -tzf ${_TMP}/${_patch}.tgz bsd >/dev/null 2>&1; then
98			tar -czf ${_PDIR}/${_REL}/rollback-${_patch}.tgz \
99				-s '/^bsd.mp$//' -s '/^bsd$/bsd.mp/' \
100				-s '/^bsd.sp$/bsd/' bsd.sp ${_rbfiles}
101		else
102			tar -czf ${_PDIR}/${_REL}/rollback-${_patch}.tgz \
103				${_rbfiles}
104		fi
105	); then
106		rm ${_PDIR}/${_REL}/rollback-${_patch}.tgz
107		sp_err "Failed to create rollback for ${_patch}"
108	fi
109}
110
111fetch_and_verify()
112{
113	# XXX privsep ala installer
114	local _patch=$1
115	[[ -n ${_patch} ]]
116
117	local _key="/etc/signify/openbsd-${_RELINT}-syspatch.pub" _p
118
119	# XXX handle bogus PATCH_PATH (ftp(1) interactive mode)
120	${_FETCH} -o "${_TMP}/SHA256.sig" "${PATCH_PATH}/SHA256.sig"
121
122	# XXX see above
123	${_FETCH} -mD "Applying" -o "${_TMP}/${_patch}.tgz" \
124		"${PATCH_PATH}/${_patch}.tgz"
125	(cd ${_TMP} &&
126		/usr/bin/signify -qC -p ${_key} -x SHA256.sig ${_patch}.tgz)
127}
128
129install_file()
130{
131	# XXX handle sym/hardlinks?
132	# XXX handle dir becoming file and vice-versa?
133	local _src=$1 _dst=$2
134	[[ -f ${_src} && -f ${_dst} ]]
135
136	local _fmode _fown _fgrp
137	eval $(stat -f "_fmode=%OMp%OLp _fown=%Su _fgrp=%Sg" ${_src})
138
139	install -DFS -m ${_fmode} -o ${_fown} -g ${_fgrp} ${_src} ${_dst}
140}
141
142install_kernel()
143{
144	local _bsd=/bsd _kern=$1
145	[[ -n ${_kern} ]]
146
147	# only save the original release kernel once
148	[[ -f /bsd.rollback${_RELINT} ]] ||
149		install -FSp /bsd /bsd.rollback${_RELINT}
150
151	if ${_BSDMP}; then
152		[[ ${_kern##*/} == bsd ]] && _bsd=/bsd.sp
153	fi
154
155	if [[ -n ${_bsd} ]]; then
156		install -FS ${_kern} ${_bsd}
157	fi
158}
159
160ls_avail()
161{
162	${_FETCH} -o - "${PATCH_PATH}/index.txt" |
163		sed 's/^.* //;s/^M//;s/.tgz$//' |
164		grep "^syspatch-${_RELINT}-.*$" | sort -V
165}
166
167ls_installed()
168{
169	local _p
170	# no _REL dir = no installed patch
171	cd ${_PDIR}/${_REL} 2>/dev/null && set -- * || return 0
172	for _p; do
173		 [[ ${_p} = rollback-syspatch-${_RELINT}-*.tgz ]] &&
174			_p=${_p#rollback-} && echo ${_p%.tgz}
175	done | sort -V
176}
177
178ls_missing()
179{
180	local _a _installed
181	_installed="$(ls_installed)"
182
183	for _a in $(ls_avail); do
184		if [[ -n ${_installed} ]]; then
185			echo ${_a} | grep -qw -- "${_installed}" || echo ${_a}
186		else
187			echo ${_a}
188		fi
189	done
190}
191
192sp_cleanup()
193{
194	local _d
195
196	# remove non matching release /var/syspatch/ content
197	cd ${_PDIR} && set -- *
198	for _d; do
199		[[ -e ${_d} ]] || continue
200		[[ ${_d} == ${_REL} ]] || rm -r ${_d}
201	done
202
203	# remove rollback kernel if all kernel syspatches have been reverted
204	cmp -s /bsd /bsd.rollback${_RELINT} && rm /bsd.rollback${_RELINT}
205}
206
207rollback_patch()
208{
209	needs_root
210	local _explodir _file _files _patch
211
212	_patch="$(ls_installed | sort -V | tail -1)"
213	[[ -n ${_patch} ]]
214
215	_explodir=${_TMP}/rollback-${_patch}
216	mkdir -p ${_explodir}
217
218	_files="$(tar xvzphf ${_PDIR}/${_REL}/rollback-${_patch}.tgz -C \
219		${_explodir})"
220
221	for _file in ${_files}; do
222		if [[ ${_file} == @(bsd|bsd.mp) ]]; then
223			install_kernel ${_explodir}/${_file} ||
224				sp_err "Failed to rollback ${_patch} (/${_file})"
225		else
226			install_file ${_explodir}/${_file} /${_file} ||
227				sp_err "Failed to rollback ${_patch} (/${_file})"
228		fi
229	done
230
231	rm ${_PDIR}/${_REL}/rollback-${_patch}.tgz \
232		${_PDIR}/${_REL}/${_patch#syspatch-${_RELINT}-}.patch.sig
233}
234
235# only run on release (not -current nor -stable)
236set -A _KERNV -- $(sysctl -n kern.version |
237	sed 's/^OpenBSD \([0-9]\.[0-9]\)\([^ ]*\).*/\1 \2/;q')
238[[ -z ${_KERNV[1]} ]]
239
240# check args
241[[ $@ == @(|-[[:alpha:]]) ]] || usage
242
243# XXX to be discussed
244[[ -n ${PATCH_PATH} ]]
245[[ -d ${PATCH_PATH} ]] && PATCH_PATH="file://$(readlink -f ${PATCH_PATH})"
246
247# XXX hw.ncpufound ?
248[[ $(sysctl -n hw.ncpu) -gt 1 ]] && _BSDMP=true || _BSDMP=false
249_FETCH="/usr/bin/ftp -MV -k ${FTP_KEEPALIVE-0}"
250_PDIR="/var/syspatch"
251_REL=${_KERNV[0]}
252_RELINT=${_REL%\.*}${_REL#*\.}
253_TMP=$(mktemp -d -p /tmp syspatch.XXXXXXXXXX)
254readonly _BSDMP _FETCH _PDIR _REL _RELINT _TMP
255[[ -n ${_REL} && -n ${_RELINT} ]]
256
257trap "rm -rf ${_TMP}; exit 1" 2 3 9 13 15 ERR
258
259while getopts clr arg; do
260	case ${arg} in
261		c) ls_missing;;
262		l) ls_installed;;
263		r) rollback_patch;;
264		*) usage;;
265	esac
266done
267shift $(( OPTIND -1 ))
268[[ $# -ne 0 ]] && usage
269
270[[ ${OPTIND} != 1 ]] || apply_patches
271
272sp_cleanup
273rm -rf ${_TMP}
274