Convert browser_tests to Swarming.
[chromium-blink-merge.git] / chrome / installer / mac / dirpatcher.sh
blobb4d0620a580d11df2512fd0e5b75ceae3f0f2af6
1 #!/bin/bash -p
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.
7 # usage: dirpatcher.sh old_dir patch_dir new_dir
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.
12 # dirpatcher performs the inverse operation to dirdiffer. For more details,
13 # consult dirdiffer.sh.
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
35 set -eu
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
47 shopt -s dotglob nullglob
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}"
60 local script_dir
61 script_dir="$(dirname "${0}")"
63 local tool="${script_dir}/${tool_name}"
64 if [[ -f "${tool}" ]] && [[ -x "${tool}" ]]; then
65 echo "${tool}"
66 return
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
78 tool="${BASH_REMATCH[1]}/src/out/Debug/${tool_name}"
79 if [[ -f "${tool}" ]] && [[ -x "${tool}" ]]; then
80 echo "${tool}"
81 return
85 echo "${tool_name}"
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'
102 err() {
103 local error="${1}"
105 echo "${ME}: ${error}" >& 2
108 declare -a g_cleanup
109 cleanup() {
110 local status=${?}
112 trap - EXIT
113 trap '' HUP INT QUIT TERM
115 if [[ ${status} -ge 128 ]]; then
116 err "Caught signal $((${status} - 128))"
119 if [[ "${#g_cleanup[@]}" -gt 0 ]]; then
120 rm -rf "${g_cleanup[@]}"
123 exit ${status}
126 copy_mode_and_time() {
127 local patch_file="${1}"
128 local new_file="${2}"
130 local mode
131 mode="$(stat "-f%OMp%OLp" "${patch_file}")"
132 if ! chmod -h "${mode}" "${new_file}"; then
133 exit 16
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
147 apply_patch() {
148 local old_file="${1}"
149 local patch_file="${2}"
150 local new_file="${3}"
151 local patcher="${4}"
153 if [[ -L "${old_file}" ]] || ! [[ -f "${old_file}" ]]; then
154 err "can't patch nonexistent or irregular file ${old_file}"
155 exit 12
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
164 decompress_file() {
165 local old_file="${1}"
166 local patch_file="${2}"
167 local new_file="${3}"
168 local decompressor="${4}"
170 if ! "${decompressor}" -c < "${patch_file}" > "${new_file}"; then
171 err "couldn't decompress ${patch_file} to ${new_file} with ${decompressor}"
172 exit 14
176 copy_file() {
177 local old_file="${1}"
178 local patch_file="${2}"
179 local new_file="${3}"
180 local extra="${4}"
182 if ! cp "${patch_file}" "${new_file}"; then
183 exit 15
187 patch_file() {
188 local old_file="${1}"
189 local patch_file="${2}"
190 local new_file="${3}"
192 local operation extra strip_length
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
219 old_file="${old_file:0:${#old_file} - ${strip_length}}"
220 new_file="${new_file:0:${#new_file} - ${strip_length}}"
222 if [[ -e "${new_file}" ]]; then
223 err "${new_file} already exists"
224 exit 8
227 "${operation}" "${old_file}" "${patch_file}" "${new_file}" "${extra}"
229 copy_mode_and_time "${patch_file}" "${new_file}"
232 patch_symlink() {
233 local patch_file="${1}"
234 local new_file="${2}"
236 # local target
237 # target="$(readlink "${patch_file}")"
238 # ln -s "${target}" "${new_file}"
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
246 copy_mode_and_time "${patch_file}" "${new_file}"
249 patch_dir() {
250 local old_dir="${1}"
251 local patch_dir="${2}"
252 local new_dir="${3}"
254 if ! mkdir "${new_dir}"; then
255 exit 7
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}"
264 if [[ -e "${new_file}" ]]; then
265 err "${new_file} already exists"
266 exit 8
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}"
279 done
281 copy_mode_and_time "${patch_dir}" "${new_dir}"
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}"
296 dirs_contained() {
297 local dir1="${1}/"
298 local dir2="${2}/"
300 if [[ "${dir1:0:${#dir2}}" = "${dir2}" ]] ||
301 [[ "${dir2:0:${#dir1}}" = "${dir1}" ]]; then
302 return 0
305 return 1
308 usage() {
309 echo "usage: ${ME} old_dir patch_dir new_dir" >& 2
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}")"
318 trap cleanup EXIT HUP INT QUIT TERM
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
326 if [[ -e "${new_dir}" ]]; then
327 err "new_dir must not exist"
328 usage
329 exit 4
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
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}")"
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
354 g_cleanup+=("${new_dir}")
356 patch_dir "${old_dir}" "${patch_dir}" "${new_dir}"
358 unset g_cleanup[${#g_cleanup[@]}]
359 trap - EXIT
362 if [[ ${#} -ne 3 ]]; then
363 usage
364 exit 2
367 main "${@}"
368 exit ${?}