1#!/bin/ksh 2# 3# $OpenBSD: syspatch.sh,v 1.115 2017/07/04 20:25:53 ajacoutot Exp $ 4# 5# Copyright (c) 2016, 2017 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 20umask 0022 21 22sp_err() 23{ 24 echo "${1}" 1>&2 && return ${2:-1} 25} 26 27usage() 28{ 29 sp_err "usage: ${0##*/} [-c | -l | -R | -r]" 30} 31 32apply_patch() 33{ 34 local _edir _file _files _patch=$1 _ret=0 _s 35 [[ -n ${_patch} ]] 36 37 _edir=${_TMP}/${_patch} 38 39 fetch_and_verify "syspatch${_patch}.tgz" 40 41 trap '' INT 42 echo "Installing patch ${_patch##${_OSrev}-}" 43 install -d ${_edir} ${_PDIR}/${_patch} 44 45 ! ${_BSDMP} && [[ ! -f /bsd.mp ]] && _s="-s /^bsd.mp$//" 46 _files="$(tar -xvzphf ${_TMP}/syspatch${_patch}.tgz -C ${_edir} ${_s})" 47 48 checkfs ${_files} 49 create_rollback ${_patch} "${_files}" 50 51 for _file in ${_files}; do 52 ((_ret == 0)) || break 53 if [[ ${_file} == @(bsd|bsd.mp) ]]; then 54 install_kernel ${_edir}/${_file} || _ret=$? 55 else 56 install_file ${_edir}/${_file} /${_file} || _ret=$? 57 fi 58 done 59 60 if ((_ret != 0)); then 61 sp_err "Failed to apply patch ${_patch##${_OSrev}-}" 0 62 rollback_patch; return ${_ret} 63 fi 64 # don't fill up /tmp when installing multiple patches at once; non-fatal 65 rm -rf ${_edir} ${_TMP}/syspatch${_patch}.tgz 66 trap exit INT 67} 68 69# quick-and-dirty filesystem status and size checks: 70# - assume old files are about the same size as new ones 71# - ignore new (nonexistent) files 72# - ignore rollback tarball: create_rollback() will handle the failure 73# - compute total size of all files per fs, simpler and less margin for error 74# - if we install a kernel, double /bsd size (duplicate it in the list) when: 75# - we are on an MP system (/bsd.mp does not exist there) 76# - /bsd.syspatchXX is not present (create_rollback will copy it from /bsd) 77checkfs() 78{ 79 local _d _dev _df _files="${@}" _ret _sz 80 [[ -n ${_files} ]] 81 82 if echo "${_files}" | grep -qw bsd; then 83 ${_BSDMP} || [[ ! -f /bsd.syspatch${_OSrev} ]] && 84 _files="bsd ${_files}" 85 fi 86 87 set +e # ignore errors due to: 88 # - nonexistent files (i.e. syspatch is installing new files) 89 # - broken interpolation due to bogus devices like remote filesystems 90 eval $(cd / && 91 stat -qf "_dev=\"\${_dev} %Sd\" %Sd=\"\${%Sd:+\${%Sd}\+}%Uz\"" \ 92 ${_files}) 2>/dev/null || _ret=$? 93 set -e 94 [[ ${_ret} == 127 ]] && sp_err "Remote filesystem, aborting" 95 96 for _d in $(printf '%s\n' ${_dev} | sort -u); do 97 mount | grep -v read-only | grep -q "^/dev/${_d} " || 98 sp_err "Read-only filesystem, aborting" 99 _df=$(df -Pk | grep "^/dev/${_d} " | tr -s ' ' | cut -d ' ' -f4) 100 _sz=$(($((_d))/1024)) 101 ((_df > _sz)) || sp_err "No space left on ${_d}, aborting" 102 done 103} 104 105create_rollback() 106{ 107 # XXX annotate new files so we can remove them if we rollback? 108 local _file _patch=$1 _rbfiles _ret=0 109 [[ -n ${_patch} ]] 110 shift 111 local _files="${@}" 112 [[ -n ${_files} ]] 113 114 for _file in ${_files}; do 115 [[ -f /${_file} ]] || continue 116 # only save the original release kernel once 117 if [[ ${_file} == bsd && ! -f /bsd.syspatch${_OSrev} ]]; then 118 install -FSp /bsd /bsd.syspatch${_OSrev} 119 fi 120 _rbfiles="${_rbfiles} ${_file}" 121 done 122 123 # GENERIC.MP: substitute bsd.mp->bsd and bsd.sp->bsd 124 if ${_BSDMP} && 125 tar -tzf ${_TMP}/syspatch${_patch}.tgz bsd >/dev/null 2>&1; then 126 tar -C / -czf ${_PDIR}/${_patch}/rollback.tgz -s '/^bsd.mp$//' \ 127 -s '/^bsd$/bsd.mp/' -s '/^bsd.sp$/bsd/' bsd.sp \ 128 ${_rbfiles} || _ret=$? 129 else 130 tar -C / -czf ${_PDIR}/${_patch}/rollback.tgz ${_rbfiles} || 131 _ret=$? 132 fi 133 134 if ((_ret != 0)); then 135 sp_err "Failed to create rollback patch ${_patch##${_OSrev}-}" 0 136 rm -r ${_PDIR}/${_patch}; return ${_ret} 137 fi 138} 139 140fetch_and_verify() 141{ 142 local _tgz=$1 _title="Get/Verify" 143 [[ -n ${_tgz} ]] 144 145 [[ -t 0 ]] || echo "${_title} ${_tgz}" 146 unpriv -f "${_TMP}/${_tgz}" ftp -VD "${_title}" -o "${_TMP}/${_tgz}" \ 147 "${_MIRROR}/${_tgz}" 148 149 (cd ${_TMP} && sha256 -qC ${_TMP}/SHA256 ${_tgz}) 150} 151 152install_file() 153{ 154 # XXX handle hard and symbolic links, dir->file, file->dir? 155 local _dst=$2 _fgrp _fmode _fown _src=$1 156 [[ -f ${_src} && -f ${_dst} ]] 157 158 eval $(stat -f "_fmode=%OMp%OLp _fown=%Su _fgrp=%Sg" ${_src}) 159 160 install -DFSp -m ${_fmode} -o ${_fown} -g ${_fgrp} ${_src} ${_dst} 161} 162 163install_kernel() 164{ 165 local _bsd _kern=$1 166 [[ -n ${_kern} ]] 167 168 if ${_BSDMP}; then 169 [[ ${_kern##*/} == bsd ]] && _bsd=bsd.sp || _bsd=bsd 170 fi 171 172 install -FSp ${_kern} /${_bsd:-${_kern##*/}} 173} 174 175ls_installed() 176{ 177 local _p 178 for _p in ${_PDIR}/${_OSrev}-+([[:digit:]])_+([[:alnum:]_]); do 179 [[ -f ${_p}/rollback.tgz ]] && echo ${_p##*/${_OSrev}-} 180 done | sort -V 181} 182 183ls_missing() 184{ 185 local _c _f _cmd _l="$(ls_installed)" _p _r _sha=${_TMP}/SHA256 186 187 # return inmediately if we cannot reach the mirror server 188 [[ -d ${_MIRROR#file://*} ]] || 189 unpriv ftp -MVo /dev/null ${_MIRROR%syspatch/*} >/dev/null 190 191 # don't output anything on stdout to prevent corrupting the patch list; 192 # redirect stderr as well in case there's no patch available 193 unpriv -f "${_sha}.sig" ftp -MVo "${_sha}.sig" "${_MIRROR}/SHA256.sig" \ 194 >/dev/null 2>&1 || return 0 # empty directory 195 unpriv -f "${_sha}" signify -Veq -x ${_sha}.sig -m ${_sha} -p \ 196 /etc/signify/openbsd-${_OSrev}-syspatch.pub >/dev/null 197 198 # if no earlier version of all files contained in the syspatch exists 199 # on the system, it means a missing set so skip it 200 grep -Eo "syspatch${_OSrev}-[[:digit:]]{3}_[[:alnum:]_]+" ${_sha} | 201 while read _c; do _c=${_c##syspatch${_OSrev}-} && 202 [[ -n ${_l} ]] && echo ${_c} | grep -qw -- "${_l}" || echo ${_c} 203 done | while read _p; do 204 _cmd="ftp -MVo - ${_MIRROR}/syspatch${_OSrev}-${_p}.tgz" 205 { unpriv ${_cmd} | tar tzf -; } 2>/dev/null | while read _f; do 206 [[ -f /${_f} ]] || continue && echo ${_p} && pkill -u \ 207 _syspatch -xf "${_cmd}" || true && break 208 done 209 done | sort -V 210} 211 212rollback_patch() 213{ 214 local _edir _file _files _patch _ret=0 215 216 _patch="$(ls_installed | tail -1)" 217 [[ -n ${_patch} ]] 218 219 _edir=${_TMP}/${_patch}-rollback 220 _patch=${_OSrev}-${_patch} 221 222 trap '' INT 223 echo "Reverting patch ${_patch##${_OSrev}-}" 224 install -d ${_edir} 225 226 _files="$(tar xvzphf ${_PDIR}/${_patch}/rollback.tgz -C ${_edir})" 227 checkfs ${_files} ${_PDIR} # check for read-only /var/syspatch 228 229 for _file in ${_files}; do 230 ((_ret == 0)) || break 231 if [[ ${_file} == @(bsd|bsd.mp) ]]; then 232 install_kernel ${_edir}/${_file} || _ret=$? 233 # remove the backup kernel if all kernel syspatches have 234 # been reverted; non-fatal 235 cmp -s /bsd /bsd.syspatch${_OSrev} && 236 rm -f /bsd.syspatch${_OSrev} 237 else 238 install_file ${_edir}/${_file} /${_file} || _ret=$? 239 fi 240 done 241 242 ((_ret != 0)) || rm -r ${_PDIR}/${_patch} || _ret=$? 243 ((_ret == 0)) || 244 sp_err "Failed to revert patch ${_patch##${_OSrev}-}" ${_ret} 245 rm -rf ${_edir} # don't fill up /tmp when using `-R'; non-fatal 246 trap exit INT 247} 248 249sp_cleanup() 250{ 251 local _d _k 252 253 # remove non matching release /var/syspatch/ content 254 for _d in ${_PDIR}/{.[!.],}*; do 255 [[ -e ${_d} ]] || continue 256 [[ ${_d##*/} == ${_OSrev}-+([[:digit:]])_+([[:alnum:]]|_) ]] && 257 [[ -f ${_d}/rollback.tgz ]] || rm -r ${_d} 258 done 259 260 # remove non matching release backup kernel 261 for _k in /bsd.syspatch+([[:digit:]]); do 262 [[ -f ${_k} ]] || continue 263 [[ ${_k} == /bsd.syspatch${_OSrev} ]] || rm ${_k} 264 done 265 266 # in case a patch added a new directory (install -D) 267 mtree -qdef /etc/mtree/4.4BSD.dist -p / -U >/dev/null 268 [[ -f /var/sysmerge/xetc.tgz ]] && 269 mtree -qdef /etc/mtree/BSD.x11.dist -p / -U >/dev/null 270} 271 272unpriv() 273{ 274 local _file=$2 _user=_syspatch 275 276 if [[ $1 == -f && -n ${_file} ]]; then 277 >${_file} 278 chown "${_user}" "${_file}" 279 chmod 0711 ${_TMP} 280 shift 2 281 fi 282 (($# >= 1)) 283 284 eval su -s /bin/sh ${_user} -c "'$@'" 285} 286 287[[ $@ == @(|-[[:alpha:]]) ]] || usage; [[ $@ == @(|-(c|r)) ]] && 288 (($(id -u) != 0)) && sp_err "${0##*/}: need root privileges" 289 290# only run on release (not -current nor -stable) 291set -A _KERNV -- $(sysctl -n kern.version | 292 sed 's/^OpenBSD \([0-9]\.[0-9]\)\([^ ]*\).*/\1 \2/;q') 293((${#_KERNV[*]} > 1)) && sp_err "Unsupported release: ${_KERNV[0]}${_KERNV[1]}" 294 295_OSrev=${_KERNV[0]%.*}${_KERNV[0]#*.} 296[[ -n ${_OSrev} ]] 297 298_MIRROR=$(while read _line; do _line=${_line%%#*}; [[ -n ${_line} ]] && 299 print -r -- "${_line}"; done </etc/installurl | tail -1) 2>/dev/null 300[[ ${_MIRROR} == @(file|http|https)://*/*[!/] ]] || 301 sp_err "${0##*/}: invalid URL configured in /etc/installurl" 302_MIRROR="${_MIRROR}/syspatch/${_KERNV[0]}/$(machine)" 303 304(($(sysctl -n hw.ncpufound) > 1)) && _BSDMP=true || _BSDMP=false 305_PDIR="/var/syspatch" 306_TMP=$(mktemp -d -p /tmp syspatch.XXXXXXXXXX) 307 308readonly _BSDMP _KERNV _MIRROR _OSrev _PDIR _TMP 309 310trap 'set +e; rm -rf "${_TMP}"' EXIT 311trap exit HUP INT TERM 312 313while getopts clRr arg; do 314 case ${arg} in 315 c) ls_missing ;; 316 l) ls_installed ;; 317 R) while [[ -n $(ls_installed) ]]; do rollback_patch; done ;; 318 r) rollback_patch ;; 319 *) usage ;; 320 esac 321done 322shift $((OPTIND - 1)) 323(($# != 0)) && usage 324 325if ((OPTIND == 1)); then 326 _PATCHES=$(ls_missing) 327 for _PATCH in ${_PATCHES}; do 328 apply_patch ${_OSrev}-${_PATCH} 329 done 330 sp_cleanup 331fi 332