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