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