1#!/bin/ksh 2# 3# $OpenBSD: syspatch.sh,v 1.36 2016/11/04 14:18:45 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 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 _m _patch _patches="$(ls_missing)" 68 69 for _patch in ${_patches}; do 70 fetch_and_verify "${_patch}" 71 trap "" 2 72 apply_patch "${_patch}" 73 trap "rm -rf ${_TMP}; exit 1" 2 74 done 75 76 sp_cleanup 77} 78 79checkfs() 80{ 81 local _files="${@}" 82 [[ -n ${_files} ]] 83 84 local _d _f 85 86 for _d in $(stat -qf "%Sd" $(for _f in ${_files}; do echo /${_f%/*} 87 done | uniq)); do mount | grep -q "^/dev/${_d} .* read-only" && 88 sp_err "Remote or read-only filesystem, aborting" 89 done 90} 91 92create_rollback() 93{ 94 local _file _patch=$1 _rbfiles 95 [[ -n ${_patch} ]] 96 shift 97 local _files="${@}" 98 [[ -n ${_files} ]] 99 100 [[ -d ${_PDIR}/${_REL} ]] || install -d ${_PDIR}/${_REL} 101 102 for _file in ${_files}; do 103 [[ -f /${_file} ]] || continue 104 _rbfiles="${_rbfiles} ${_file}" 105 done 106 107 if ! (cd / && 108 # GENERIC.MP: substitute bsd.mp->bsd and bsd.sp->bsd 109 if ${_BSDMP} && 110 tar -tzf ${_TMP}/${_patch}.tgz bsd >/dev/null 2>&1; then 111 tar -czf ${_PDIR}/${_REL}/rollback-${_patch}.tgz \ 112 -s '/^bsd.mp$//' -s '/^bsd$/bsd.mp/' \ 113 -s '/^bsd.sp$/bsd/' bsd.sp ${_rbfiles} 114 else 115 tar -czf ${_PDIR}/${_REL}/rollback-${_patch}.tgz \ 116 ${_rbfiles} 117 fi 118 ); then 119 rm ${_PDIR}/${_REL}/rollback-${_patch}.tgz 120 sp_err "Failed to create rollback for ${_patch}" 121 fi 122} 123 124fetch_and_verify() 125{ 126 # XXX privsep ala installer 127 local _patch=$1 128 [[ -n ${_patch} ]] 129 130 local _key="/etc/signify/openbsd-${_RELINT}-syspatch.pub" _p 131 132 ${_FETCH} -o "${_TMP}/SHA256.sig" "${PATCH_PATH}/SHA256.sig" 133 ${_FETCH} -mD "Applying" -o "${_TMP}/${_patch}.tgz" \ 134 "${PATCH_PATH}/${_patch}.tgz" 135 (cd ${_TMP} && 136 /usr/bin/signify -qC -p ${_key} -x SHA256.sig ${_patch}.tgz) 137} 138 139install_file() 140{ 141 # XXX handle symlinks, dir->file, file->dir? 142 local _src=$1 _dst=$2 143 [[ -f ${_src} && -f ${_dst} ]] 144 145 local _fmode _fown _fgrp 146 eval $(stat -f "_fmode=%OMp%OLp _fown=%Su _fgrp=%Sg" ${_src}) 147 148 install -DFS -m ${_fmode} -o ${_fown} -g ${_fgrp} ${_src} ${_dst} 149} 150 151install_kernel() 152{ 153 local _bsd=/bsd _kern=$1 154 [[ -n ${_kern} ]] 155 156 # only save the original release kernel once 157 [[ -f /bsd.rollback${_RELINT} ]] || 158 install -FSp /bsd /bsd.rollback${_RELINT} 159 160 if ${_BSDMP}; then 161 [[ ${_kern##*/} == bsd ]] && _bsd=/bsd.sp 162 fi 163 164 if [[ -n ${_bsd} ]]; then 165 install -FS ${_kern} ${_bsd} 166 fi 167} 168 169ls_installed() 170{ 171 local _p 172 for _p in ${_PDIR}/${_REL}/*; do 173 _p=${_p:##*/} 174 [[ ${_p} = rollback-syspatch-${_RELINT}-*.tgz ]] && 175 _p=${_p#rollback-} && echo ${_p%.tgz} 176 done | sort -V 177} 178 179ls_missing() 180{ 181 # XXX match with installed sets (comp, x...)? 182 local _a _installed 183 _installed="$(ls_installed)" 184 185 ${_FETCH} -o "${_TMP}/index.txt" "${PATCH_PATH}/index.txt" 186 187 for _a in $(sed 's/^.* //;s/^M//;s/.tgz$//' ${_TMP}/index.txt | 188 grep "^syspatch-${_RELINT}-.*$" | sort -V); do 189 if [[ -n ${_installed} ]]; then 190 echo ${_a} | grep -qw -- "${_installed}" || echo ${_a} 191 else 192 echo ${_a} 193 fi 194 done 195} 196 197rollback_patch() 198{ 199 needs_root 200 local _explodir _file _files _patch 201 202 _patch="$(ls_installed | sort -V | tail -1)" 203 [[ -n ${_patch} ]] 204 205 echo "Reverting ${_patch}" 206 _explodir=${_TMP}/rollback-${_patch} 207 mkdir -p ${_explodir} 208 209 _files="$(tar xvzphf ${_PDIR}/${_REL}/rollback-${_patch}.tgz -C \ 210 ${_explodir})" 211 checkfs ${_files} 212 213 for _file in ${_files}; do 214 if [[ ${_file} == @(bsd|bsd.mp) ]]; then 215 install_kernel ${_explodir}/${_file} || 216 sp_err "Failed to revert ${_patch} (/${_file})" 217 else 218 install_file ${_explodir}/${_file} /${_file} || 219 sp_err "Failed to revert ${_patch} (/${_file})" 220 fi 221 done 222 223 rm ${_PDIR}/${_REL}/rollback-${_patch}.tgz \ 224 ${_PDIR}/${_REL}/${_patch#syspatch-${_RELINT}-}.patch.sig 225 226 sp_cleanup 227} 228 229sp_cleanup() 230{ 231 local _d _k _m 232 233 # remove non matching release /var/syspatch/ content 234 for _d in ${_PDIR}/*; do 235 [[ -e ${_d} ]] || continue 236 [[ ${_d:##*/} == ${_REL} ]] || rm -r ${_d} 237 done 238 239 # remove non matching release rollback kernel 240 for _k in /bsd.rollback*; do 241 [[ -f ${_k} ]] || continue 242 [[ ${_k} == /bsd.rollback${_RELINT} ]] || rm ${_k} 243 done 244 245 # remove rollback kernel if all kernel syspatches have been reverted 246 cmp -s /bsd /bsd.rollback${_RELINT} && rm /bsd.rollback${_RELINT} 247 248 # non-fatal: the syspatch|rollback tarball should have correct perms 249 for _m in 4.4BSD BSD.x11; do 250 mtree -qdef /etc/mtree/${_m}.dist -p / -U >/dev/null || true 251 done 252} 253 254# only run on release (not -current nor -stable) 255set -A _KERNV -- $(sysctl -n kern.version | 256 sed 's/^OpenBSD \([0-9]\.[0-9]\)\([^ ]*\).*/\1 \2/;q') 257[[ -z ${_KERNV[1]} ]] 258 259# check args 260[[ $@ == @(|-[[:alpha:]]) ]] || usage 261 262# XXX to be discussed; check for $ARCH? 263[[ -n ${PATCH_PATH} ]] 264[[ -d ${PATCH_PATH} ]] && PATCH_PATH="file://$(readlink -f ${PATCH_PATH})" 265[[ ${PATCH_PATH:%%://*} == @(file|ftp|http|https) ]] 266 267[[ $(sysctl -n hw.ncpufound) -gt 1 ]] && _BSDMP=true || _BSDMP=false 268_FETCH="/usr/bin/ftp -MVk ${FTP_KEEPALIVE-0}" 269_PDIR="/var/syspatch" 270_REL=${_KERNV[0]} 271_RELINT=${_REL%\.*}${_REL#*\.} 272_TMP=$(mktemp -d -p /tmp syspatch.XXXXXXXXXX) 273readonly _BSDMP _FETCH _PDIR _REL _RELINT _TMP 274[[ -n ${_REL} && -n ${_RELINT} ]] 275 276trap "rm -rf ${_TMP}; exit 1" 2 3 9 13 15 ERR 277 278while getopts clr arg; do 279 case ${arg} in 280 c) ls_missing;; 281 l) ls_installed;; 282 r) rollback_patch;; 283 *) usage;; 284 esac 285done 286shift $(( OPTIND -1 )) 287[[ $# -ne 0 ]] && usage 288 289[[ ${OPTIND} != 1 ]] || apply_patches 290 291rm -rf ${_TMP} 292