xref: /openbsd-src/usr.sbin/syspatch/syspatch.sh (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1#!/bin/ksh
2#
3# $OpenBSD: syspatch.sh,v 1.15 2016/09/11 13:10:59 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
21trap "rm -rf ${_TMP}; exit 1" 2 3 9 13 15 ERR
22
23sp_err()
24{
25	echo "${@}" 1>&2 && return 1
26}
27
28usage()
29{
30	sp_err "usage: ${0##*/} [-c | -l | -r]"
31}
32
33needs_root()
34{
35	[[ $(id -u) -ne 0 ]] && sp_err "${0##*/}: need root privileges"
36}
37
38apply_patches()
39{
40	needs_root
41	# XXX cleanup old rollback patches and sig (installer should as well)
42	local _m _patch _patches="$(ls_missing)"
43	[[ -n ${_patches} ]] || return 0 # nothing to do
44
45	for _patch in ${_patches}; do
46		fetch_and_verify "${_patch}" && install_patch "${_patch}"
47	done
48
49	# non-fatal: the syspatch tarball should have correct permissions
50	for _m in 4.4BSD BSD.x11; do
51		mtree -qdef /etc/mtree/${_m}.dist -p / -U >/dev/null || true
52	done
53}
54
55create_rollback()
56{
57	local _file _patch=$1 _rbfiles
58	[[ -n ${_patch} ]]
59	shift
60	local _files="${@}"
61	[[ -n ${_files} ]]
62
63	[[ -d ${_PDIR}/${_REL} ]] || install -d ${_PDIR}/${_REL}
64
65	for _file in ${_files}; do
66		[[ -f /${_file} ]] || continue
67		_rbfiles="${_rbfiles} ${_file}"
68	done
69
70	(cd / &&
71		# GENERIC.MP: substitute bsd.mp->bsd and bsd.sp->bsd
72		if ${_BSDMP} &&
73			tar -tzf ${_TMP}/${_patch}.tgz bsd >/dev/null 2>&1; then
74			tar -czf ${_PDIR}/${_REL}/rollback-${_patch}.tgz \
75				-s '/^bsd.mp$//' -s '/^bsd$/bsd.mp/' \
76				-s '/^bsd.sp$/bsd/' bsd.sp ${_rbfiles}
77		else
78			tar -czf ${_PDIR}/${_REL}/rollback-${_patch}.tgz \
79				${_rbfiles}
80		fi
81	)
82}
83
84fetch_and_verify()
85{
86	# XXX privsep ala installer
87	local _patch="$@"
88	[[ -n ${_patch} ]]
89
90	local _key="/etc/signify/openbsd-${_RELINT}-syspatch.pub" _p
91
92	${_FETCH} -o "${_TMP}/SHA256.sig" "${PATCH_PATH}/SHA256.sig"
93
94	for _p in ${_patch}; do
95		_p=${_p}.tgz
96		 ${_FETCH} -mD "Get/Verify" -o "${_TMP}/${_p}" \
97			"${PATCH_PATH}/${_p}"
98		(cd ${_TMP} &&
99			/usr/bin/signify -qC -p ${_key} -x SHA256.sig ${_p})
100	done
101}
102
103install_file()
104{
105	# XXX handle sym/hardlinks?
106	# XXX handle dir becoming file and vice-versa?
107	local _src=$1 _dst=$2
108	[[ -f ${_src} && -f ${_dst} ]]
109
110	local _fmode _fown _fgrp
111	eval $(stat -f "_fmode=%OMp%OLp _fown=%Su _fgrp=%Sg" ${_src})
112
113	install -DFS -m ${_fmode} -o ${_fown} -g ${_fgrp} ${_src} ${_dst}
114}
115
116install_kernel()
117{
118	local _bsd=/bsd _kern=$1
119	[[ -n ${_kern} ]]
120
121	# we only save the original release kernel once
122	[[ -f /bsd.rollback${_RELINT} ]] ||
123		install -FSp /bsd /bsd.rollback${_RELINT}
124
125	if ${_BSDMP}; then
126		[[ ${_kern##*/} == bsd ]] && _bsd=/bsd.sp
127	fi
128
129	if [[ -n ${_bsd} ]]; then
130		install -FS ${_kern} ${_bsd}
131	fi
132}
133
134install_patch()
135{
136	local _explodir _file _files _patch="$1"
137	[[ -n ${_patch} ]]
138
139	local _explodir=${_TMP}/${_patch}
140	mkdir -p ${_explodir}
141
142	_files="$(tar xvzphf ${_TMP}/${_patch}.tgz -C ${_explodir})"
143	create_rollback ${_patch} "${_files}"
144
145	for _file in ${_files}; do
146		if [[ ${_file} == @(bsd|bsd.mp) ]]; then
147			if ! install_kernel ${_explodir}/${_file}; then
148				rollback_patch; return 1
149			fi
150		else
151			if ! install_file ${_explodir}/${_file} /${_file}; then
152				rollback_patch; return 1
153			fi
154		fi
155	done
156}
157
158ls_avail()
159{
160	${_FETCH} -o - "${PATCH_PATH}/index.txt" |
161		sed 's/^.* //;s/^M//;s/.tgz$//' |
162		grep "^syspatch-${_RELINT}-.*$" | sort -V
163}
164
165ls_installed()
166{
167	local _p
168	# no _REL dir = no installed patch
169	cd ${_PDIR}/${_REL} 2>/dev/null && set -- * || return 0
170	for _p; do
171		 [[ ${_p} = rollback-syspatch-${_RELINT}-*.tgz ]] &&
172			_p=${_p#rollback-} && echo ${_p%.tgz}
173	done | sort -V
174}
175
176ls_missing()
177{
178	local _a _installed
179	_installed="$(ls_installed)"
180
181	for _a in $(ls_avail); do
182		if [[ -n ${_installed} ]]; then
183			echo ${_a} | grep -qw -- "${_installed}" || echo ${_a}
184		else
185			echo ${_a}
186		fi
187	done
188}
189
190rollback_patch()
191{
192	needs_root
193	local _explodir _file _files _patch
194
195	_patch="$(ls_installed | sort -V | tail -1)"
196	[[ -n ${_patch} ]]
197
198	_explodir=${_TMP}/rollback-${_patch}
199	mkdir -p ${_explodir}
200
201	_files="$(tar xvzphf ${_PDIR}/${_REL}/rollback-${_patch}.tgz -C \
202		${_explodir})"
203
204	for _file in ${_files}; do
205		if [[ ${_file} == @(bsd|bsd.mp) ]]; then
206			install_kernel ${_explodir}/${_file}
207		else
208			install_file ${_explodir}/${_file} /${_file}
209		fi
210	done
211
212	rm ${_PDIR}/${_REL}/rollback-${_patch}.tgz \
213		${_PDIR}/${_REL}/${_patch#syspatch-${_RELINT}-}.patch.sig
214}
215
216# we do not run on current
217set -A _KERNV -- $(sysctl -n kern.version |
218	sed 's/^OpenBSD \([0-9]\.[0-9]\)\([^ ]*\).*/\1 \2/;q')
219[[ -z ${_KERNV[1]} ]] || [[ ${_KERNV[1]} == "-stable" ]]
220
221# check args
222[[ $@ == @(|-[[:alpha:]]) ]] || usage
223
224# XXX to be discussed
225[[ -n ${PATCH_PATH} ]]
226[[ -d ${PATCH_PATH} ]] && PATCH_PATH="file://$(readlink -f ${PATCH_PATH})"
227
228# XXX hw.ncpufound ?
229[[ $(sysctl -n hw.ncpu) -gt 1 ]] && _BSDMP=true || _BSDMP=false
230_FETCH="/usr/bin/ftp -MV -k ${FTP_KEEPALIVE-0}"
231_PDIR="/var/syspatch"
232_REL=${_KERNV[0]}
233_RELINT=${_REL%\.*}${_REL#*\.}
234_TMP=$(mktemp -d -p /tmp syspatch.XXXXXXXXXX)
235readonly _BSDMP _FETCH _PDIR _REL _RELINT_TMP
236
237while getopts clr arg; do
238	case ${arg} in
239		c) ls_missing;;
240		l) ls_installed;;
241		r) rollback_patch;;
242		*) usage;;
243	esac
244done
245shift $(( OPTIND -1 ))
246[[ $# -ne 0 ]] && usage
247
248[[ ${OPTIND} != 1 ]] || apply_patches
249
250rm -rf ${_TMP}
251