- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / installer / mac / dirpatcher.sh
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 ${?}