Home | History | Annotate | Download | only in mac
      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 ${?}
    369