xref: /openbsd-src/usr.sbin/syspatch/syspatch.sh (revision 3cab49e485395e9131ea4dc6fe07d26b80b2981d)
1#!/bin/ksh
2#
3# $OpenBSD: syspatch.sh,v 1.33 2016/11/03 16:12:23 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
67	for _patch in ${_patches}; do
68		fetch_and_verify "${_patch}"
69		trap "" 2
70		apply_patch "${_patch}"
71		trap "rm -rf ${_TMP}; exit 1" 2
72	done
73
74	sp_cleanup
75}
76
77create_rollback()
78{
79	local _file _patch=$1 _rbfiles
80	[[ -n ${_patch} ]]
81	shift
82	local _files="${@}"
83	[[ -n ${_files} ]]
84
85	[[ -d ${_PDIR}/${_REL} ]] || install -d ${_PDIR}/${_REL}
86
87	for _file in ${_files}; do
88		[[ -f /${_file} ]] || continue
89		_rbfiles="${_rbfiles} ${_file}"
90	done
91
92	if ! (cd / &&
93		# GENERIC.MP: substitute bsd.mp->bsd and bsd.sp->bsd
94		if ${_BSDMP} &&
95			tar -tzf ${_TMP}/${_patch}.tgz bsd >/dev/null 2>&1; then
96			tar -czf ${_PDIR}/${_REL}/rollback-${_patch}.tgz \
97				-s '/^bsd.mp$//' -s '/^bsd$/bsd.mp/' \
98				-s '/^bsd.sp$/bsd/' bsd.sp ${_rbfiles}
99		else
100			tar -czf ${_PDIR}/${_REL}/rollback-${_patch}.tgz \
101				${_rbfiles}
102		fi
103	); then
104		rm ${_PDIR}/${_REL}/rollback-${_patch}.tgz
105		sp_err "Failed to create rollback for ${_patch}"
106	fi
107}
108
109fetch_and_verify()
110{
111	# XXX privsep ala installer
112	local _patch=$1
113	[[ -n ${_patch} ]]
114
115	local _key="/etc/signify/openbsd-${_RELINT}-syspatch.pub" _p
116
117	${_FETCH} -o "${_TMP}/SHA256.sig" "${PATCH_PATH}/SHA256.sig"
118	${_FETCH} -mD "Applying" -o "${_TMP}/${_patch}.tgz" \
119		"${PATCH_PATH}/${_patch}.tgz"
120	(cd ${_TMP} &&
121		/usr/bin/signify -qC -p ${_key} -x SHA256.sig ${_patch}.tgz)
122}
123
124install_file()
125{
126	# XXX handle symlinks, dir->file, file->dir?
127	local _src=$1 _dst=$2
128	[[ -f ${_src} && -f ${_dst} ]]
129
130	local _fmode _fown _fgrp
131	eval $(stat -f "_fmode=%OMp%OLp _fown=%Su _fgrp=%Sg" ${_src})
132
133	install -DFS -m ${_fmode} -o ${_fown} -g ${_fgrp} ${_src} ${_dst}
134}
135
136install_kernel()
137{
138	local _bsd=/bsd _kern=$1
139	[[ -n ${_kern} ]]
140
141	# only save the original release kernel once
142	[[ -f /bsd.rollback${_RELINT} ]] ||
143		install -FSp /bsd /bsd.rollback${_RELINT}
144
145	if ${_BSDMP}; then
146		[[ ${_kern##*/} == bsd ]] && _bsd=/bsd.sp
147	fi
148
149	if [[ -n ${_bsd} ]]; then
150		install -FS ${_kern} ${_bsd}
151	fi
152}
153
154ls_installed()
155{
156	local _p
157	for _p in ${_PDIR}/${_REL}/*; do
158		_p=${_p:##*/}
159		[[ ${_p} = rollback-syspatch-${_RELINT}-*.tgz ]] &&
160			_p=${_p#rollback-} && echo ${_p%.tgz}
161	done | sort -V
162}
163
164ls_missing()
165{
166	# XXX match with installed sets (comp, x...)?
167	local _a _installed
168	_installed="$(ls_installed)"
169
170	${_FETCH} -o "${_TMP}/index.txt" "${PATCH_PATH}/index.txt"
171
172	for _a in $(sed 's/^.* //;s/^M//;s/.tgz$//' ${_TMP}/index.txt |
173		grep "^syspatch-${_RELINT}-.*$" | sort -V); do
174		if [[ -n ${_installed} ]]; then
175			echo ${_a} | grep -qw -- "${_installed}" || echo ${_a}
176		else
177			echo ${_a}
178		fi
179	done
180}
181
182rollback_patch()
183{
184	needs_root
185	local _explodir _file _files _patch
186
187	_patch="$(ls_installed | sort -V | tail -1)"
188	[[ -n ${_patch} ]]
189
190	echo "Reverting ${_patch}"
191	_explodir=${_TMP}/rollback-${_patch}
192	mkdir -p ${_explodir}
193
194	_files="$(tar xvzphf ${_PDIR}/${_REL}/rollback-${_patch}.tgz -C \
195		${_explodir})"
196
197	for _file in ${_files}; do
198		if [[ ${_file} == @(bsd|bsd.mp) ]]; then
199			install_kernel ${_explodir}/${_file} ||
200				sp_err "Failed to revert ${_patch} (/${_file})"
201		else
202			install_file ${_explodir}/${_file} /${_file} ||
203				sp_err "Failed to revert ${_patch} (/${_file})"
204		fi
205	done
206
207	rm ${_PDIR}/${_REL}/rollback-${_patch}.tgz \
208		${_PDIR}/${_REL}/${_patch#syspatch-${_RELINT}-}.patch.sig
209
210	sp_cleanup
211}
212
213sp_cleanup()
214{
215	local _d _k
216
217	# remove non matching release /var/syspatch/ content
218	for _d in ${_PDIR}/*; do
219		[[ -e ${_d} ]] || continue
220		[[ ${_d:##*/} == ${_REL} ]] || rm -r ${_d}
221	done
222
223	# remove non matching release rollback kernel
224	for _k in /bsd.rollback*; do
225		[[ -f ${_k} ]] || continue
226		[[ ${_k} == /bsd.rollback${_RELINT} ]] || rm ${_k}
227	done
228
229	# remove rollback kernel if all kernel syspatches have been reverted
230	cmp -s /bsd /bsd.rollback${_RELINT} && rm /bsd.rollback${_RELINT}
231
232	# non-fatal: the syspatch|rollback tarball should have correct perms
233	for _m in 4.4BSD BSD.x11; do
234		mtree -qdef /etc/mtree/${_m}.dist -p / -U >/dev/null || true
235	done
236}
237
238# only run on release (not -current nor -stable)
239set -A _KERNV -- $(sysctl -n kern.version |
240	sed 's/^OpenBSD \([0-9]\.[0-9]\)\([^ ]*\).*/\1 \2/;q')
241[[ -z ${_KERNV[1]} ]]
242
243# check args
244[[ $@ == @(|-[[:alpha:]]) ]] || usage
245
246# XXX to be discussed; check for $ARCH?
247[[ -n ${PATCH_PATH} ]]
248[[ -d ${PATCH_PATH} ]] && PATCH_PATH="file://$(readlink -f ${PATCH_PATH})"
249[[ ${PATCH_PATH:%%://*} == @(file|ftp|http|https) ]]
250
251[[ $(sysctl -n hw.ncpufound) -gt 1 ]] && _BSDMP=true || _BSDMP=false
252_FETCH="/usr/bin/ftp -MVk ${FTP_KEEPALIVE-0}"
253_PDIR="/var/syspatch"
254_REL=${_KERNV[0]}
255_RELINT=${_REL%\.*}${_REL#*\.}
256_TMP=$(mktemp -d -p /tmp syspatch.XXXXXXXXXX)
257readonly _BSDMP _FETCH _PDIR _REL _RELINT _TMP
258[[ -n ${_REL} && -n ${_RELINT} ]]
259
260trap "rm -rf ${_TMP}; exit 1" 2 3 9 13 15 ERR
261
262while getopts clr arg; do
263	case ${arg} in
264		c) ls_missing;;
265		l) ls_installed;;
266		r) rollback_patch;;
267		*) usage;;
268	esac
269done
270shift $(( OPTIND -1 ))
271[[ $# -ne 0 ]] && usage
272
273[[ ${OPTIND} != 1 ]] || apply_patches
274
275rm -rf ${_TMP}
276