1#!/bin/bash -p 2 3# Copyright (c) 2011 The Chromium Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7# usage: dirpatcher.sh old_dir patch_dir new_dir 8# 9# dirpatcher creates new_dir from patch_dir by decompressing and copying 10# files, and using goobspatch to apply binary diffs to files in old_dir. 11# 12# dirpatcher performs the inverse operation to dirdiffer. For more details, 13# consult dirdiffer.sh. 14# 15# Exit codes: 16# 0 OK 17# 1 Unknown failure 18# 2 Incorrect number of parameters 19# 3 Input directories do not exist or are not directories 20# 4 Output directory already exists 21# 5 Parent of output directory does not exist or is not a directory 22# 6 An input or output directories contains another 23# 7 Could not create output directory 24# 8 File already exists in output directory 25# 9 Found an irregular file (non-directory, file, or symbolic link) in input 26# 10 Could not create symbolic link 27# 11 Unrecognized file extension 28# 12 Attempt to patch a nonexistent or non-regular file 29# 13 Patch application failed 30# 14 File decompression failed 31# 15 File copy failed 32# 16 Could not set mode (permissions) 33# 17 Could not set modification time 34 35set -eu 36 37# Environment sanitization. Set a known-safe PATH. Clear environment variables 38# that might impact the interpreter's operation. The |bash -p| invocation 39# on the #! line takes the bite out of BASH_ENV, ENV, and SHELLOPTS (among 40# other features), but clearing them here ensures that they won't impact any 41# shell scripts used as utility programs. SHELLOPTS is read-only and can't be 42# unset, only unexported. 43export PATH="/usr/bin:/bin:/usr/sbin:/sbin" 44unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT 45export -n SHELLOPTS 46 47shopt -s dotglob nullglob 48 49# find_tool looks for an executable file named |tool_name|: 50# - in the same directory as this script, 51# - if this script is located in a Chromium source tree, at the expected 52# Release output location in the Mac out directory, 53# - as above, but in the Debug output location 54# If found in any of the above locations, the script's path is output. 55# Otherwise, this function outputs |tool_name| as a fallback, allowing it to 56# be found (or not) by an ordinary ${PATH} search. 57find_tool() { 58 local tool_name="${1}" 59 60 local script_dir 61 script_dir="$(dirname "${0}")" 62 63 local tool="${script_dir}/${tool_name}" 64 if [[ -f "${tool}" ]] && [[ -x "${tool}" ]]; then 65 echo "${tool}" 66 return 67 fi 68 69 local script_dir_phys 70 script_dir_phys="$(cd "${script_dir}" && pwd -P)" 71 if [[ "${script_dir_phys}" =~ ^(.*)/src/chrome/installer/mac$ ]]; then 72 tool="${BASH_REMATCH[1]}/src/out/Release/${tool_name}" 73 if [[ -f "${tool}" ]] && [[ -x "${tool}" ]]; then 74 echo "${tool}" 75 return 76 fi 77 78 tool="${BASH_REMATCH[1]}/src/out/Debug/${tool_name}" 79 if [[ -f "${tool}" ]] && [[ -x "${tool}" ]]; then 80 echo "${tool}" 81 return 82 fi 83 fi 84 85 echo "${tool_name}" 86} 87 88ME="$(basename "${0}")" 89readonly ME 90GOOBSPATCH="$(find_tool goobspatch)" 91readonly GOOBSPATCH 92readonly BUNZIP2="bunzip2" 93readonly GUNZIP="gunzip" 94XZDEC="$(find_tool xzdec)" 95readonly XZDEC 96readonly GBS_SUFFIX='$gbs' 97readonly BZ2_SUFFIX='$bz2' 98readonly GZ_SUFFIX='$gz' 99readonly XZ_SUFFIX='$xz' 100readonly PLAIN_SUFFIX='$raw' 101 102err() { 103 local error="${1}" 104 105 echo "${ME}: ${error}" >& 2 106} 107 108declare -a g_cleanup 109cleanup() { 110 local status=${?} 111 112 trap - EXIT 113 trap '' HUP INT QUIT TERM 114 115 if [[ ${status} -ge 128 ]]; then 116 err "Caught signal $((${status} - 128))" 117 fi 118 119 if [[ "${#g_cleanup[@]}" -gt 0 ]]; then 120 rm -rf "${g_cleanup[@]}" 121 fi 122 123 exit ${status} 124} 125 126copy_mode_and_time() { 127 local patch_file="${1}" 128 local new_file="${2}" 129 130 local mode 131 mode="$(stat "-f%OMp%OLp" "${patch_file}")" 132 if ! chmod -h "${mode}" "${new_file}"; then 133 exit 16 134 fi 135 136 if ! [[ -L "${new_file}" ]]; then 137 # Symbolic link modification times can't be copied because there's no 138 # shell tool that provides direct access to lutimes. Instead, the symbolic 139 # link was created with rsync, which already copied the timestamp with 140 # lutimes. 141 if ! touch -r "${patch_file}" "${new_file}"; then 142 exit 17 143 fi 144 fi 145} 146 147apply_patch() { 148 local old_file="${1}" 149 local patch_file="${2}" 150 local new_file="${3}" 151 local patcher="${4}" 152 153 if [[ -L "${old_file}" ]] || ! [[ -f "${old_file}" ]]; then 154 err "can't patch nonexistent or irregular file ${old_file}" 155 exit 12 156 fi 157 158 if ! "${patcher}" "${old_file}" "${new_file}" "${patch_file}"; then 159 err "couldn't create ${new_file} by applying ${patch_file} to ${old_file}" 160 exit 13 161 fi 162} 163 164decompress_file() { 165 local old_file="${1}" 166 local patch_file="${2}" 167 local new_file="${3}" 168 local decompressor="${4}" 169 170 if ! "${decompressor}" -c < "${patch_file}" > "${new_file}"; then 171 err "couldn't decompress ${patch_file} to ${new_file} with ${decompressor}" 172 exit 14 173 fi 174} 175 176copy_file() { 177 local old_file="${1}" 178 local patch_file="${2}" 179 local new_file="${3}" 180 local extra="${4}" 181 182 if ! cp "${patch_file}" "${new_file}"; then 183 exit 15 184 fi 185} 186 187patch_file() { 188 local old_file="${1}" 189 local patch_file="${2}" 190 local new_file="${3}" 191 192 local operation extra strip_length 193 194 if [[ "${patch_file: -${#GBS_SUFFIX}}" = "${GBS_SUFFIX}" ]]; then 195 operation="apply_patch" 196 extra="${GOOBSPATCH}" 197 strip_length=${#GBS_SUFFIX} 198 elif [[ "${patch_file: -${#BZ2_SUFFIX}}" = "${BZ2_SUFFIX}" ]]; then 199 operation="decompress_file" 200 extra="${BUNZIP2}" 201 strip_length=${#BZ2_SUFFIX} 202 elif [[ "${patch_file: -${#GZ_SUFFIX}}" = "${GZ_SUFFIX}" ]]; then 203 operation="decompress_file" 204 extra="${GUNZIP}" 205 strip_length=${#GZ_SUFFIX} 206 elif [[ "${patch_file: -${#XZ_SUFFIX}}" = "${XZ_SUFFIX}" ]]; then 207 operation="decompress_file" 208 extra="${XZDEC}" 209 strip_length=${#XZ_SUFFIX} 210 elif [[ "${patch_file: -${#PLAIN_SUFFIX}}" = "${PLAIN_SUFFIX}" ]]; then 211 operation="copy_file" 212 extra="patch" 213 strip_length=${#PLAIN_SUFFIX} 214 else 215 err "don't know how to operate on ${patch_file}" 216 exit 11 217 fi 218 219 old_file="${old_file:0:${#old_file} - ${strip_length}}" 220 new_file="${new_file:0:${#new_file} - ${strip_length}}" 221 222 if [[ -e "${new_file}" ]]; then 223 err "${new_file} already exists" 224 exit 8 225 fi 226 227 "${operation}" "${old_file}" "${patch_file}" "${new_file}" "${extra}" 228 229 copy_mode_and_time "${patch_file}" "${new_file}" 230} 231 232patch_symlink() { 233 local patch_file="${1}" 234 local new_file="${2}" 235 236 # local target 237 # target="$(readlink "${patch_file}")" 238 # ln -s "${target}" "${new_file}" 239 240 # Use rsync instead of the above, as it's the only way to preserve the 241 # timestamp of a symbolic link using shell tools. 242 if ! rsync -lt "${patch_file}" "${new_file}"; then 243 exit 10 244 fi 245 246 copy_mode_and_time "${patch_file}" "${new_file}" 247} 248 249patch_dir() { 250 local old_dir="${1}" 251 local patch_dir="${2}" 252 local new_dir="${3}" 253 254 if ! mkdir "${new_dir}"; then 255 exit 7 256 fi 257 258 local patch_file 259 for patch_file in "${patch_dir}/"*; do 260 local file="${patch_file:${#patch_dir} + 1}" 261 local old_file="${old_dir}/${file}" 262 local new_file="${new_dir}/${file}" 263 264 if [[ -e "${new_file}" ]]; then 265 err "${new_file} already exists" 266 exit 8 267 fi 268 269 if [[ -L "${patch_file}" ]]; then 270 patch_symlink "${patch_file}" "${new_file}" 271 elif [[ -d "${patch_file}" ]]; then 272 patch_dir "${old_file}" "${patch_file}" "${new_file}" 273 elif ! [[ -f "${patch_file}" ]]; then 274 err "can't handle irregular file ${patch_file}" 275 exit 9 276 else 277 patch_file "${old_file}" "${patch_file}" "${new_file}" 278 fi 279 done 280 281 copy_mode_and_time "${patch_dir}" "${new_dir}" 282} 283 284# shell_safe_path ensures that |path| is safe to pass to tools as a 285# command-line argument. If the first character in |path| is "-", "./" is 286# prepended to it. The possibly-modified |path| is output. 287shell_safe_path() { 288 local path="${1}" 289 if [[ "${path:0:1}" = "-" ]]; then 290 echo "./${path}" 291 else 292 echo "${path}" 293 fi 294} 295 296dirs_contained() { 297 local dir1="${1}/" 298 local dir2="${2}/" 299 300 if [[ "${dir1:0:${#dir2}}" = "${dir2}" ]] || 301 [[ "${dir2:0:${#dir1}}" = "${dir1}" ]]; then 302 return 0 303 fi 304 305 return 1 306} 307 308usage() { 309 echo "usage: ${ME} old_dir patch_dir new_dir" >& 2 310} 311 312main() { 313 local old_dir patch_dir new_dir 314 old_dir="$(shell_safe_path "${1}")" 315 patch_dir="$(shell_safe_path "${2}")" 316 new_dir="$(shell_safe_path "${3}")" 317 318 trap cleanup EXIT HUP INT QUIT TERM 319 320 if ! [[ -d "${old_dir}" ]] || ! [[ -d "${patch_dir}" ]]; then 321 err "old_dir and patch_dir must exist and be directories" 322 usage 323 exit 3 324 fi 325 326 if [[ -e "${new_dir}" ]]; then 327 err "new_dir must not exist" 328 usage 329 exit 4 330 fi 331 332 local new_dir_parent 333 new_dir_parent="$(dirname "${new_dir}")" 334 if ! [[ -d "${new_dir_parent}" ]]; then 335 err "new_dir parent directory must exist and be a directory" 336 usage 337 exit 5 338 fi 339 340 local old_dir_phys patch_dir_phys new_dir_parent_phys new_dir_phys 341 old_dir_phys="$(cd "${old_dir}" && pwd -P)" 342 patch_dir_phys="$(cd "${patch_dir}" && pwd -P)" 343 new_dir_parent_phys="$(cd "${new_dir_parent}" && pwd -P)" 344 new_dir_phys="${new_dir_parent_phys}/$(basename "${new_dir}")" 345 346 if dirs_contained "${old_dir_phys}" "${patch_dir_phys}" || 347 dirs_contained "${old_dir_phys}" "${new_dir_phys}" || 348 dirs_contained "${patch_dir_phys}" "${new_dir_phys}"; then 349 err "directories must not contain one another" 350 usage 351 exit 6 352 fi 353 354 g_cleanup+=("${new_dir}") 355 356 patch_dir "${old_dir}" "${patch_dir}" "${new_dir}" 357 358 unset g_cleanup[${#g_cleanup[@]}] 359 trap - EXIT 360} 361 362if [[ ${#} -ne 3 ]]; then 363 usage 364 exit 2 365fi 366 367main "${@}" 368exit ${?} 369