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