OLD | NEW |
(Empty) | |
| 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 |
| 35 set -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. |
| 43 export PATH="/usr/bin:/bin:/usr/sbin:/sbin" |
| 44 unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT |
| 45 export -n SHELLOPTS |
| 46 |
| 47 shopt -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. |
| 57 find_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 |
| 88 ME="$(basename "${0}")" |
| 89 readonly ME |
| 90 GOOBSPATCH="$(find_tool goobspatch)" |
| 91 readonly GOOBSPATCH |
| 92 readonly BUNZIP2="bunzip2" |
| 93 readonly GUNZIP="gunzip" |
| 94 XZDEC="$(find_tool xzdec)" |
| 95 readonly XZDEC |
| 96 readonly GBS_SUFFIX='$gbs' |
| 97 readonly BZ2_SUFFIX='$bz2' |
| 98 readonly GZ_SUFFIX='$gz' |
| 99 readonly XZ_SUFFIX='$xz' |
| 100 readonly PLAIN_SUFFIX='$raw' |
| 101 |
| 102 err() { |
| 103 local error="${1}" |
| 104 |
| 105 echo "${ME}: ${error}" >& 2 |
| 106 } |
| 107 |
| 108 declare -a g_cleanup |
| 109 cleanup() { |
| 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 |
| 126 copy_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 |
| 147 apply_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 |
| 164 decompress_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 |
| 176 copy_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 |
| 187 patch_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 |
| 232 patch_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 |
| 249 patch_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. |
| 287 shell_safe_path() { |
| 288 local path="${1}" |
| 289 if [[ "${path:0:1}" = "-" ]]; then |
| 290 echo "./${path}" |
| 291 else |
| 292 echo "${path}" |
| 293 fi |
| 294 } |
| 295 |
| 296 dirs_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 |
| 308 usage() { |
| 309 echo "usage: ${ME} old_dir patch_dir new_dir" >& 2 |
| 310 } |
| 311 |
| 312 main() { |
| 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 |
| 362 if [[ ${#} -ne 3 ]]; then |
| 363 usage |
| 364 exit 2 |
| 365 fi |
| 366 |
| 367 main "${@}" |
| 368 exit ${?} |
OLD | NEW |