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