15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/bin/bash -p
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2011 The Chromium Authors. All rights reserved.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# usage: dirdiffer.sh old_dir new_dir patch_dir
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# dirdiffer creates a patch directory patch_dir that represents the difference
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# between old_dir and new_dir. patch_dir can be used with dirpatcher to
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# recreate new_dir given old_dir.
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# dirdiffer operates recursively, properly handling ordinary files, symbolic
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# links, and directories, as they are found in new_dir. Symbolic links and
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# directories are always replicated as-is in patch_dir. Ordinary files will
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# be represented at the appropriate location in patch_dir by one of the
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# following:
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  - a binary diff prepared by goobsdiff that can transform the file at the
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#    same position in old_dir to the version in new_dir, but only when such a
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#    file already exists in old_dir and is an ordinary file. These files are
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#    given a "$gbs" suffix.
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  - a bzip2-compressed copy of the new file from new_dir; in patch_dir, the
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#    new file will have a "$bz2" suffix.
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  - a gzip-compressed copy of the new file from new_dir; in patch_dir, the
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#    new file will have a "$gz" suffix.
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  - an xz/lzma2-compressed copy of the new file from new_dir; in patch_dir,
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#    the new file will have an "$xz" suffix.
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  - an uncompressed copy of the new file from new_dir; in patch_dir, the
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#    new file will have a "$raw" suffix.
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The unconventional suffixes are used because they aren't likely to occur in
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# filenames.
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Of these options, the smallest possible representation is chosen. Note that
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# goobsdiff itself will also compress various sections of a binary diff with
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# bzip2, gzip, or xz/lzma2, or leave them uncompressed, according to which is
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# smallest. The approach of choosing the smallest possible representation is
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# time-consuming but given the choices of compressors results in an overall
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# size reduction of about 3%-5% relative to using bzip2 as the only
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# compressor; bzip2 is generally more effective for these data sets than gzip,
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# and xz/lzma2 more effective than bzip2.
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# For large input files, goobsdiff is also very time-consuming and
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# memory-intensive. The overall "wall clock time" spent preparing a patch_dir
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# representing the differences between Google Chrome's 6.0.422.0 and 6.0.427.0
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# versioned directories from successive weekly dev channel releases on a
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 2.53GHz dual-core 4GB MacBook Pro is 3 minutes. Reconstructing new_dir with
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# dirpatcher is much quicker; in the above configuration, only 10 seconds are
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# needed for reconstruction.
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# After creating a full patch_dir structure, but before returning, dirpatcher
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# is invoked to attempt to recreate new_dir in a temporary location given
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# old_dir and patch_dir. The recreated new_dir is then compared against the
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# original new_dir as a verification step. Should verification fail, dirdiffer
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# exits with a nonzero status, and patch_dir should not be used.
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Environment variables:
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# DIRDIFFER_EXCLUDE
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   When an entry in new_dir matches this regular expression, it will not be
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   included in patch_dir. All prospective paths in new_dir will be matched
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   against this regular expression, including directories. If a directory
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   matches this pattern, dirdiffer will also ignore the directory's contents.
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# DIRDIFFER_NO_DIFF
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   When an entry in new_dir matches this regular expression, it will not be
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   represented in patch_dir by a $gbs file prepared by goobsdiff. It will only
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   appear as a $bz2, $gz, or $raw file. Only files in new_dir, not
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   directories,  will be matched against this regular expression.
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Exit codes:
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  0  OK
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  1  Unknown failure
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  2  Incorrect number of parameters
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  3  Input directories do not exist or are not directories
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  4  Output directory already exists
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  5  Parent of output directory does not exist or is not a directory
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  6  An input or output directories contains another
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  7  Could not create output directory
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  8  File already exists in output directory
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  9  Found an irregular file (non-directory, file, or symbolic link) in input
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 10  Could not create symbolic link
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 11  File copy failed
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 12  bzip2 compression failed
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 13  gzip compression failed
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 14  xz/lzma2 compression failed
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 15  Patch creation failed
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 16  Verification failed
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 17  Could not set mode (permissions)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 18  Could not set modification time
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 19  Invalid regular expression (irregular expression?)
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)set -eu
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Environment sanitization. Set a known-safe PATH. Clear environment variables
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# that might impact the interpreter's operation. The |bash -p| invocation
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# on the #! line takes the bite out of BASH_ENV, ENV, and SHELLOPTS (among
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# other features), but clearing them here ensures that they won't impact any
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# shell scripts used as utility programs. SHELLOPTS is read-only and can't be
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# unset, only unexported.
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)export PATH="/usr/bin:/bin:/usr/sbin:/sbin"
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)export -n SHELLOPTS
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)shopt -s dotglob nullglob
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# find_tool looks for an executable file named |tool_name|:
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  - in the same directory as this script,
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  - if this script is located in a Chromium source tree, at the expected
1091e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)#    Release output location in the Mac out directory,
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  - as above, but in the Debug output location
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# If found in any of the above locations, the script's path is output.
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Otherwise, this function outputs |tool_name| as a fallback, allowing it to
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# be found (or not) by an ordinary ${PATH} search.
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)find_tool() {
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local tool_name="${1}"
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local script_dir
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  script_dir="$(dirname "${0}")"
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local tool="${script_dir}/${tool_name}"
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -f "${tool}" ]] && [[ -x "${tool}" ]]; then
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    echo "${tool}"
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local script_dir_phys
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  script_dir_phys="$(cd "${script_dir}" && pwd -P)"
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${script_dir_phys}" =~ ^(.*)/src/chrome/installer/mac$ ]]; then
1291e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    tool="${BASH_REMATCH[1]}/src/out/Release/${tool_name}"
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -f "${tool}" ]] && [[ -x "${tool}" ]]; then
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      echo "${tool}"
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1351e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    tool="${BASH_REMATCH[1]}/src/out/Debug/${tool_name}"
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -f "${tool}" ]] && [[ -x "${tool}" ]]; then
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      echo "${tool}"
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  echo "${tool_name}"
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ME="$(basename "${0}")"
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly ME
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)DIRPATCHER="$(dirname "${0}")/dirpatcher.sh"
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly DIRPATCHER
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)GOOBSDIFF="$(find_tool goobsdiff)"
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly GOOBSDIFF
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly BZIP2="bzip2"
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly GZIP="gzip"
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)XZ="$(find_tool xz)"
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly XZ
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly GBS_SUFFIX='$gbs'
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly BZ2_SUFFIX='$bz2'
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly GZ_SUFFIX='$gz'
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly XZ_SUFFIX='$xz'
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly PLAIN_SUFFIX='$raw'
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Workaround for http://code.google.com/p/chromium/issues/detail?id=83180#c3
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# In bash 4.0, "declare VAR" no longer initializes VAR if not already set.
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles): ${DIRDIFFER_EXCLUDE:=}
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles): ${DIRDIFFER_NO_DIFF:=}
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)err() {
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local error="${1}"
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  echo "${ME}: ${error}" >& 2
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)declare -a g_cleanup g_verify_exclude
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)cleanup() {
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local status=${?}
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  trap - EXIT
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  trap '' HUP INT QUIT TERM
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ ${status} -ge 128 ]]; then
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "Caught signal $((${status} - 128))"
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${#g_cleanup[@]}" -gt 0 ]]; then
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -rf "${g_cleanup[@]}"
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  exit ${status}
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)copy_mode_and_time() {
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local new_file="${1}"
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local patch_file="${2}"
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local mode
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  mode="$(stat "-f%OMp%OLp" "${new_file}")"
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! chmod -h "${mode}" "${patch_file}"; then
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 17
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! [[ -L "${patch_file}" ]]; then
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Symbolic link modification times can't be copied because there's no
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # shell tool that provides direct access to lutimes. Instead, the symbolic
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # link was created with rsync, which already copied the timestamp with
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # lutimes.
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! touch -r "${new_file}" "${patch_file}"; then
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 18
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)file_size() {
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local file="${1}"
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  stat -f %z "${file}"
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)make_patch_file() {
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local old_file="${1}"
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local new_file="${2}"
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local patch_file="${3}"
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local uncompressed_file="${patch_file}${PLAIN_SUFFIX}"
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! cp "${new_file}" "${uncompressed_file}"; then
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 11
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local uncompressed_size
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  uncompressed_size="$(file_size "${new_file}")"
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local keep_file="${uncompressed_file}"
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local keep_size="${uncompressed_size}"
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local bz2_file="${patch_file}${BZ2_SUFFIX}"
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -e "${bz2_file}" ]]; then
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "${bz2_file} already exists"
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 8
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! "${BZIP2}" -9c < "${new_file}" > "${bz2_file}"; then
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "couldn't compress ${new_file} to ${bz2_file} with ${BZIP2}"
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 12
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local bz2_size
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bz2_size="$(file_size "${bz2_file}")"
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${bz2_size}" -ge "${keep_size}" ]]; then
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -f "${bz2_file}"
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -f "${keep_file}"
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    keep_file="${bz2_file}"
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    keep_size="${bz2_size}"
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local gz_file="${patch_file}${GZ_SUFFIX}"
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -e "${gz_file}" ]]; then
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "${gz_file} already exists"
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 8
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! "${GZIP}" -9cn < "${new_file}" > "${gz_file}"; then
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "couldn't compress ${new_file} to ${gz_file} with ${GZIP}"
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 13
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local gz_size
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gz_size="$(file_size "${gz_file}")"
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${gz_size}" -ge "${keep_size}" ]]; then
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -f "${gz_file}"
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -f "${keep_file}"
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    keep_file="${gz_file}"
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    keep_size="${gz_size}"
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local xz_flags=("-c")
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If the file looks like a Mach-O file, including a universal/fat file, add
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the x86 BCJ filter, which results in slightly better compression of x86
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # and x86_64 executables. Mach-O files might contain other architectures,
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # but they aren't currently expected in Chrome.
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local file_output
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  file_output="$(file "${new_file}" 2> /dev/null || true)"
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${file_output}" =~ Mach-O ]]; then
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    xz_flags+=("--x86")
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Use an lzma2 encoder. This is equivalent to xz -9 -e, but allows filters
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # to precede the compressor.
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  xz_flags+=("--lzma2=preset=9e")
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local xz_file="${patch_file}${XZ_SUFFIX}"
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -e "${xz_file}" ]]; then
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "${xz_file} already exists"
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 8
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! "${XZ}" "${xz_flags[@]}" < "${new_file}" > "${xz_file}"; then
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "couldn't compress ${new_file} to ${xz_file} with ${XZ}"
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 14
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local xz_size
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  xz_size="$(file_size "${xz_file}")"
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${xz_size}" -ge "${keep_size}" ]]; then
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -f "${xz_file}"
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -f "${keep_file}"
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    keep_file="${xz_file}"
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    keep_size="${xz_size}"
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -f "${old_file}" ]] && ! [[ -L "${old_file}" ]] &&
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     ! [[ "${new_file}" =~ ${DIRDIFFER_NO_DIFF} ]]; then
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local gbs_file="${patch_file}${GBS_SUFFIX}"
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -e "${gbs_file}" ]]; then
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "${gbs_file} already exists"
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 8
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! "${GOOBSDIFF}" "${old_file}" "${new_file}" "${gbs_file}"; then
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "couldn't create ${gbs_file} by comparing ${old_file} to ${new_file}"
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 15
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local gbs_size
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gbs_size="$(file_size "${gbs_file}")"
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ "${gbs_size}" -ge "${keep_size}" ]]; then
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      rm -f "${gbs_file}"
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      rm -f "${keep_file}"
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      keep_file="${gbs_file}"
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      keep_size="${gbs_size}"
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy_mode_and_time "${new_file}" "${keep_file}"
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)make_patch_symlink() {
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local new_file="${1}"
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local patch_file="${2}"
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # local target
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # target="$(readlink "${new_file}")"
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # ln -s "${target}" "${patch_file}"
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Use rsync instead of the above, as it's the only way to preserve the
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # timestamp of a symbolic link using shell tools.
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! rsync -lt "${new_file}" "${patch_file}"; then
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 10
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy_mode_and_time "${new_file}" "${patch_file}"
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)make_patch_dir() {
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local old_dir="${1}"
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local new_dir="${2}"
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local patch_dir="${3}"
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! mkdir "${patch_dir}"; then
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 7
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local new_file
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for new_file in "${new_dir}/"*; do
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local file="${new_file:${#new_dir} + 1}"
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local old_file="${old_dir}/${file}"
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local patch_file="${patch_dir}/${file}"
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ "${new_file}" =~ ${DIRDIFFER_EXCLUDE} ]]; then
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      g_verify_exclude+=("${new_file}")
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -e "${patch_file}" ]]; then
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "${patch_file} already exists"
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 8
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -L "${new_file}" ]]; then
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      make_patch_symlink "${new_file}" "${patch_file}"
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif [[ -d "${new_file}" ]]; then
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      make_patch_dir "${old_file}" "${new_file}" "${patch_file}"
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif [[ ! -f "${new_file}" ]]; then
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "can't handle irregular file ${new_file}"
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 9
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      make_patch_file "${old_file}" "${new_file}" "${patch_file}"
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  done
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy_mode_and_time "${new_dir}" "${patch_dir}"
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)verify_patch_dir() {
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local old_dir="${1}"
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local new_dir="${2}"
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local patch_dir="${3}"
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local verify_temp_dir verify_dir
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  verify_temp_dir="$(mktemp -d -t "${ME}")"
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  g_cleanup+=("${verify_temp_dir}")
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  verify_dir="${verify_temp_dir}/patched"
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! "${DIRPATCHER}" "${old_dir}" "${patch_dir}" "${verify_dir}"; then
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "patch application for verification failed"
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 16
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # rsync will print a line for any file, directory, or symbolic link that
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # differs or exists only in one directory. As used here, it correctly
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # considers link targets, file contents, permissions, and timestamps.
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local rsync_command=(rsync -clprt --delete --out-format=%n \
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       "${new_dir}/" "${verify_dir}")
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ ${#g_verify_exclude[@]} -gt 0 ]]; then
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local exclude
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for exclude in "${g_verify_exclude[@]}"; do
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # ${g_verify_exclude[@]} contains paths in ${new_dir}. Strip off
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # ${new_dir} from the beginning of each, but leave a leading "/" so that
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # rsync treats them as being at the root of the "transfer."
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      rsync_command+=("--exclude" "${exclude:${#new_dir}}")
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    done
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local rsync_output
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! rsync_output="$("${rsync_command[@]}")"; then
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "rsync for verification failed"
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 16
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  rm -rf "${verify_temp_dir}"
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  unset g_cleanup[${#g_cleanup[@]}]
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${rsync_output}" ]]; then
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "verification failed"
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 16
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# shell_safe_path ensures that |path| is safe to pass to tools as a
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# command-line argument. If the first character in |path| is "-", "./" is
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# prepended to it. The possibly-modified |path| is output.
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)shell_safe_path() {
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local path="${1}"
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${path:0:1}" = "-" ]]; then
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    echo "./${path}"
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    echo "${path}"
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)dirs_contained() {
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local dir1="${1}/"
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local dir2="${2}/"
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${dir1:0:${#dir2}}" = "${dir2}" ]] ||
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     [[ "${dir2:0:${#dir1}}" = "${dir1}" ]]; then
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 0
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 1
4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)usage() {
4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  echo "usage: ${ME} old_dir new_dir patch_dir" >& 2
4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)main() {
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local old_dir new_dir patch_dir
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  old_dir="$(shell_safe_path "${1}")"
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  new_dir="$(shell_safe_path "${2}")"
4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  patch_dir="$(shell_safe_path "${3}")"
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  trap cleanup EXIT HUP INT QUIT TERM
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! [[ -d "${old_dir}" ]] || ! [[ -d "${new_dir}" ]]; then
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "old_dir and new_dir must exist and be directories"
4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    usage
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 3
4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -e "${patch_dir}" ]]; then
4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "patch_dir must not exist"
4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    usage
4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 4
4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local patch_dir_parent
4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  patch_dir_parent="$(dirname "${patch_dir}")"
4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! [[ -d "${patch_dir_parent}" ]]; then
4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "patch_dir parent directory must exist and be a directory"
4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    usage
4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 5
4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The weird conditional structure is because the status of the RE comparison
4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # needs to be available in ${?} without conflating it with other conditions
4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # or negating it. Only a status of 2 from the =~ operator indicates an
4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # invalid regular expression.
4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${DIRDIFFER_EXCLUDE}" ]]; then
4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ "" =~ ${DIRDIFFER_EXCLUDE} ]]; then
4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      true
5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif [[ ${?} -eq 2 ]]; then
5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "DIRDIFFER_EXCLUDE contains an invalid regular expression"
5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 19
5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
5045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${DIRDIFFER_NO_DIFF}" ]]; then
5075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ "" =~ ${DIRDIFFER_NO_DIFF} ]]; then
5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      true
5095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif [[ ${?} -eq 2 ]]; then
5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "DIRDIFFER_NO_DIFF contains an invalid regular expression"
5115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 19
5125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
5135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local old_dir_phys new_dir_phys patch_dir_parent_phys patch_dir_phys
5165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  old_dir_phys="$(cd "${old_dir}" && pwd -P)"
5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  new_dir_phys="$(cd "${new_dir}" && pwd -P)"
5185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  patch_dir_parent_phys="$(cd "${patch_dir_parent}" && pwd -P)"
5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  patch_dir_phys="${patch_dir_parent_phys}/$(basename "${patch_dir}")"
5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if dirs_contained "${old_dir_phys}" "${new_dir_phys}" ||
5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     dirs_contained "${old_dir_phys}" "${patch_dir_phys}" ||
5235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     dirs_contained "${new_dir_phys}" "${patch_dir_phys}"; then
5245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "directories must not contain one another"
5255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    usage
5265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 6
5275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
5285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  g_cleanup[${#g_cleanup[@]}]="${patch_dir}"
5305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  make_patch_dir "${old_dir}" "${new_dir}" "${patch_dir}"
5325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  verify_patch_dir "${old_dir}" "${new_dir}" "${patch_dir}"
5345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  unset g_cleanup[${#g_cleanup[@]}]
5365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  trap - EXIT
5375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
5385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if [[ ${#} -ne 3 ]]; then
5405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  usage
5415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  exit 2
5425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)fi
5435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)main "${@}"
5455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)exit ${?}
546